首页 > 编程知识 正文

mysql间隙锁与mvcc,mysql间隙锁详解

时间:2023-05-04 07:40:27 阅读:166594 作者:4743

MySQL中MVCC的正确打开方式

最近正在学习MySQL的MVCC。 看了网上的各种版本,制作了版本号码,删除了。 一开始看起来是对的,但实际上经常是错的。 经过几天的比较调查,我觉得在一些博客的帮助下,终于正确理解了MySQL的MVCC。

本文对MVCC进行了总结,找到了相关源代码的验证(talk is cheap,show me the code )! 网络上的错误解释太多了)。 如果你刚接触MVCC,或者差点被网络上的各种解释弄晕,请继续看到底。 一定会有收获的。

目录

1、MVCC的概念

1.1、隐藏字段

1.2、读视图结构(重点) ) ) ) ) ) )。

1.3、还原日志

2、记录明细修改的具体流程

3、可见性比较算法

4、当前导线和快照导线

5、例(帮助理解)

1、MVCC的概念

多版本控制(Multiversion Concurrency Control ) :是一种提高同时性的技术。 第一个数据库系统只能在读和读之间同时阻止读写、写入和写入。 引入多个版本后,只有写入之间被阻止,其他三个操作可以并行,大大提高了InnoDB的并发性。 在内部实现中,InnoDB以还原日志形式存储每个数据的多个版本,并恢复数据的历史版本以向用户提供读取。 每个事务读取的数据版本可能不同。 在同一事务中,用户只能看到在事务创建快照之前提交的更改以及事务本身所做的更改。

MVCC仅在两个隔离级别上运行:读命令和读命令。 其他两个隔离级别与MVCC不兼容,Read Uncommitted始终读取最新的记录行,并且不是与当前事务版本匹配的记录行。 Serializable锁定所有读取的记录行。

MySQL的InnoDB存储引擎的缺省事务隔离级别为可重复读取(RR ),并通过“行级锁MVCC”一起实现。 通常的读取时不锁定,写入时锁定。 MCVV的实现取决于隐藏字段、读视图和还原日志。

1.1、隐藏字段

InnoDB存储引擎在每行数据的后面添加了三个隐藏字段。

1.db_Trx_ID(6个字节) :指示对该记录行进行上次修改后的事务处理id。 对于delete操作,InnoDB识别为update操作,但会更新另一个将行表示为deleted的删除位。 并不是真的删除了。

2.db_roll_ptr(7字节) :回滚指向当前记录行中的还原日志信息的指针

3.db_row_ID(6个字节) :行id随着插入新行而单调增加。 理解:如果表没有主键或没有非空唯一索引,innodb将使用此行ID自动生成簇索引。 如果表具有主键或非空的唯一索引,则聚簇索引不包含行ID。 此DB_ROW_ID与MVCC关系不大。

隐藏字段不是版本的创建或删除。 官方文档: 14.3 innodb多版本

同调

1.2、读视图结构(重点) ) ) ) ) ) )。

实际上,Read View (读取视图)与快照和快照的概念相同。

Read View主要用于确定可见性,它包含“对本事务不可见的其他活动事务”。

Read View结构的源代码中包含几个变量,互联网上对这些变量的解释各种各样,以下结合源代码给出对它们的正确解释。

low_limit_ID :当前发生的最大事务ID 1,即下一个分配的事务id。 源代码350行:

628行源代码中的max_trx_ID的定义如下,翻译为“最小的未被分配的事务ID”,即下一个被分配的事务ID。 (low_limit_ID不是活动事务列表中最大的事务id。)

up_limit_id :活动事务列表trx_ids中最小的事务id。 在Trx_ids为空的情况下,up_limit_id为low_limit_id。 源代码358行:

最后一个是最小的活动事务id,因为trx_IDs的活动事务编号是反向的。 (up_limit_id不是提交的最大事务ID 1。 后面的例子2将证明这是错误的)

Trx _ ids :创建读视图时其他未提交的活动事务id的列表。 这意味着,创建Read View时,将记录当前未提交的事务ID,并且如果随后记录行的值发生更改,则不会显示在当前事务中。

注: Read View中trx_ids的活动事务处理不包括当前事务处理本身和提交的事务处理(在内存中)。 源代码295行:

creator_trx_ID :当前创建的事务处理的id。 是增量号码,源代码为345行。 (此编号不是DB_ROW_ID )

1.3、还原日志

还原日志包含旧版本的数据。 如果事务需要读取记录行,并且当前记录行不可见,则可以沿着还原日志链找到满足可显示条件的记录行版本。

大多数对数据的变更操作包括 insert/update/delete,在InnoDB里,undo log分为如下两类:

①insert undo log : 事务对insert新记录时产生的undo log, 只在事务回滚时需要, 并且在事务提交后就可以立即丢弃。

②update undo log : 事务对记录进行delete和update操作时产生的undo log,不仅在事务回滚时需要,快照读也需要,只有当数据库所使用的快照中不涉及该日志记录,对应的回滚日志才会被purge线程删除。

Purge线程:为了实现InnoDB的MVCC机制,更新或者删除操作都只是设置一下旧记录的deleted_bit,并不真正将旧记录删除。

为了节省磁盘空间,InnoDB有专门的purge线程来清理deleted_bit为true的记录。purge线程自己也维护了一个read view,如果某个记录的deleted_bit为true,并且DB_TRX_ID相对于purge线程的read view可见,那么这条记录一定是可以被安全清除的。

2、记录行修改的具体流程

假设有一条记录行如下,字段有Name和Honor,值分别为"Curry"和"MVP",行ID是1,最新修改这条记录的事务ID为1。

(1)现在事务A(事务ID为2)对该记录的Honor做出了修改,将Honor改为"FMVP":

①事务A先对该行加排它锁

②然后把该行数据拷贝到undo log中,作为旧版本

③拷贝完毕后,修改该行的Honor为"FMVP",并且修改DB_TRX_ID为2(事务A的ID), 回滚指针指向拷贝到undo log的旧版本。(然后还会将修改后的最新数据写入redo log)

④事务提交,释放排他锁

(2) 接着事务B(事务ID为3)修改同一个记录行,将Name修改为"Iguodala":

①事务B先对该行加排它锁

②然后把该行数据拷贝到undo log中,作为旧版本

③拷贝完毕后,修改该行Name为"Iguodala",并且修改DB_TRX_ID为3(事务B的ID), 回滚指针指向拷贝到undo log最新的旧版本。

④事务提交,释放排他锁

从上面可以看出,不同事务或者相同事务的对同一记录行的修改,会使该记录行的undo log成为一条链表,undo log的链首就是最新的旧记录,链尾就是最早的旧记录。

3、可见性比较算法

在innodb中,创建一个新事务后,执行第一个select语句的时候,innodb会创建一个快照(read view),快照中会保存系统当前不应该被本事务看到的其他活跃事务id列表(即trx_ids)。当用户在这个事务中要读取某个记录行的时候,innodb会将该记录行的DB_TRX_ID与该Read View中的一些变量进行比较,判断是否满足可见性条件。

假设当前事务要读取某一个记录行,该记录行的DB_TRX_ID(即最新修改该行的事务ID)为trx_id,Read View的活跃事务列表trx_ids中最早的事务ID为up_limit_id,将在生成这个Read Vew时系统出现过的最大的事务ID+1记为low_limit_id(即还未分配的事务ID)。

具体的比较算法如下(可以照着后面的 例子 ,看这段):

1. 如果 trx_id < up_limit_id, 那么表明“最新修改该行的事务”在“当前事务”创建快照之前就提交了,所以该记录行的值对当前事务是可见的。跳到步骤5。

2. 如果 trx_id >= low_limit_id, 那么表明“最新修改该行的事务”在“当前事务”创建快照之后才修改该行,所以该记录行的值对当前事务不可见。跳到步骤4。

3. 如果 up_limit_id <= trx_id < low_limit_id, 表明“最新修改该行的事务”在“当前事务”创建快照的时候可能处于“活动状态”或者“已提交状态”;所以就要对活跃事务列表trx_ids进行查找(源码中是用的二分查找,因为是有序的):

(1) 如果在活跃事务列表trx_ids中能找到 id 为 trx_id 的事务,表明在“当前事务”创建快照前,“该记录行的值”被“id为trx_id的事务”修改了,但没有提交;或者在“当前事务”创建快照后,“该记录行的值”被“id为trx_id的事务”修改了(不管有无提交);这些情况下,这个记录行的值对当前事务都是不可见的,跳到步骤4;

(2)在活跃事务列表中找不到,则表明“id为trx_id的事务”在修改“该记录行的值”后,在“当前事务”创建快照前就已经提交了,所以记录行对当前事务可见,跳到步骤5。

4. 在该记录行的 DB_ROLL_PTR 指针所指向的undo log回滚段中,取出最新的的旧事务号DB_TRX_ID, 将它赋给trx_id,然后跳到步骤1重新开始判断。

5. 将该可见行的值返回。

比较算法源码 84行,也可看下图,有注释,图代码来自 link:

4、当前读和快照读

快照读(snapshot read):普通的 select 语句(不包括 select ... lock in share mode, select ... for update)

当前读(current read) :select ... lock in share mode,select ... for update,insert,update,delete 语句(这些语句获取的是数据库中的最新数据,官方文档:14.7.2.4 Locking Reads )

只靠 MVCC 实现RR隔离级别,可以保证可重复读,还能防止部分幻读,但并不是完全防止。

比如事务A开始后,执行普通select语句,创建了快照;之后事务B执行insert语句;然后事务A再执行普通select语句,得到的还是之前B没有insert过的数据,因为这时候A读的数据是符合快照可见性条件的数据。这就防止了部分幻读,此时事务A是快照读。

但是,如果事务A执行的不是普通select语句,而是select ... for update等语句,这时候,事务A是当前读,每次语句执行的时候都是获取的最新数据。也就是说,在只有MVCC时,A先执行 select ... where nid between 1 and 10 … for update;然后事务B再执行  insert … nid = 5 …;然后 A 再执行 select ... where nid between 1 and 10 … for update,就会发现,多了一条B insert进去的记录。这就产生幻读了,所以单独靠MVCC并不能完全防止幻读。

因此,InnoDB在实现RR隔离级别时,不仅使用了MVCC,还会对“当前读语句”读取的记录行加记录锁(record lock)和间隙锁(gap lock),禁止其他事务在间隙间插入记录行,来防止幻读。也就是前文说的"行级锁+MVCC"。

如果你对这些锁不是很熟悉,这是一篇将MySQL 中锁机制讲的很详细的博客 。

RR和RC的Read View产生区别:

①在innodb中的Repeatable Read级别, 只有事务在begin之后,执行第一条select(读操作)时, 才会创建一个快照(read view),将当前系统中活跃的其他事务记录起来;并且事务之后都是使用的这个快照,不会重新创建,直到事务结束。

②在innodb中的Read Committed级别, 事务在begin之后,执行每条select(读操作)语句时,快照会被重置,即会重新创建一个快照(read view)。

官方文档:consistent read,里面所说的consistent read 一致性读,我的理解就是 快照读,也就是普通select语句,它们不会对访问的数据加锁。     只有普通select语句才会创建快照,select ... lock in share mode,select ... for update不会,update、delete、insert语句也不会,因为它们都是 当前读,会对访问的数据加锁。

5、例子(帮助理解)

假设原始数据行:

Field    DB_ROW_ID    DB_TRX_ID    DB_ROLL_PTR

0    10    10000    0x13525342

例子1

例子2

(证明“up_limit_id为已提交的最大事务ID + 1”是错误的)

例子3

(跟例子2一样的情况,不过up_limit_id变为trx_ids中最小的事务ID):

MySQL中MVCC的正确打开方式相关教程

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。