MySQL 是一个客户端/服务器架构的软件,对于同一个服务器来说,可以有若干个客户端与之连接,每个客户端与服务器连接上之后,就称之为一个会话(Session)。每个客户端都可以在自己的会话中向服务器发出请求语句,一个请求语句可能是某个事务的一部分,也就是对于服务器来说可能同时处理多个事务
21.2.1 事务并发执行时遇到的一致性问题
脏写:一个事务修改了另一个未提交事务修改过的数据
脏读:一个事务读到了另一个未提交事务修改过的数据
不可重复读:一个事务只能读到另一个已经提交的事务修改过的数据,并且其他事务每对该数据进行一次修改并提交后,该事务都能查询得到最新值
幻读:一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次按照该条件查询时,能把另一个事务插入的记录也读出来
21.2.2 SQL标准中的4种隔离级别
READ UNCOMMITTED
:未提交读READ COMMITTED
:已提交读REPEATABLE READ
:可重复读SERIALIZABLE
:可串行化
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ UNCOMMITTED | √ | √ | √ |
READ COMMITTED | × | √ | √ |
REPEATABLE READ | × | × | √ |
SERIALIZABLE | × | × | × |
21.3 MVCC原理
每次对记录进行改动,都会记录一条 undo 日志,每条 undo 日志也都有一个roll_pointer
属性(INSERT 操作对应的 undo 日志没有该属性,因为该记录并没有更早的版本),可以将这些 undo 日志都连起来,串成一个链表,称为版本链。版本链的头节点就是当前记录最新的值。另外,每个版本中还包含生成该版本时对应的事务id
21.3.2 ReadView
需要判断一下版本链中的哪个版本是当前事务可见的
ReadView 中主要包含4个比较重要的内容
m_ids
:表示在生成 ReadView 时当前系统中活跃的读写事务的事务id列表min_trx_id
:表示在生成 ReadView 时当前系统中活跃的读写事务中最小的事务id,也就是m_ids
中的最小值max_trx_id
:表示生成 ReadView 时系统中应该分配给下一个事务的id值creator_trx_id
:表示生成该 ReadView 的事务的事务id
访问某条记录时,需要判断记录的某个版本是否可见
- 如果被访问版本的
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
之间,那就需要判断一下是不是在m_ids
列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问
如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后一个版本。如果最后一个版本也不可见的话,那么就意味着该条记录对该事务完全不可见,查询结果就不包含该记录
- 使用
READ COMMITTED
隔离级别的事务在每次查询开始时都会生成一个独立的 ReadView- 对于使用
REPEATABLE READ
隔离级别的事务来说,只会在第一次执行查询语句时生成一个 ReadView,之后的查询就不会重复生成了