MVCC(Multi-Version Concurrency Control)在数据库系统中是一种并发控制机制,它通过维护数据的不同版本来解决事务间的读写冲突,尤其是像MySQL InnoDB存储引擎这样需要支持高并发访问的场景。下面详细讲解MVCC的底层原理和实现方式:
隐藏列:
- InnoDB为每一行记录添加了额外的隐藏列,例如:
DB_TRX_ID
:记录最后一次修改该行数据的事务ID。DB_ROLL_PTR
(或称为trx_id
):指向回滚段中的undo日志记录,用于恢复旧版本的数据。- 在某些情况下还可能有
DB_ROW_ID
作为唯一标识符。
- InnoDB为每一行记录添加了额外的隐藏列,例如:
Undo Logs(回滚日志):
- 当事务对一行数据进行修改时,并不会直接覆盖原有的值,而是创建一个新的数据版本,并将旧版本的数据保存在undo日志中。
- Undo日志按照时间顺序链接起来,形成一个链表结构,用于在事务回滚时能够恢复到之前的状态,同时也用于提供给其他并发事务查询历史版本。
Read View(读视图):
- 每个只读事务开始时或者在一个可重复读(Repeatable Read)隔离级别下执行快照读操作时,都会生成一个读视图。
- 读视图包含以下信息:
m_ids
:所有未提交事务的事务ID集合。low_limit_id
:最小未提交事务ID。up_limit_id
:当前系统事务ID+1。creator_trx_id
:创建这个读视图的事务ID。
可见性判断规则:
- 当事务尝试读取一行数据时,会根据这行数据的
DB_TRX_ID
与当前事务的读视图进行比较,判断数据版本是否可见:- 如果
DB_TRX_ID
小于read_view.low_limit_id
,表示这个版本是在当前事务开始前就已经提交的,因此是可见的。 - 如果
DB_TRX_ID
在read_view.m_ids
范围内,表示这是由未提交事务创建的新版本,不可见。 - 如果
DB_TRX_ID
大于等于read_view.up_limit_id
,则事务已提交且在视图创建后,也是不可见的。
- 如果
- 当事务尝试读取一行数据时,会根据这行数据的
事务版本号管理:
- 每个新启动的事务都会被赋予一个全局递增的事务ID,这个ID用来决定事务之间的影响范围以及数据版本的可见性。
垃圾回收(Purge):
- 不再需要的历史版本数据(即没有事务能看到的老版本)需要通过purge线程清理掉,以释放空间。
- Purge线程会定期从undo日志中找出那些不再影响任何事务的已提交事务产生的旧版本记录并删除。
MVCC使得不同事务可以同时看到不同的数据版本,从而避免了加锁带来的阻塞等待,提高了系统的并发性能,并确保了事务的隔离性。在可重复读隔离级别下,同一个事务内的多次SELECT查询结果始终是一致的,不受其他并发事务的更新影响。
王司徒对话诸葛亮
诸葛村夫(亮):二成贼子,可知数据库事务处理之难?
二成贼子(允):孔明休要卖弄学问,快道来听听!
亮:若各路兵马同时读写军情,互不相让,岂非乱作一团?犹如你那司徒府的账本,要是人人都可随意涂改,如何确保账实相符?
允:嘿!那该如何是好?
亮:这就得提我大MySQL中InnoDB的“多版本并发控制”(MVCC),就好比咱们每更迭一次军令,都留个备份,不急于销毁。看官儿们(事务)都能看到适合自己那个时间点的“旧版军令”,互不影响。
允:哦,所以这MVCC就是给每个记录加上了小秘密——“DB_TRX_ID”相当于谁最后动的手脚,“DB_ROLL_PTR”指向藏着旧版军令的秘密仓库(undo日志),还有个“DB_ROW_ID”便于找寻记录。
亮:没错!而且每次你想查账(只读操作),就像你二成贼子定下规矩,从这一刻起,甭管后面怎么变,咱就看这一刻之前的账目。这就是“读视图”。
允:那我明白了,我只要看看这文牒上的印记(DB_TRX_ID)是不是在我立规矩之前,就能知道能不能看、该不该看。
亮:正是如此,如果改动早于你定下的规矩,自然可以查看;若是晚于或者还在审核中,你就看不到。这样既能保证速度,又保持了各自的独立性。
允:哼,听起来挺机巧的,不过这些旧版的军令(历史数据)不会一直堆着吧,总得清理清理?
亮:二成贼子还算有点见识,确实如此。那些无人问津的老版本(已提交且无事务需要的undo日志),系统会定期清理回收,就如同我们处理过时的军令一样。
允:原来如此,诸葛村夫这次没诓我,这MVCC确实高明,以后我司徒府也要学学这数据库的管理之道!
亮:哈哈,二成贼子能明白便好,虽是兵法与数据库原理相通,但实际应用还需细思量,方能在信息战场上决胜千里之外也!