事务隔离级别
事务并发执行问题
- 脏写:一个事务修改了另一个未提交事务修改过的数据,就是脏写
- 脏读:一个事务读到了另一个未提交事务修改过的数据,就是脏读
- 不可重复读:如果一个事务只能读到另一个已提交事务修改过的数据,并且其他事务每次对该数据修改并提交后,该事务都能查到最新值,这就是不可重复读
- 幻读:如果一个事务按照一定条件查询出一些记录,之后另一个事务插入了符合该条件的记录,原有的事务再按照该条件进行查询时,能把刚事务插入的记录查询出来,这就是幻读。幻读是强调一个事务按照某个相同条件多次查询时,之后的查询读到了之前没有的数据
SQL标准中的四种隔离级别
- READ UNCOMMITTED : 未提交读
- READ COMMITTED : 已提交读
- REPEATABLE READ : 可重复读
- SERIALIZABLE : 可串行化
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 未提交读 | ✔️ | ✔️ | ✔️ |
| 已提交读 | ❎ | ✔️ | ✔️ |
| 可重复读 | ❎ | ❎ | ✔️ |
| 可串行化 | ❎ | ❎ | ❎ |
MySQL 隔离级别
MySQL 支持四种隔离级别,但是MySQL 对于 REPEATABLE READ 可以禁止幻读问题的发生。默认隔离级别是 REPEATABLE READ
设置MySQL 隔离级别
SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level;
level 有四种选择
level: {
REPEATABLE READ
| READ COMMITTED
| READ UNCOMMITTED
| SERIALIZABLE
}
- GLOBAL
- 只对执行完该语句之后的会话有效
- 当前已存在的会话无效
- SESSION
- 对当前会话的所有后续事务有效
- 该语句可以在已经开启的事务中间执行,但不会影响当前正在执行的事务
- 如果在事务之间执行,则会影响之后的事务
- 省略
- 只对当前会话中下一个开启的事务起作用
- 下一个事务执行完后,又恢复到之前的隔离级别
- 不可在已开启的事务中执行
MVCC 原理
所谓的MVCC(Multi-Version Concurrency Control ,多版本并发控制)指的就是在使用READ COMMITTD、REPEATABLE READ这两种隔离级别的事务在执行普通的SELECT操作时访问记录的版本链的过程,这样子可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能
版本链
InnoDB 存储引擎中,他的聚簇索引记录包含两个必要隐藏列:trx_id,roll_pointer;
- trx_id: 每一个事务对某条聚簇索引记录改动时,都会把该事务的事务ID赋值给 trx_id
- roll_pointer: 每次对某条聚簇索引记录该董事,都会把旧的数据写到 undo日志 中,可以通过 roll_pointer 找到该记录修改前记录
每次对记录进行改动,都会记录一条undo日志,每条undo日志也都有一个roll_pointer属性,可以将这些undo日志都连起来,串成一个链表,这个链表就是 版本链
ReadView
对于使用READ COMMITTED和REPEATABLE READ隔离级别的事务来说,都必须保证读到已经提交了的事务修改过的记录,也就是说假如另一个事务已经修改了记录但是尚未提交,是不能直接读取最新版本的记录的,为此提出 ReadView 概念
ReadView 属性
-
m_ids :生成ReadView时,当前系统中活跃的读写事务的 事务ID 集合
-
min_trx_id :生成ReadView时,当前系统中活跃的读写事务中,最小的 事务ID
-
max_trx_id :生成ReadView时,系统应该分配给下一个事务的 事务ID
-
creator_trx_id :生成该ReadView的 事务ID
对表中的记录做改动时(执行INSERT、DELETE、UPDATE这些语句时)才会为事务分配事务id,否则在一个只读事务中的事务id值都默认为0
事务是否可见判断准则:
- 如果被访问版本的 trx_id 的属性值与ReadView中 creator_trx_id 一致,意味着当前事务访问他自己修改过的值,所以该版本可以被当前事务访问
- 如果被访问版本的 trx_id 的属性值小于ReadView中 min_trx_id ,意味着该版本的事务在当前事务生成 ReadView 前已提交,所以该版本可以被当前事务访问
- 如果被访问版本的 trx_id 的属性值大于或等于ReadView中 max_trx_id ,意味着该版本的事务在当前事务生成 ReadView 之后才开启,所以该版本不可以被当前事务访问
- 如果被访问版本的 trx_id 属性值在 ReadView 的 min_trx_id 和 max_trx_id 之间,那就需要判断一下 trx_id 属性值是不是在 m_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问
READ COMMITTED和REPEATABLE READ所谓的生成ReadView的时机
- READ COMMITTED —— 每次读取数据前都生成一个ReadView
- REPEATABLE READ —— 在第一次读取数据时生成一个ReadView
