1. 事务隔离与MVCC:不只是背概念,更要懂实战
面试官问你事务隔离级别,如果你只背出“读未提交、读已提交、可重复读、串行化”这四个名词,那大概率只能拿到一个及格分。真正拉开差距的,是你能不能说清楚它们在实际业务中到底是怎么工作的,以及为什么MySQL默认选择了“可重复读”(Repeatable Read, RR)而不是“读已提交”(Read Committed, RC)。我自己在项目里就踩过坑,一个财务对账系统,因为早期隔离级别设置不当,出现了同一笔订单金额前后两次查询结果不一致的“幻读”问题,导致对账不平,排查了大半天。
1.1 隔离级别的本质:在性能和数据一致性之间走钢丝
事务的四大特性ACID里,隔离性(Isolation)是最微妙的一个。它不像原子性(A)和持久性(D)那样非黑即白,而是在“并发性能”和“数据准确性”之间做权衡。你可以把它想象成一个多线程读写同一个Excel文件的场景。读未提交(Read Uncommitted)就是A线程正在改一个单元格,还没保存,B线程就能看到这个半成品数据,这显然风险极大,几乎没人用。读已提交(RC)则要求B线程必须等A线程保存(提交)了之后,才能看到最新数据,这解决了“脏读”,但带来了“不可重复读”问题——在同一个事务里,两次读同一个数据,中间被其他事务修改并提交了,结果就不一样了。
MySQL的InnoDB引擎默认使用可重复读(RR),它通过一种叫快照读(Snapshot Read)的机制,在事务开始的时候,给它拍个“数据快照”。之后在这个事务里,无论别的事务怎么增删改,它读到的都是自己快照里的那个旧版本数据,从而保证了“可重复读”。那“幻读”呢?幻读是指一个事务里两次查询,第二次查到了第一次没有的新行(被其他事务插入的)。在RR级别下,普通的SELECT快照读是能避免幻读的,但如果你用的是SELECT ... FOR UPDATE这种当前读(Current Read),InnoDB就会通过**间隙锁(Gap Lock)和临键锁(Next-Key Lock)**来锁住一个范围,阻止其他事务在这个范围内插入新数据,从而从根源上杜绝幻读。这就是RR比RC更“严格”的地方,也是它作为默认级别的底气。
1.2 MVCC:让读写互不阻塞的魔法
理解了隔离级别,MVCC(多版本并发控制)就很好懂了。它就是实现“快照读”那个魔法的具体技术。每次你修改一行数据,InnoDB并不会直接覆盖旧数据,而是会生成该行数据的一个新版本,同时旧版本被保留在**Undo Log(回滚日志)**里。每一行数据都有两个隐藏字段:trx_id记录最近修改它的事务ID,roll_pointer指向它在Undo Log里的旧版本链表。
当一个事务执行查询时,它会拿到一个当前系统活跃事务ID的视图。对于每一行数据,它会沿着roll_pointer组成的版本链,找到第一个trx_id小于等于当前事务视图ID、且已提交的版本。这样,这个事务就能“穿越”到过去,看到它启动那一刻的数据快照,而不用管这期间其他事务是否已经提交了更新。这个机制的精妙之处在于,读操作完全不用加锁,写操作也只需要做行级锁,读写之间互不阻塞,极大地提升了数据库的并发吞吐量。我当初理解这一点后,再看那些高并发场景下的数据库设计,思路就清晰多了。
1.3 Undo Log、Redo Log、Binlog:三大日志,各司其职
面试中经常被拿来一起问的,就是这三大日志。很多朋友容易记混,我教你一个生活化的记法:把数据库想象成一个银行柜台。
- Undo Log(回滚日志):相当于你的草稿纸。柜员办理业务(事务)时,每一步修改先在草稿纸上记下原值。如果客户突然说“我不办了”(事务回滚),柜员就照着草稿纸把数据改回去。同时,它也是MVCC实现快照读的“历史档案库”。
- Redo Log(重做日志):相当于银行的流水账本。柜员每办完一笔确认的业务(事务提交),就立刻用复写纸在流水账本上记一笔“某账户支出/收入XX元”。这个账本是顺序追加写的,速度极快。万一银行停电(数据库崩溃

250

被折叠的 条评论
为什么被折叠?



