MySQL数据库读写并发存在线程安全问题,比如脏读、幻读、不可重复读
MySQL实现隔离性本质是通过MVCC和Read View
多版本并发控制(MVCC):
是MySQL解决读写冲突的一种无锁的策略,根据事务开始的先后顺序,按递增为事务分配不同的事务ID
记录的一些隐藏字段:
DB_TRX_ID:最近修改改记录的事务ID
DB_ROLL_PTR: 回滚指针,指向这条记录的上一个版本
DB_ROW_ID: 隐藏主键,如果数据表没有主键, InnoDB以 DB_ROW_ID 生成一个聚簇索引
记录的不同的版本通过回滚指针相连形成了版本链
假如有一条记录是姓名为张三,ID为7的事务修改姓名为李四,ID为9的事务修改姓名为王五,形成的版本链大致如下:
回滚操作就是用历史版本覆盖当前版本,一个个版本可以叫做一个个快照
版本链存在MySQL的undo日志当中,undo日志可以理解为MySQL中的一段缓冲区
当前读和快照读:
读取某条记录的最新版本就是当前读,读取历史版本叫做快照读。
增删改都是当前读,需要加锁,select操作也有可能是当前读,这时也需要加锁
这时候这些当前读就是串行化的。快照读读取的是历史版本,所以不用加锁,这就是mvcc提高读写并发效率的方式。
select是当前读还是快照读取决于隔离级别,而确认读取版本链中的哪个版本还需要Read View
Read View 是MySQL 源码中的一个类
事务进行 快照读 的时候生产读视图 (Read View),可以记录维护系统当前活跃的事务的ID等信息。
Read View类成员中影响快照读读取哪个版本的成员有如下几个:
m_ids; //Read View生成时刻活跃事务ID的列表
up_limit_id; //m_ids列表中最小的ID
low_limit_id; //目前已出现过的事务ID的最大值+1
creator_trx_id //创建该ReadView的事务ID
确认读取版本链中的哪个版本就是通过版本中的ID和上面的类成员比较
如果隔离级别是读提交,可以认为没有隔离性,不用上述的方式,隔离级别如果是串行化,操作都加锁自然也不需要上述方式,读提交和可重复读这两种隔离级别都会用到MVCC+Read View的方式来确定读取到的记录是哪一个版本
而读提交和可重复读隔离级别不同,也就是快照读读到的版本不同的原因在于:
读提交隔离级别在某个事务中的每一次快照读都会产生新的Read View,而可重复读隔离级别在某个事务中只在第一次快照读时产生Read View,之后不管其他事务做什么,这个Read View都不变。
总之,MySQL的隔离性本质的实现原理就是多版本并发控制(MVCC)和读视图(Read View)同时起作用。