当前位置: 首页 > news >正文

MySQL日志锁

一、日志概述

1.1 日志分类

  1. 错误日志(Error Log):记录MySQL服务器在启动、运行过程中发生的错误和异常情况,如启动错误、语法错误等。
  2. 查询日志(Query Log):记录所有执行的查询语句,包括SELECT、INSERT、UPDATE、DELETE等操作。可以用于分析查询性能和调试问题,但需要注意对于高负载的系统,开启查询日志可能会对性能产生影响。
  3. 慢查询日志(Slow Query Log):记录执行时间超过指定阈值的查询语句。慢查询日志可以帮助你找出执行时间较长的查询,以便进行性能优化。
  4. 二进制日志(Binary Log):记录所有对数据库的更改操作,包括数据修改、表结构变更等。二进制日志可以用于数据恢复、主从复制等场景。
  5. 事务日志(Transaction Log):也称为重做日志(Redo Log),记录正在进行的事务的更改操作。事务日志用于保证数据库的ACID特性,并支持崩溃恢复。
  • 查询日志tips:
  1. 开启查询日志对MySQL的性能产生影响,高负载环境下,谨慎开启。
  2. 查询日志可能记录大量的查询语句,日志文件过大,需要定期清理,限制日志文件大小。
  3. 查询日志可能会包含敏感信息(密码),增加授权。

1.2 慢查询日志

        记录所有执行时间超过参数long_query_time设置值,默认10s,最小0,精度到微秒。

1.2.1 日志参数配置

        默认情况下,MySQL数据库没有开启慢查询日志,需要我们手动来设置这个参数。当然,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。

        慢查询日志默认是关闭的 。可以通过两个参数来控制慢查询日志 :

# 该参数用来控制慢查询日志是否开启, 可取值: 1 和 0 , 1 代表开启, 0 代表关闭
SET GLOBAL slow_query_log=1; 
​
# 该选项用来配置查询的时间限制, 超过这个时间将认为值慢查询, 将需要进行日志记录, 默认10s
SET long_query_time =0.1;

1.2.2 日志内容读取

        和错误日志、查询日志一样,慢查询日志记录的格式也是纯文本,可以被直接读取。

  1. 查询慢查询是否开启以及日志文件位置SHOW VARIABLES LIKE '%slow_query_log%';
  2. 查询long_query_time 的值。SHOW VARIABLES LIKE '%long_query_time%'; -- 查看值:默认10秒

二、MySQL事务日志

2.1 事务概述

  • 概述:就是由多个操作组成的一个逻辑单元,组成这个逻辑单元的多个操作要么都成功要么都失败。
  • 作用:保证数据的一致性

2.2 ACID四大特性

注意:

1、事务的隔离性由 锁机制 实现。

2、而事务的原子性、一致性和持久性由事务的 redo日志undo日志来保证。

  • redo log称为重做日志 ,它记录了对数据库进行修改的操作,包括插入、更新和删除等。Redo日志的主要作用是保证数据库的持久性和恢复能力。

  • undo log称为回滚日志 ,它记录了对数据库进行修改的操作的逆操作,用于实现事务的回滚和MVCC(多版本并发控制)。

2.3 **redo日志

2.3.1 innodb写数据过程

        innodb存储引擎是以页为单位来管理存储空间的。在真正访问页面之前,需要把磁盘上的页缓存到内存【Buffer Pool】之后在可以访问。所有的变更必须先更新缓存池中的数据。然后缓存池中的脏页会以一定的频率被刷到磁盘,通过缓存池来优化CPU和磁盘之间的鸿沟,这样就保证了整体的性能不会下降太快。

2.3.2 redo日志的意义

        Innodb引擎采用的是WAL技术(write-ahead logging) , 这种技术就是先写日志,再写磁盘,只有日志写入成功,才算事务提交成功,这里的日志就是redo log。通俗来讲,就是 MySQL 的写操作并不是立刻更新到磁盘上,而是先记录在日志上,然后在合适的时间再更新到磁盘上

  • redo log两个部分:
  1. 重做日志的缓冲 (redo log buffer) ,保存在内存中,是易失的。redo log buffer 大小,默认 16M ,最大值是4096M,最小值为1M。
  2. show variables like '%innodb_log_buffer_size%'
  3. 重做日志文件 (redo log file) ,保存在硬盘中,是持久的。

整体写数据的流程如下所示:

整体流程说明:

第1步:先将原始数据从磁盘中读入内存中来,修改数据的内存拷贝

第2步:生成一条重做日志并写入redo log buffer,记录的是数据被修改后的值

第3步:当事务commit时,将redo log buffer中的内容刷新到 redo log file,对 redo log file采用追加 写的方式

第4步:定期将内存中修改的数据刷新到磁盘中当发生宕机且数据未刷新到磁盘的时候,可以通过redo log来恢复,保证了ACID中的D,这就是redo log的作用

2.3.4 redo log的好处和特点

优点:

1、redo日志降低了刷盘频率

2、redo日志占用的空间非常小

特点:

1、redo日志是顺序写入磁盘的

2、事务执行过程中,redo log不断记录

2.3.5 redo log的刷盘策略

注意:redo log buffer刷盘到redo log file的过程并不是真正的刷到磁盘中去,只是刷入到 文件系统缓存(page cache)中去。那么就需要有刷盘的策略。

针对这种情况,InnoDB给出 innodb_flush_log_at_trx_commit 参数,该参数控制 commit提交事务时,如何将 redo log buffer 中的日志刷新到 redo log file 中。

-- 查看innodb_flush_log_at_trx_commit变量的值
SHOW VARIABLES LIKE '%innodb_flush_log_at_trx_commit%';

它支持三种策略:

1、设置为0交给后台表示每次事务提交时不进行刷盘操作【不往文件系统中写】。(系统默认master thread每隔1s进行一次重做日志的同步)。性能最佳、数据风险较高。

2、设置为1主动刷盘 表示每次事务提交时都将进行同步,刷盘操作( 默认值 )。数据安全性较高、性能稍差。

3、设置为2同步缓存 表示每次事务提交时都只把 redo log buffer 内容写入 page cache,不进行同步。由os自己决定什么时候同步到磁盘文件。性能较高数据安全性较高。在发生数据库故障时,可能会丢失最近提交的事务的数据,因为尚未刷新到磁盘上的日志文件中。

2.4 **undo日志

2.4.1 undo日志简介

redo log是事务持久性的保证,undo log是事务原子性的保证。在事务中更新数据前置操作其实是要先写入一个undo log。

Undo日志(Undo Log)是数据库管理系统(DBMS)中的一种事务日志,用于记录事务中执行的更改操作的反向操作。Undo日志的主要目的是为了支持数据一致性和事务的回滚。以下是有关Undo日志的一些关键概念和用途:

  1. 记录事务的撤销操作:Undo日志记录了事务执行期间对数据库进行的更改操作的反向操作。这些反向操作包括撤销事务中的更新、删除和插入操作,以便在需要时可以回滚(撤销)事务。

  2. 支持事务回滚:如果事务执行过程中发生错误,需要回滚事务,数据库引擎使用Undo日志来执行撤销操作,将数据库恢复到事务开始之前的状态。这确保了事务不会留下不一致的数据。

  3. 多版本并发控制:Undo日志还用于多版本并发控制(MVCC)系统,以支持并发事务。它允许事务在不锁定其他事务的情况下访问数据库,因为Undo日志中存储了旧版本的数据,从而允许不同事务同时访问相同的数据,而不会干扰彼此。

  4. 垃圾回收:数据库引擎定期清理不再需要的Undo日志,以释放存储空间。垃圾回收过程涉及删除已经不再需要的Undo日志,因为它们所记录的更改操作已经被永久应用到了数据库中。

  5. 事务的隔离级别:Undo日志在不同的事务隔离级别中起到关键作用,如读已提交(Read Committed)和可重复读(Repeatable Read)。在这些隔离级别下,Undo日志可以用于控制不同事务之间对相同数据的可见性。

2.4.2 undo log存储结构

  • 回滚段与undo页

InnoDB对undo log的管理采用段的方式,也就是回滚段(rollback segment) 。每个回滚段记录了 1024 个 undo log segment ,而在每个undo log segment段中进行 undo页 (存储的就是回滚记录)的申请。 在 InnoDB1.1版本之前 (不包括1.1版本),只有一个rollback segment,因此支持同时在线的事务限制为 1024 。虽然对绝大多数的应用来说都已经够用。 从1.1版本开始InnoDB支持最大 128个rollback segment ,故其支持同时在线的事务限制提高到 了 128*1024 。

# 通过如下的SQL语句查询回滚段的大小
mysql> SHOW VARIABLES LIKE 'innodb_rollback_segments';
+--------------------------+-------+ 
| Variable_name            | Value |
+--------------------------+-------+
| innodb_rollback_segments | 128   |
+--------------------------+-------+
1 row in set (0.00 sec)
  • undo页的重用

在MySQL中,undo页的重用是指当事务提交或回滚后,之前使用的undo页可以被重新利用来存储新的事务的undo信息。这个过程称为undo页的重用。

  • 回滚段与事务

1、每个事务只会使用一个回滚段,一个回滚段在同一时刻可能会服务于多个事务。

2、当一个事务开始的时候,会制定一个回滚段,在事务进行的过程中,当数据被修改时,原始的数据会被复制到回滚段。

3、当事务提交时,InnoDB存储引擎会做以下两件事情:

  • 将undo log放入列表中,以供之后的purge(清理)操作

  • 判断undo log所在的页是否可以重用,若可以分配给下个事务使用

  • 回滚段中的数据分类
  1. 活动数据(Active Data):这是当前正在进行的事务所涉及的数据。活动数据包括已被修改但尚未提交的数据,这些数据存储在回滚段中,以便在需要时进行回滚或提交。这些数据是事务的当前状态。

  2. 回滚段头信息(Rollback Segment Header Information):回滚段包含了用于管理回滚数据的头部信息。这些信息包括回滚段的标识符、事务状态、指向回滚段中数据块的指针等。这些头信息使数据库系统能够有效地管理回滚段中的数据。

  3. 已提交数据(Committed Data):这是已成功提交的事务所涉及的数据。已提交数据可以在回滚段中保留一段时间,以满足某些隔离级别的需求。一旦不再需要,已提交数据会被清理,以释放回滚段的空间。

  4. 未提交数据(Uncommitted Data):这是已经被修改但尚未成功提交的数据。未提交数据存储在回滚段中,以便在事务回滚时能够撤销这些更改。这种数据的存在是为了确保事务回滚后数据库状态与事务开始前的状态一致。

回滚段的管理和维护是数据库管理系统内部的复杂任务,以确保事务的隔离性、一致性和可靠性。不同数据库管理系统可能采用不同的机制来管理回滚段,但回滚段的核心目标是支持事务的回滚和隔离级别的实现。

2.4.3 undo log类型

在InnoDB存储引擎中,undo log分为:insert undo log和update undo log

  • insert undo log

Insert undo log(插入撤销日志)是数据库中用于记录插入操作的一种撤销日志。因为insert操作的记录,只对事务本身可见,对其他事务不可见(这是事务的隔离性的要求),因此 undo log可以在事务提交之后删除

  • update undo log

Update undo log(更新撤销日志)是数据库中用于记录更新操作(delete、update)的一种撤销日志。该undo log可能需要提供MVCC机制,因此不能在事务提交是就进行删除。提交是放入undo log链表,等待purge线程进行最后的删除。

2.4.4 undo log的生命周期

事务开始 → 执行修改生成undo log → 事务提交 → undo log支持MVCC(被Read View引用)→ 所有相关事务结束 → purge线程清理过期undo log → 空间标记为空闲可重用

undo log生成过程

简要生成过程

假设有2个数值,分别为A=1和B=2, 然后将A修改为3,B修改为4

1、start transaction ;
2、记录A=1到undo log  ;
3、update A = 3      ;
4、记录B=2到undo log  ;
5、update B = 4 ;
6、commit

异常情况分析:

我们需要先将该数据事务开始之前的状态写入Undo log中。假设更新到一半出错了,我们就可以通过Undo log来回滚到事务开始前。

  • undo log的删除

1、针对于insert undo log 因为insert操作的记录,只对事务本身可见,对其他事务不可见。故该undo log可以在事务提交后直接删除,不需要进行purge操作。

2、针对于update undo log 该undo log可能需要提供MVCC机制,因此不能在事务提交时就进行删除。提交时放入undo log链表,等待purge线程进行最后的删除。

3 MySQL锁

事务的隔离性来实现。

3.1 锁概述

        锁是计算机协调多个进程或者线程并发访问某一个资源的机制。在开发过程中加锁是为了保证数据的一致性。

        在数据库中,除传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源。为保证数据的一致性,需要对并发操作进行控制 ,因此产生了锁 。同时锁机制也为实现MySQL的各个隔离级别提供了保证。 锁冲突 也是影响数据库 并发访问性能 的一个重要因素。

3.2 **并发事务带来的问题

  • 脏读(Dirty read): 一个事务读到了另一个事务还未提交修改的数据
  • 丢失修改(Lost to modify):两个事务修改数据,其中一个数据修改被覆盖了没有显示出来。
  • 不可重复读(Unrepeatableread):指在一个事务内多次读同一数据,另一事务修改此数据,前一事务读取数据前后不一致。称为不可重复读。
  • 幻读(Phantom read):事务1进行读取数据时,事务2增加数据,事务1看不到却能进行对新增数据的修改,仿佛出现幻觉。

3.3 **并发事务的解决方案

MySQL的四种隔离级别如下:

  1.  读未提交(READ UNCOMMITTED):这个隔离级别下,其他事务可以看到本事务没有提交的部分修改。什么问题也解决不了。
  2.  读已提交(READ COMMITTED):其他事务只能读取到本事务已经提交的部分。这个隔离级别有不可重复读的问题,在同一个事务内的两次读取,拿到的结果竟然不一样,因为另外一个事务对数据进行了修改。
  3. REPEATABLE READ(可重复读):可重复读隔离级别解决了上面不可重复读的问题),但是不能完全解决幻读。MySql默认的事务隔离级别就是:REPEATABLE READ
  4. SERIALIZABLE(可串行化):这是最高的隔离级别,可以解决上面提到的所有问题,因为他强制将所有的操作串行执行,这会导致并发性能极速下降,因此也不是很常用。

3.4 并发事务访问情况说明

并发事务访问相同记录的情况大致可以划分为3种:读-读情况、写-写情况、读-写或写-读情况

3.4.1 读-读情况

读-读情况,即并发事务相继读取相同的记录 。读取操作本身不会对记录有任何影响,并不会引起什么问题,所以允许这种情况的发生。

3.4.2 **写-写情况

        写-写 情况,即并发事务相继对相同的记录做出改动。 在这种情况下会发生 脏写(脏写读取、脏写覆盖) 的问题,任何一种隔离级别都不允许这种问题的发生。所以在多个未提交事务相继对一条记录做改动时,需要让它们排队执行 ,这个排队的过程其实是通过来实现的。这个所谓 的锁其实是一个 内存中的结构 ,在事务执行前本来是没有锁的,也就是说一开始是没有锁结构和记录进 行关联的

        当一个事务想对这条记录做改动时,首先会看看内存中有没有与这条记录关联的锁结构 ,当没有的时候 就会在内存中生成一个 锁结构 与之关联。比如,事务 T1 要对这条记录做改动,就需要生成一个锁结构与之关联:

在锁结构中存在很多的信息,为了简化理解,只把两个比较重要的属性拿出来:

1、trx信息:代表这个锁结构是哪一个事务生成的

2、is_waiting: 代表当前事务是否在线等待(true有锁,false无锁)

事务1结束释放锁,改为false,唤醒等待事务。

小结:

1、不加锁 :不需要在内存中生成对应的 锁结构 ,可以直接执行操作。

2、获取锁成功,或者加锁成功 :在内存中生成了对应的 锁结构

3、获取锁失败,或者加锁失败,或在内存中生成了对应的 锁结构 ,不过锁结构的 is_waiting 属性为 true ,也就是事务需要等待,不可以继续执行操作。

3.4.3**读-写情况

读-写 或 写-读 ,即一个事务进行读取操作,另一个进行改动操作。这种情况下可能发生 脏读 、 不可重 复读 、 幻读 的问题。

要想解决这些问题就需要使用到到事务的隔离级别,而事务的隔离性的实现原理有两种:

1、使用MVCC:读操作利用多版本并发控制( MVCC ),写操作进行加锁 。

普通的SELECT语句在READ COMMITTED和REPEATABLE READ隔离级别下会使用到MVCC读取记录。

1、在 READ COMMITTED 隔离级别下,一个事务在执行过程中每次执行SELECT操作时都会生成一 个ReadView,ReadView的存在本身就保证了 事务不可以读取到未提交的事务所做的更改 ,也就是避免了脏读现象;

2、在 REPEATABLE READ 隔离级别下,一个事务在执行过程中只有 第一次执行SELECT操作才会生成一个ReadView,之后的SELECT操作都复用 这个ReadView,这样也就避免了不可重复读 和部分幻读的问题。

2、读、写操作都采用 加锁 的方式。

采用 MVCC 方式的话, 读-写 操作彼此并不冲突, 性能更高 。

3.5 锁的分类

  • 从对数据操作的粒度分 :
  1. 表锁:操作时,会锁定整个表。
  2. 页面锁:操作时,会锁定某一页的数据。
  3. 行锁:操作时,会锁定当前操作行。
  • 从对数据操作的类型分:
  1. 读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响。
  2. 写锁(排它锁):当前操作没有完成之前,它会阻断其他写锁和读锁。
存储引擎表级锁行级锁页面锁
MyISAM支持不支持不支持
InnoDB支持支持不支持
MEMORY支持不支持不支持
BDB支持不支持支持

MySQL这3种锁的特性可大致归纳如下 :

锁类型特点
表级锁偏向MyISAM 存储引擎,开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
行级锁偏向InnoDB 存储引擎,开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
页面锁开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。

3.6 MyISAM表锁

MyISAM 存储引擎只支持表锁,这也是MySQL开始几个版本中唯一支持的锁类型。

3.6.1 加锁特点

MyISAM 在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT 等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此,用户一般不需要直接用 LOCK TABLE 命令给 MyISAM 表显式加锁。

显式加表锁语法:

加读锁 : lock table table_name read;
加写锁 : lock table table_name write;

3.6.2 总结

读锁会阻塞写,但是不会阻塞读。而写锁,则既会阻塞读,又会阻塞写

此外,MyISAM 的读写锁调度是写优先,这也是MyISAM不适合做写为主的表的存储引擎的原因。因为写锁后,其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永远阻塞。

3.7 InnoDB行锁

3.7.1 加锁特点

行锁特点 :开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。

InnoDB 与 MyISAM 的最大不同有两点:一是支持事务;二是 采用了行级锁。

InnoDB 实现了以下两种类型的行锁。

1、共享锁(S):又称为读锁,简称S锁,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。

2、排他锁(X):又称为写锁,简称X锁,排他锁就是不能与其他锁并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。

对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);

对于普通SELECT语句,InnoDB不会加任何锁;

可以通过以下语句显示给记录集加共享锁或排他锁 。

共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE

3.7.3 行锁基本演示

3.7.4 无索引行锁升级为表锁

如果不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,实际效果跟表锁一样。

由于 执行更新时 , name字段本来为varchar类型, 我们是作为数字类型使用,存在类型转换,索引失效,最终行锁变为表锁 ;

3.7.5 间隙锁危害

当我们用范围条件,而不是使用相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据进行加锁; 对于键值在条件范围内但并不存在的记录,叫做 "间隙(GAP)" , InnoDB也会对这个 "间隙" 加锁,这种锁机制就是所谓的 间隙锁(Next-Key锁) 。

3.7.6 InnoDB 行锁争用情况

show status like 'innodb_row_lock%';

Innodb_row_lock_current_waits: 当前正在等待锁定的数量
Innodb_row_lock_time: 从系统启动到现在锁定总时间长度
Innodb_row_lock_time_avg:每次等待所花平均时长
Innodb_row_lock_time_max:从系统启动到现在等待最长的一次所花的时间
Innodb_row_lock_waits: 系统启动后到现在总共等待的次数
​
当等待的次数很高,而且每次等待的时长也不小的时候,我们就需要分析系统中为什么会有如此多的等待,然后根据分析结果着手制定优化计划。

3.7.7 总结

        InnoDB存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面带来了性能损耗可能比表锁会更高一些,但是在整体并发处理能力方面要远远高于MyISAM的表锁的。当系统并发量较高的时候,InnoDB的整体性能和MyISAM相比就会有比较明显的优势。

        InnoDB的行级锁同样也有其脆弱的一面,当我们使用不当的时候,可能会让InnoDB的整体性能表现会更差。

优化建议:

1、尽可能让所有数据检索都能通过索引来完成,避免无索引行锁升级为表锁。

2、合理设计索引,尽量缩小锁的范围

3、尽可能减少索引条件,及索引范围,避免间隙锁

4、尽量控制事务大小,减少锁定资源量和时间长度

5、尽可使用低级别事务隔离(但是需要业务层面满足需求)

4 MySQL中的MVCC

4.1 MVCC概述

除了加锁以外,在数据库领域,还有一种无锁的方案可以来实现并发控制,MVCC。

MVCC (Multiversion Concurrency Control),多版本并发控制。顾名思义,MVCC 是通过数据行的多个版本管理来实现数据库的并发控制

        这项技术使得在InnoDB的事务隔离级别下执行 一致性读 操作有了保 证。换言之,就是为了查询一些正在被另一个事务更新的行,并且可以看到它们被更新之前的值,这样在做查询的时候就不用等待另一个事务释放锁。

读-写并发则可以通过MVCC的机制解决。

4.2 快照读和当前读

        MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突 ,做到即使有读写冲突时,也能做到 不加锁 ,非阻塞并发读 ,而这个读指的就是快照读 , 而非当前读 。当前读实际上是一种加锁的操作,是悲观锁的实现。而MVCC本质是采用乐观锁思想的一种方式。

4.2.1 快照读

        快照读又叫一致性读,读取的是快照数据。不加锁的简单的 SELECT 都属于快照读,即不加锁的非阻塞读,快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。 快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读。

4.2.2 当前读

        当前读读取的是记录的最新版本(最新数据,而不是历史版本的数据),读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。加锁的 SELECT,或者对数据进行增删改都会进行当前读

4.3 隐藏字段以及Undo Log版本链

        对于使用 InnoDB 存储引擎的表来说,它的聚簇索引记录中都包含两个必 要的隐藏列。

  1. trx_id :每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的 事务id 赋值给 trx_id 隐藏列。
  2. roll_pointer :每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到 undo日志 中,然 后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。

insert undo只在事务回滚时起作用,当事务提交后,该类型的undo日志就没用了,它占用的Undo Log Segment也会被系统回收(也就是该undo日志占用的Undo页面链表要么被重用,要么被释放)。

1.假设 id = 100 的事务 A 插入一条行记录(id = 1, username = "Jack", age = 18),那么,这行记录的两个隐藏字段 trx_id = 100 和 roll_pointer指向一个存主键列信息的 undo log,因为在这之前并没有事务操作 id = 1 的这行记录。

2.id = 200 的事务 B 修改了这条行记录,把 age 从 18 修改成了 20,于是,这条行记录的 trx_id就变成了 200,roll_pointer就指向事务 A 生成的 undo log :

3.id = 300 的事务 C 再次修改了这条行记录,把 age 从 20 修改成了 30,如下图:

可以看到,每次修改行记录都会更新 trx_id 和 roll_pointer 这两个隐藏字段,之前的多个数据快照对应的 undo log 会通过 roll_pointer 指针串联起来,从而形成一个版本链。MVCC 这个机制,其实就是靠 update undo log 实现的。

4.4 MVCC之ReadView

4.4.1 ReadView简介

MVCC(Multi-Version Concurrency Control)中的"read view"是一个用于事务的数据快照或版本视图,用于实现不同事务的隔离和并发控制。每个事务都有自己的"read view",它定义了在事务开始时可以看到哪些数据版本。

"read view"的主要目的是为了确保事务在其生命周期内可以读取一致的数据版本,同时不受其他并发事务的影响。以下是关于"read view"的一些关键概念:

  1. 事务开始时的"read view":当事务启动时,它会创建一个"read view",该视图包括数据库中已经提交的数据版本。事务只能看到在其启动时已经提交的数据。

  2. 防止脏读:通过限制事务只能看到已提交的数据版本,"read view"可以防止脏读。这意味着事务无法读取其他事务尚未提交的修改。

  3. 一致性读取:"read view"确保事务在其生命周期内看到一致的数据版本,而不受其他并发事务的修改干扰。这有助于维护事务的隔离性。

  4. 读取多个数据版本:尽管"read view"在事务开始时定义了可以看到的数据版本,但事务仍然可以在其生命周期内多次读取不同的数据版本。每次读取都会使用事务开始时定义的"read view",以确保一致性。。

4.4.2 ReadView工作原理

通过一个例子来理解下 ReaView 机制是如何做到判断当前事务能够看见哪些版本的:

1.假设表中已经被之前的事务 A(id = 100)插入了一条行记录(id = 1, username = "Jack", age = 18)

2.有两个事务 B(id = 200)查询 和 C(id = 300)修改并发执行

这行数据,这两个事务都执行了相应的操作但是还没有进行提交

如果现在事务 B 开启了一个 ReadView,在这个 ReadView 里面:

1、m_ids 就包含了当前的活跃事务的 id,即事务 B 和事务 C 这两个 id,200 和 300

2、min_trx_id就是 200

3、max_trx_id是下一个能够分配的事务的 id,那就是 301

4、creator_trx_id是当前创建 ReadView 事务 B 的 id 200

3.事务 B 进行第一次查询,会把使用行记录的隐藏字段 trx_id 和 ReadView 的 min_trx_id 进行下判断,row.trx_id < ReadView.min_trx_id, 这说明在事务 B 开始之前,修改这行记录的事务 A 已经提交了。row.trx_id < ReadView.min_trx_id

4.事务 C 过来修改这行记录,把 age = 18 改成了 age = 20,所以这行记录的 trx_id就变成了 300,同时 roll_pointer指向了事务 C 修改之前生成的 undo log:

5.事务 B 再次进行查询操作,row.trx_id > ReadView.min_trx_id && row.trx_id < max_trx_id

更新这行记录的事务很有可能也存在于 ReadView 的 m_ids(活跃事务)中。所以事务 B 会去判断下 ReadView 的 m_ids 里面是否存在 trx_id = 300的事务,显然是存在的,这就表示这个 id = 300 的事务是跟自己(事务 B)在同一时间段并发执行的事务,也就说明这行 age = 20 的记录事务 B 是不能查询到的。

这时事务 B 就会顺着这行记录的 roll_pointer 指针往下找,就会找到最近的一条trx_id = 100 的 undo log,而自己的 id 是 200,即说明这个 trx_id = 100 的 undo log 版本必然是在事务 B 开启之前就已经提交的了。所以事务 B 的这次查询操作读到的就是这个版本的数据即 age = 18。

通过上述的例子,我们得出的结论是,通过 undo log 版本链和 ReadView 机制,可以保证一个事务不会读到并发执行的另一个事务的更新

6.事务 C 的修改已经提交了,然后事务 B 更新了这行记录,把 age = 20 改成了 age = 66,

7.事务 B 再来查询这条记录row.trx_id = ReadView.creator_trx_id

8.在事务 B 的执行期间,突然开了一个 id = 500 的事务 D,然后更新了这行记录的 age = 88 并且还提交了,然后事务 B 再去读这行记录

事务 B 再去查询这行记录,就会发现 trx_id = 500大于 ReadView 中的 max_trx_id = 301,这说明事务 B 执行期间,有另外一个事务更新了数据,所以不能查询到另外一个事务的更新。row.trx_id > ReadView.max_trx_id。

通过 undo log 版本链和 ReadView 机制,可以保证一个事务只可以读到该事务自己修改的数据或该事务开始之前的数据

4.4.3 ReadView总结

        在隔离级别为读已提交(Read Committed)时,一个事务中的每一次 SELECT 查询都会重新获取一次 Read View。

注意:此时同样的查询语句都会重新获取一次 Read View,这时如果 Read View 不同,就可能产生不可重复读或者幻读的情况。

        当隔离级别为可重复读的时候,就避免了不可重复读,这是因为一个事务只在第一次 SELECT 的时候会 获取一次 Read View,而后面所有的 SELECT 都会复用这个 Read View,如下表所示:

相关文章:

  • 数据结构 栈与队列 6.18
  • Linux软件管理包-yum和基础开发工具-vim
  • Evertz SVDN 3080ipx-10G Web管理接口任意命令注入及认证绕过漏洞(CVE-2025-4009)
  • 构建低代码平台的技术解析
  • 龙蜥OS搭建Technitium DNS全指南
  • git的使用——初步认识git和基础操作
  • 计算机视觉课程总结
  • python实现将COQE数据转换成字符串的格式
  • ollama在win10中使用
  • 前端面试专栏-主流框架:10. React状态管理方案(Redux、Mobx、Zustand)
  • 错误监控----比如实现sentry一些思路
  • web和uniapp接入腾讯云直播
  • 腾讯云TCCA认证考试报名 - TDSQL数据库交付运维工程师(MySQL版)
  • Matlab学习笔记
  • 解决idea无法正常加载lombok包
  • CTF解题:[NSSCTF 2022 Spring Recruit]弱类型比较绕过
  • TikTok 矩阵如何快速涨粉
  • MySQL存储引擎深度解析:InnoDB、MyISAM、MEMORY 与 ARCHIVE 的全面对比与选型建议
  • YOLOv11改进系列---Conv篇---2024最新深度可分卷积与多尺度卷积结合的模块MSCB助力yolov11有效涨点
  • 微信中 qrcode 生成二维码长按无效果的解决方案
  • 公司网站设计欣赏/安徽seo报价
  • 中国做的最好的网站/seo视频
  • 宿州哪有做网站的/关键词抓取工具都有哪些
  • 网站设计怎么好看/外贸谷歌优化
  • 展示型网站建设方案书/汕头seo网站建设
  • 中山市做网站的公司/互动营销公司