事务隔离级别
mysql是一个客户端/服务器架构的软件,对于同一个服务器来说,可以有多个客户端与之连接。每个客户端与服务器连接后就形成了一个会话。每个客户端都可以在自己的会话中向服务器发出请求语句,一个请求语句可能是某个事务的一部分,服务器可以同时处理来自多个客户端的多个事务
有一个例子,狗哥向猫爷同时进行了两次转账5元,一开始狗哥有11元,猫爷有2元,如果前后两次转账对应的事务分别命名为T1和T2
按如图的执行顺序来进行两次转账,则最终狗哥的账户里还剩6元,只扣了5元;但是猫爷的账户有12元,多了10元,账户总余额变为18元,显然违背了参与转账的账户的总余额保持不变的一致性需求
我们需要使用某种手段来强制这些事务按照顺序一个一个单独地执行,或保证最终执行的效果和单独执行的一样【“隔离”执行,互不干涉;事务的隔离性】
串行执行太严格了,会严重降低系统吞吐量和资源利用率,增加事务的等待时间
并发事务之所以可能影响一致性,是因为他们在执行过程中可能访问相同的数据,又分别前后对其进行修改;因此我们可以在某个事务访问某个数据时,要求其他试图访问相同数据的事务进行限制,当该事务提交后,其他事务才能继续访问这个数据,使其并发执行的结果和串行执行的结果一样【可串行化执行】
事务并发执行时遇到的一致性问题
- 脏写
- 一个事务修改了另一个未提交事务修改过的数据
- 脏写数据可能引发一致性问题,也可能破坏原子性和持久性
- 脏读
一个事务读到了另一个未提交事务修改过的数据
在这个执行顺序下,T2只读事务读取到的x=1,y=0,虽然最终数据库状态还是一致的,但是T2 事务得到了一个不一致状态,数据库的不一致状态是不应该暴露给用户的
- 不可重复读/模糊读(fuzzy read)
- 一个事务修改了另一个未提交事务读取的数据
- 可能引发一致性问题
- 幻读phantom
- 一个事务先根据某些搜索条件查询出一些记录,在该事务未提交时,另一个事务写入了一些符合那些搜索条件的记录
一致性需求是让z表示符合搜索条件P的记录数- T1先读区符合搜索条件P的记录,然后T2插入了一条符合搜索条件P的记录,并且更新数据项z的值为4;然后T2提交,之后T1再读取数据项z,z的值变为了4,与T1之前实际读出的符合搜索条件P的记录条数不符,不符合一致性需求
mysql的默认隔离级别是repeatable read
MVCC原理
版本链
trx_id:一个事务每次对某条聚簇索引记录进行改动时,都会把该事务的事务id赋值给trx_id隐藏列
roll_pointer:每次对某条聚簇索引记录进行修改时,都会把旧版本写入到undo日志中,这个隐藏列相当于一个指针,可以通过它找到该记录修改前的信息
每次更新该记录后,都会将旧值放到一条undo日志中(该记录的一个旧版本),所有版本都会被roll_pointer属性连接成一个链表【版本链】
- 版本链的头节点就是当前记录的最新值
- 每个版本还包含生成该版本时对应的事务ID
利用这个版本链来控制并发事务访问相同记录时的行为【MVCC多版本并发控制机制】
对于使用read committed和repeatable read隔离级别的事务来说,都必须保证读到已经提交的事务修改过的记录,也就是说假如另一个事务已经修改了记录但是尚未提交,则不能直接读取最新版本的记录。核心问题:需要判断版本链中的哪个版本是当前事务可见的
ReadView
m_ids:当前系统中活跃的读写事务的事务id列表
min_trx_id:当前系统中活跃的读写事务中最小的事务id,也就是m_ids的最小值
max_trx_id:系统应该分配给下一个事务的事务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后才开启【访问的版本要大于当前活跃的事务和已提交事务对应的ID,说明该版本对应的事务还未发生】,所以该版本不可以被当前事务访问
- 如果被访问版本的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
二级索引与MVCC
只有在聚簇索引记录中才有trx_id和roll_pointer隐藏列,如果是使用二级索引来执行查询,怎么判断可见性
- 二级索引页面的Page Header中有一个PAGE_MAX_TRX_ID属性,每当对该页面中的记录执行增删改操作时,如果该事务id大于PAGE_MAX_TRX_ID属性值,就会把PAGE_MAX_TRX_ID属性设置为该事务的事务id【PAGE_MAX_TRX_ID属性代表着修改该二级索引页面的最大事务ID时多少】当select语句访问二级索引记录时,首先看对应的readview的min_trx_id是否大于该页面的PAGE_MAX_TRX_ID,如果是,则说明该页面所有记录都对该readview可见,否则执行2,在回表后再判断可见性
- 利用二级索引中的主键值进行回表,得到对应聚簇索引记录后再按照之前的方式找到对该readview可见的第一个版本,然后判断该版本中相应的二级索引列的值是否与利用该二级索引查询时的值相同