隔离级别实现的原理--读写锁|| MVCC(一次性快照读)
事务并发存在的问题
-
脏读:一个事务在提交之前,在事务过程中修改的数据,被其他事务读取到了。
-
不可重复读:一个事务在提交之前,在事务过程中读取以前的数据发生数据发生了改变
-
幻读:一个事务按照相同的条件读取以前检索过的数据时,缺发现了其他事务插入的新数据
-
更新丢失:两个并行操作,后进行的操作覆盖了先进行操作的操作结果
四种隔离级别
-
读未提交(RU):允许一个事务可以看到其他事务未提交的修改
-
读已提交(RC):允许一个事务只能看到其他事务已经提交的修改,未提交的修改不可见
-
可重复读(RR):确保如果一个事务中执行两次相同的select语句,都能得到相同的结果,不管其他事务是否提交这些修改
-
串行化(序列化):将一个事务与其他事务完全隔离开,读写不允许并发,保证了安全性
实现隔离机制的方法主要有两种:
-
读写锁
-
一致性快照读,即MVCC
读为提交,采取的是读不加锁原理
-
事务读不加锁,不阻塞其他事务的读写
-
事务写阻塞其他事务写,但不阻塞其他事务读
串行化
-
所有select语句会隐示加共享锁
-
读加共享锁,写加排它锁,读写互斥。如果有未提交的事务正在修改某些行,所有select这些行的语句都会阻塞
MVCC的实现原理
介绍:
-
MySQL中innoDb引擎支持MVCC
-
应对高并发事务,MVCC比单纯的加行锁更有效,开销更小
-
MVCC在读已提交(Read Committed) 和可重复读(Repeatable Read)隔离级别下起作用。
-
MVCC既可以基于乐观锁又可以基于悲观锁
-
MVCC(多版本并发控制),它是通过读取历史版本的数据,来降低事务冲突。从而提高并发性能的一种机制。他的实现依赖于 隐示字段,undo日志,快照读&当前读,Read View。
-
隐藏字段
-
对于InnoDB存储引擎,每一行记录都有两个隐藏列DB_TRX_ID、DB_ROLL_PTR,如果表中没有主键和非NULL唯一键时,则还会有第三个隐藏的主键列DB_ROW_ID。
-
DB_TRX_ID,记录每一行最近一次修改(修改/更新)它的事务ID,大小为6字节;
-
DB_ROLL_PTR,这个隐藏列就相当于一个指针,指向回滚段的undo日志,大小为7字节;
-
DB_ROW_ID,单调递增的行ID,大小为6字节;
-
-
-
undo日志
-
事务未提交的时候,修改数据的镜像(修改前的旧版本)存到undo日志里。以便事务回滚时,恢复旧版本数据,撤销未提交数据对数据库的影响。
-
undo日志是逻辑日志。可以认为,当删除一个记录时,undo log中就会记录一条对应的insert记录,当update一条数据时,它就记录一条相应相反的update记录
-
存放undo日志的地方,就是回滚段
例:对事务A的操作过程
-
对DB_ROW_ID(单调递增的行ID)= 1 的这行记录加锁
-
把该行的原有值拷贝到undo log中,DB_TRX_ID(记录最新修改的一个事务ID) 和DB_ROLL_PTR(指向回滚) 都不动
-
修改该行的值这是产生一个新的版本,更新DATA_TRX_ID 为修改记录的事务ID,将DATA_ROLL_PIR指向刚刚拷贝到undo log链中的旧版本记录,这样可以通过DB_ROLL_PTR(指向回滚的指针)找到这条记录的历史版本。如果对同一行记录执行连续的UPdate,undo log会组成一个链表,遍历这个链表可以看到这条记录的变迁。
-
记录redo log 包括undo log中的修改
多个事务并向操作一行数据时,不同事务对该行数据的修改会产生多个版本,然后通过回滚指针连一条undo日志连。
快照读:
-
读取的是记录数据的可见版本(有旧的版本),不加锁,普通的select语句都是快照读
当前读:
-
读取的是记录数据的最新版本,显示加锁的都是当前读
select * from account where id>2 lock in share mode
-
-
(如何实现一致性读)--Read View
解释:在读未提交(RU)隔离级别下,直接读取版本的最新记录,对于(序列化)隔离级别下,则是通过加锁互斥来访问数据,因此不需要MVCC的帮助。因此MVCC运行在RC(读已提交)和RR(重复读)这两个隔离级别下,当innodb隔离级别设置为二者其一时,在select数据就会用到版本链
核心问题:版本链中那些版本对当前事务可见?
innoDB 为了解决这个问题,设计了ReadView(可读视图)的概念
RR下的ReadView生成
在RR隔离级别下,每个事务touch first read时(本质上就是执行第一个select语句时,后续所有的select都是复用这个ReadView,其他update,delete,insert语句和一致性读的建立没有关系),会将当前系统中所有的活跃事务拷贝到一个列表生成ReadView。
RC下的ReadView生成
在RC隔离级别下,每个select语句开始时,都会重新将当前系统中所有的活跃事务拷贝到一个列表生成ReadView.
与RR区别:就在于生成ReadView的时间点不同,一个是事务之后第一个select语句开始,一个是事务中每条select语句开始
ReadView中是当前活跃的事务ID列表,称之为m_ids,其中最小值为up_limit_id,最大值为low_limit_id,事务ID是事务开启时InnoDB分配的,其大小决定了事务开启的先后顺序,因此我们可以通过ID的大小关系来决定版本记录的可见性,具体判断流程如下:-
如果被访问版本的
trx_id小于m_ids中的最小值up_limit_id,说明生成该版本的事务在ReadView生成前就已经提交了,所以该版本可以被当前事务访问。 -
如果被访问版本的
trx_id大于m_ids列表中的最大值low_limit_id,说明生成该版本的事务在生成ReadView后才生成,所以该版本不可以被当前事务访问。需要根据Undo Log链找到前一个版本,然后根据该版本的 DB_TRX_ID 重新判断可见性。 -
如果被访问版本的
trx_id属性值在m_ids列表中最大值和最小值之间(包含),那就需要判断一下trx_id的值是不是在m_ids列表中。如果在,说明创建ReadView时生成该版本所属事务还是活跃的,因此该版本不可以被访问,需要查找 Undo Log 链得到上一个版本,然后根据该版本的DB_TRX_ID再从头计算一次可见性;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。 -
此时经过一系列判断我们已经得到了这条记录相对
ReadView来说的可见结果。此时,如果这条记录的delete_flag为true,说明这条记录已被删除,不返回。否则说明此记录可以安全返回给客户端。
-

浙公网安备 33010602011771号