事务并发问题
- 脏写:一个事务修改了另外一个未提交事务修改过的数据,会导致数据不一致问题和持久性问题
- 脏读:一个事务读取了另外一个未提交事务修改过的数据,数据库出现了不一致状态,比如说读取的这个数据出现了回滚,即该数据没有意义了,但是仍然读取了.
- 不可重复读:一个事务修改了另外一个未提交事务读取的数据,比如说read1(x = 1),write2(x = 2),write2(y = 2),commit2(),read1(y = 2),commit1(),最终事务一提交事务读取的x值为1,y值为2,而x已经提交到数据库中,实际的值已经是2了,但是这里却只是1,出现了不一致问题
- 幻读:一个事务读取查询了一些记录的时候,另外一个未提交事务写入了一些符合这些查询条件的记录(包括插入和删除),导致该事务查询前后记录不一样,导致了不一致问题
事务隔离级别
严重性:脏写>脏读>不可重复读>幻读,脏写是最严重的,无论哪个事务隔离级别都不允许其发生
- READ UNCOMMITTED:未提交读,可能发生脏读、不可重复读、幻读现象
- READ COMMITTED:已提交读,克服了脏读问题,但是仍然不可避免不可重复读和幻读问题
- REAPEATABLE READ:可重复读,MySQL默认的级别,克服了脏读、不可重复读问题,但是可能发生幻读问题,如果加入MVCC机制就可以避免幻读问题了
- SEREALIZABLE:可串行化,代价最高,但解决了所有问题
如何设置事务隔离级别:
- 使用GLOBAL关键字,即全局起作用:SET GLOBAL TRANSACTION ISOLUTION LEVEL 上述事务隔离级别之一(英文)
- 只对执行完该语句之后的新会话起作用(会话就是数据库实例,即一个用户连接线程,比如说创建某个用户,用该用户登录数据库实例,即为一个完全会话)
- 对当前会话无效
- 使用SESSION关键字:SET SESSION TRANSACTION ISOLUTION LEVEL 上述事务隔离级别之一(英文)
- 只对当前会话有效
- 当前会话中所有后续事务有效
- 不影响正在执行的事务
- 不使用任何关键字:SET TRANSACTION ISOLUTION LEVEL 上述事务隔离级别之一(英文)
- 只对该语句之后的一个事务有效
- 后一个事务提交完恢复为默认隔离级别
MVCC机制
版本链
对于INNODB引擎,每一行数据都有两个隐藏列,为trx_id和roll_pointer
- trx_id:每一个事务对当前记录进行改动的时候,会把该事务ID赋值给trx_id
- roll_pointer:每当对该记录进行改动时,会把旧记录写入到undo日志,然后roll_pointer作为指针指向该undo日志,形成一个版本链
下图为对某一天记录进行添加修改时对版本链

快照ReadView
目的:用于判断版本链中哪个版本是当前事务可见的
其包含内容:
- m_ids:生成当前快照时,当前系统中活跃的读写事务的id列表
- Min_trx_id:生成快照时,当前系统中活跃的读写事务列表中的最小事务ID
- max_trx_id:同上最大的事务ID
- Creator_trx_id:生成该快照的事务ID
可见性判断规则:
- 如果当前记录的trx_id与快照的Creator_trx_id相同,说明记录在当前事务中进行改动的,因此可见
- 如果当前记录的trx_id小于快照的Min_trx_id,那么说明在生成该快照时,事务已经提交,因此可见
- 如果当前记录的trx_id大于快照的max_trx_id,说明是生成快照后提交的事务,因此不可见
- 如果当前记录的trx_id在快照的Min_trx_id与max_trx_id之间,那就需要判断是否在活跃列表m_ids中,如果在,那就说明生成该快照时,事务还未提交,因此不可见,如果不在,说明事务已经提交,因此可见
READ COMMITTED和REAPEATABLE READ之间的区别:
- READ COMMITTED:事务中每个语句都会生成一个ReadView
- REAPEATABLE READ:事务的第一个语句才生成一个ReadView,之后还是用该原来的ReadView,不会被提交在此执行过程中的事务提交修改数据所影响,因此可以解决不可重复提交问题Min_trx_id与
针对二级索引的MVCC
上述机制中的trx_id和roll_pointer在聚簇索引中才有,二级索引则不是,因此二级索引中如何判断可见性?
- 二级索引页面的Page Header中有一个PAGE_MAX_TRX_ID的属性,即改动该索引页的最大事务ID,每次对其进行改动时,都会更新PAGE_MAX_TRX_ID,因此当需要判断可见性的时候,会把快照中的Min_trx_id与PAGE_MAX_TRX_ID对比,如果前者大,说明事务已经提交,因此记录可见,如果不是,那么就需要执行回表判断
- 回表判断:利用二级索引的主键值进行回表操作,找到聚簇索引对应的记录,找到该快照可见的第一个版本即可,如果有查询条件则需要符合查询条件,否则跳过
