一.背景
数据库为了实现读写并行,也就是说某一行数据在修改的时候会阻塞其他线程修改但不会阻塞读,其他线程还可以读,因为大部分程序都是读多于写,从而大大提升并发性能,使用MVCC (Multiversion Concurrency Control)即多版本并发控制方式实现。
大部分主流的数据库比如mysql、oracle都是采用这种方式实现。
通过学习这种机制来更加了解数据库底层实现细节同时也是学习这种设计思想。
二.DML整体处理流程
上面流程中看出来:
一笔update请求在innodb中主要做以下操作:
1>.收到当前读请求加锁并返回数据给server。
2>.server过滤完数据,对满足条件的数据发起更新。
3>.innodb收到发起更新操作并不是修改原数据,而是copy一份出来,写入undo缓冲中,异步线程刷入位于共享表空间中undo文件。
4>.insert一条新纪录到redo日志到LRU列表大约37%处,Page Clearner线程会定时通过checkpoint机制将脏页刷新回磁盘。
5>.server发起commit
写binlog到缓冲区,默认sync_binlog=0则异步刷盘,发提交请求
innodb释放锁记录X锁,释放页表库的IX锁,
undo日志设置删除标记为1,会被purge线程定时清理掉。
写提交记录到history list,用于回放redo日志判断是否提交(redo因为记录的是物理日志所以是幂等的,重放多次结果都一样)。
注:因为redo日志在事务未提交之前已经写好了,所以很大的事物提交也是很快的,大部分工作都在事务进行中完成了。
Q:读线程怎么读到原来的数据?下面来看。
三.MVCC实现
接着上面的问题读线程怎么读到原来的数据,来实现事务隔离级别呢?
Innodb每行记录都有两个隐藏字段:
DB_TRX_ID :事务id,数据库为事务维护一个自增的序列,存该序列值。
DB_ROLL_PTR:回滚指针,用于update查询或回滚寻找undo版本数据。
当插入一条数据回滚段指针为NULL,事务id为当前事务id,因为这个不需要提供给其他线程读。
更新事务:
1.当前读返回并加锁。
2.把该行的逻辑值copy到undo缓冲区
3. 插入一条新记录,填写事务编号和回滚指针
4.记录redo日志。
MySQL就是根据事务ID判断记录是否可见,如果不可见继续按照DB_ROLL_PTR继续回溯查找。
四.MVCC如何判断记录可见
可见性分析:
假设原数据如下:
原数据:(新插入已提交)
idnameDB_TRX_IDDB_ROLL_PTR
1 mbj 1
执行update操作 :update t_user set name='mbj1' where id=1;
undo日志:(原数据copy一份出来至undo缓存中)
地址idnameDB_TRX_IDDB_ROLL_PTR
A100 1 mbj 1
LRU数据:(插入一条新数据至数据缓存池,当前事务正在修改未提交)
idnameDB_TRX_IDDB_ROLL_PTR
1 mbj1 2 A100
不同的隔离级别,数据的可见度不同。 RC隔离级别下允许幻读,期间新插入的数据是可以看到,RR下不允许。
innodb为了能支持多个事务同时查询,会将当前系统中的所有的进行中的事务拷贝到一个列表中(read view),
RC隔离级别,在事务中的每个语句开始时,copy活跃事务到read view。
RR隔离级别,在每个事务开始的时候,copy活跃事务到read view。
假设此时事务2:update t_user set name='mbj2' where id=1;(事务1未提交)
事务2 read view中有1。
获取数据DB_TRX_ID=1,因为1在read view中所以不展示,找到undo回滚数据DB_TRX_ID=0,同时DB_TRX_ID<当前事务id,也就是2.都满足,则返回数据。
网友评论