MySQL:并发

本文将讲解并发事务带来的问题、并发事务的控制方式、事务隔离级别。

并发事务带来的问题

在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对同一数据进行操作)。

并发虽然是必须的,但可能会导致以下的问题:脏读、修改丢失、不可重复读、幻读。

这4个问题是并发操作中最常见的事务一致性问题,直接关系到数据库的完整性和一致性;解决这四个问题可以极大地减少并发环境下的数据一致性问题。

其实还存在其他问题,比如:

  • 事务悬挂(Transaction Serialization Anomaly):也称为“活锁”或“死锁”,当两个或多个事务互相等待对方释放资源而无法继续执行时,就会发生这种情况。
  • 时间戳顺序违背(Timestamp Order Violation):在使用基于时间戳的并发控制方法时,可能出现的时间戳分配不合理导致的事务间逻辑冲突。
  • 读偏斜(Read Skew):这是一种特殊的不可重复读现象,指的是在一个事务中,基于多行的条件判断结果与在同一事务中后续的实际操作之间存在不一致性。
  • 写偏斜(Write Skew):类似读偏斜,但在更新操作上出现,两个事务都基于同一条件更新数据,但各自看到的结果不同,最终可能导致数据不一致。

此外,对于某些特定的应用场景和业务逻辑,可能存在更复杂的数据一致性需求,这时候可能需要结合业务规则和数据库事务特性制定更为细致的并发控制策略。
总的来说,针对“脏读”、“不可重复读”、“幻读”和“更新丢失”的解决方案(比如设置合适的事务隔离级别)是并发控制的基础,但全面解决并发问题还需要综合运用多种并发控制手段和技术。

脏读(Dirty read)

一个事务读取数据并且对数据进行了修改,这个修改对其他事务来说是可见的,即使当前事务没有提交。这时另外一个事务读取了这个还未提交的数据,但第一个事务突然回滚,导致数据并没有被提交到数据库,那第二个事务读取到的就是脏数据,这也就是脏读的由来。

示意图:

说明:

  1. 进程A将属性A设置为10。
  2. 属性A被进程B读取了,进程B获得到的属性A为10。
  3. 进程A发出回滚,属性A变回1。

这导致了进程B获得的A=10是毫无意义的垃圾数据。

丢失修改(Lost to modify)

在一个事务读取一个数据,将其保存为本地变量;另外一个事务也访问了该数据,做了同样的操作。
然后第一个事务在本地修改了这个数据后将其保存;第二个事务也修改了这个数据,做了同样的操作。
这样数据库中数据的值会变成后一个提交的事务的值,前一个事务的修改会无效。

示意图:

说明:

  1. 进程A查询A属性,获得A=1,在本地将A改为2。
  2. 进程B查询A属性,获得A=1,在本地将A改为1。
  3. 进程A更新A属性,A=2。
  4. 进程B更新A属性,A=1.

进程A的事务未发生任何作用,修改被丢失了。

感觉有点像区块链的双重支付问题,但是那个玩意在等待一段时间后就会被发现无法获得货币,因为具体节点的修改操作是可见的;但是在丢失修改中,两个事务无法观测到对方的执行情况。

不可重复读(Unrepeatable read)

指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读

示意图:

说明:

  1. 进程A、进程B都发起事务。
  2. 进程A修改属性A为2。
  3. 进程B读取属性A为2。
  4. 进程A修改属性A为5。
  5. 进程B读取属性A为5。

进程B两次读取属性不一致,一次是2一次是5。

幻读(Phantom read)

幻读可以看作是不可重复读的一种特殊情况,它发生在一个事务读取了几行数据,接着另一个并发事务插入了一些数据时。在随后的查询中,第一个事务就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

单独把区分幻读的原因主要是解决幻读和不可重复读的方案不一样。

示意图:

说明:

  1. 进程A、进程B都发起事务。
  2. 进程A添加属性B=2。
  3. 进程B读取全部属性A=1、B=2。
  4. 进程A添加属性C=3。
  5. 进程B读取全部属性A=1、B=2、C=3。

进程B两次读取到的属性数量不同,一次是2个一次是3个。

事务隔离级别

SQL 标准定义了四个隔离级别:

  • READ-UNCOMMITTED(读取未提交):最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
  • READ-COMMITTED(读取已提交):允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
  • REPEATABLE-READ(可重复读):对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。MySQL InnoDB存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重复读)。
  • SERIALIZABLE(可串行化):最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。

MySQL 的隔离级别基于锁和 MVCC 机制共同实现的。
SERIALIZABLE 隔离级别是通过锁来实现的,READ-COMMITTED 和 REPEATABLE-READ 隔离级别是基于 MVCC 实现的。不过, SERIALIZABLE 之外的其他隔离级别可能也需要用到锁机制,就比如 REPEATABLE-READ 在当前读情况下需要使用加锁读来保证不会出现幻读。

并发事务的控制方式

MySQL中并发事务的控制方式为以下两种:MVCC

我们将先介绍MVCC,然后讲解锁。

多版本并发控制 (Multi-Version Concurrency Control)

MVCC的核心思想:让读操作并不直接读取数据库中的最新数据,而是读取历史版本的数据,这些历史版本是在读操作开始时就已经存在的已提交版本。
MVCC最大的优势:读不加锁,读写不冲突。在读多写少的OLTP应用中,读写不冲突是非常重要的,极大的增加了系统的并发性能。

MVCC 是一种并发控制机制,用于在多个并发事务同时读写数据库时保持数据的一致性和隔离性。它是通过在每个数据行上维护多个版本的数据来实现的。当一个事务要对数据库中的数据进行修改时,MVCC 会为该事务创建一个数据快照,而不是直接修改实际的数据行。

简单讲述一下在MVCC下读、写、事务提交/回滚的思路:

  1. 读(select):当一个事务执行读操作时,它会使用快照读取。快照读取是基于事务开始时数据库中的状态创建的,因此事务不会读取其他事务尚未提交的修改。具体工作情况如下:
    • 对于读取操作,事务会查找符合条件的数据行,并选择合适的数据版本进行读取。
    • 由于事务读取的是旧版的快照数据,因此其他并发事务对数据行的修改不会影响当前事务的读取操作。
  2. 写操作(INSERT、UPDATE、DELETE):当一个事务执行写操作时,它会生成一个新的数据版本,并将修改后的数据写入数据库。具体工作情况如下:
    • 对于写操作,事务会为要修改的数据行创建一个新的版本,并将修改后的数据写入新版本
    • 新版本的数据会带有当前事务的版本号,以便其他事务能够正确读取旧版本的数据,避免发生脏读。
  3. 事务提交和回滚
    • 当一个事务提交时,它所做的修改将成为数据库的最新版本,并且对其他事务可见。
    • 当一个事务回滚时,它所做的修改将被撤销,始终对其他事务不可见。

版本的回收:为了防止数据库中的版本无限增长,MVCC 会定期进行版本的回收。回收机制会删除已经不再需要的旧版本数据,从而释放空间。

一致性非锁定读和锁定读

一致性非锁定读锁定读是MySQL数据库中InnoDB存储引擎在处理事务并发控制时采用的两种不同的读取数据的方式。

一致性非锁定读

对于一致性非锁定读(Consistent Nonlocking Reads)的实现,通常做法是加一个版本号或者时间戳字段,在更新数据的同时版本号 + 1 或者更新时间戳。查询时,将当前可见的版本号与对应记录的版本号进行比对,如果记录的版本小于可见版本,则表示该记录可见。

在 InnoDB 存储引擎中,多版本并发控制就是对非锁定读的实现。如果读取的行正在执行 DELETE 或 UPDATE 操作,这时读取操作不会去等待行上锁的释放。相反地,InnoDB 存储引擎会去读取行的一个快照数据,对于这种读取历史数据的方式,我们叫它快照读(snapshot read)

MVCC的实现依赖于undo log(回滚日志),每当有事务对某一行进行修改时,都会生成一个旧版本,并将其存储在undo日志中。这样,即使该行被其他事务锁定或修改,当前事务仍能读取到之前的版本数据,而无需对这行记录加锁,从而提高并发性能。

Repeatable ReadRead Committed 两个隔离级别下,如果是执行普通的 select 语句(不包括select ... lock in share modeselect ... for update)就会使用一致性非锁定读。并且在 Repeatable Read 下 MVCC 实现了可重复读和防止部分幻读。

在不同的事务隔离级别下,一致性非锁定读的行为会有一定差异,比如在REPEATABLE READ隔离级别下,每次一致性读取都会看到事务开始时的同一视图,而在READ COMMITTED隔离级别下,每次读取可能会看到稍有不同的已提交版本。

锁定读

锁定读是传统的锁定机制,它会在读取数据时对数据行加锁,以确保在读取期间不会有其他事务修改这些数据 ,这种读也被称为当前读(current read)
对于SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODE这样的查询语句,InnoDB会对读取到的数据行加行锁,使得其他事务不能对该行进行更新或删除,直到持有锁的事务结束为止。

相比之下,锁定读虽然提供了更强的事务隔离效果,但可能导致更高的并发环境下出现锁等待和死锁等问题。

MVCC实现

MVCC 的实现依赖于:隐藏字段Read Viewundo log
在内部实现中,InnoDB 通过数据行的 DB_TRX_ID 和 Read View 来判断数据的可见性,如不可见,则通过数据行的 DB_ROLL_PTR 找到 undo log 中的历史版本。

undo-log

当读取记录时,若该记录被其他事务占用或当前版本对该事务不可见,则可以通过 undo log 读取之前的版本数据,以此实现非锁定读。

undo log 分为两种:

  • insert undo log:指在 insert 操作中产生的 undo log。因为 insert 操作的记录只对事务本身可见,对其他事务不可见,故该 undo log 可以在事务提交后直接删除。
  • update undo log:update 或 delete 操作中产生的undo log。该undo log可能需要提供 MVCC 机制,因此不能在事务提交时就进行删除。提交时放入undo log链表,等待purge线程 进行最后的删除。

隐藏字段

在内部,InnoDB 存储引擎为每行数据添加了三个隐藏字段:

  • DB_TRX_ID:用于记录最后一次修改该行记录的事务ID。注意,delete操作在内部被视为更新,只不过会在记录头Record header中的 deleted_flag字段将其标记为已删除。
  • DB_ROLL_PTR:回滚指针,指向该行的undo log。如果该行未被更新,则为空。
  • DB_ROW_ID:如果没有设置主键且该表没有唯一非空索引时,InnoDB 会使用该id来生成聚簇索引。

当我们更新一条数据,InnoDB会更新行记录的 DB_TRX_ID 属性为当前的事务Id。

ReadView

Read View主要是用来做可见性判断,里面保存了 “当前对本事务不可见的其他活跃事务”

在数据库系统中,活跃事务是指已经执行了一部分操作(如读取或写入数据),但尚未最终确认或撤销其更改的事务。

主要有以下字段:

  • m_low_limit_id:“高水位线”,生成ReadView时系统将要分配给下一个事务的Id值。
  • m_up_limit_id:“低水位线”,低水位,取m_ids列表的第一个节点,因为m_ids是升序列表,因此也就是m_ids中事务Id最小的那个。
  • m_ids:一个自定义类型的容器,存储了一系列事务ID,表示在Read View创建时活跃的未提交事务ID集合,Read View不会看到这些事务所做的修改。(由于事务Id大的也可能提交比较早,所以不能仅仅依靠m_up_limit_id,需要做进一步判断,通过m_id来判断该事务是否提交)
  • m_creator_trx_id:创建当前Read View的事务ID。
  • m_low_limit_no:Read View不需要查看事务号严格小于此值的事务的undo日志,这些事务的undo日志在purge过程中可以被安全移除,如果不被其他Read View需要的话。
  • m_closed:标志Read View是否已经被关闭,如果关闭,则表示这个Read View不再有效。

高低水位线说明:

数据可见性算法

读已提交级别下,在 InnoDB 存储引擎中,创建一个新事务后,执行每个 select 语句前,都会创建一个Read View。
可重复读级别下,只在事务开始后的第一次select 数据前生成一个Read View。

当用户在这个事务中要读取某个记录行的时候,InnoDB 会将该记录行的DB_TRX_ID与Read View中的一些变量及当前事务ID进行比较,判断是否满足可见性条件。

具体判断过程

  1. 判断DB_TRX_ID==m_creator_trx_id:即记录版本上一次修改为当前事务自己的修改,那么无论何种情况,事务总是能看到自己做的修改。
  2. 判断DB_TRX_ID < m_up_limit_id:表明被访问版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
  3. 判断DB_TRX_ID >= m_low_limit_id:表明被访问版本的事务在当前事务生成 ReadView 后才开启,所以该版本不可以被当前事务访问。需通过记录行的DB_ROLL_PTR指向的undo log取出快照记录,用快照记录的DB_TRX_ID重新进入判断。
  4. 如果被访问版本的 trx_id 属性值在 ReadView 的 m_up_limit_id 和 m_low_limit_id 之间,那就需要判断 trx_id 属性值是不是在 m_ids 列表中,这边会通过二分法查找。如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。

由于可重复读级别下,Read View只有一个,因此多次select间隔期间,即使有旧事务进行修改并提交,当前读事务也会由于该提交事务的ID在m_ids中导致葫芦该提交结果,进行访问undo log;而在读已提交的情况下,多次select间隔期间,如果有新事务进行修改并提交,由于新事务已被提交,不会存在有新构建的快照中的m_ids中,所以可读到新事务提交的结果。

MVCC对幻读的解决程度

MVCC 解决了部分幻读,但并没有完全解决幻读。
对于快照读,MVCC 因为因为从 ReadView 读取,所以必然不会看到新插入的行,所以天然就解决了幻读的问题。而对于当前读的幻读,MVCC 是无法解决的。

参考文章

锁类型介绍

InnoDB中有7种锁:

共享锁/排他锁

  • 共享锁(S锁):在事务要读一条记录时,需要先获取该记录的S锁。
  • 排他锁(X锁):在事务需要改动一条记录时,需要先获取该记录的X锁。

如果一行已经一个事务被上了S锁,当另一个事务试图再次上锁时:

  • 试图上S锁:通过,该记录上存在了由两个不同事务持有的S锁。
  • 试图上X锁:拒绝,上锁操作被堵塞。

如果一行已经一个事务被上了X锁,当另一个事务试图再次上锁时:

  • 试图上S锁:拒绝,上锁操作被堵塞。
  • 试图上X锁:拒绝,上锁操作被堵塞。

S锁和X锁的兼容关系示意表:

兼容性 S锁 X锁
S锁 兼容 不兼容
X锁 不兼容 不兼容

共享锁和排他锁一样可以存在于表上。

意向锁

意向锁是一种不与行级锁冲突的表级锁
在未来的某个时刻,事务可能要加S锁或者X锁时,因此先提前声明一个意向。

由于InnoDB同时存在行级锁和表级锁,如果一个表的某些记录被添加了X锁或S锁,当另一个事务试图获得表级S锁或表级X锁时,需要遍历每一行来保证没有冲突的锁,这是一个非常耗时的操作,因此InnoDB提出了意向锁,用来代表一个表是否已经被上了行级的X锁或S锁。

  • 意向共享锁(IS锁)Intention Shared Lock,当事务准备在某些记录上加S锁时,需要现在表级别加一个IS锁。
  • 意向排他锁(IX锁)Intention Exclusive Lock,当事务准备在某条记录上加上X锁时,需要现在表级别加一个IX锁。

当一个事务需要给一个表的某几行添加S锁时,其需要同时给表添加一个IS锁;如果一个表上有IS锁,就说明该表有几行正在被读,因此不能添加表级的X锁。
当一个事务需要给一个表的某几行添加X锁时,其需要同时给表添加一个IX锁;如果一个表上有IX锁,就说明该表有几行正在被写,因此不能添加表级的S锁和X锁。

注意:由于IX和IS锁仅仅是意向,并不会真正锁住对应的表,因此IX和IS是相互兼容的。

兼容性示意图:

兼容性 IS锁 IX锁 表级S锁 表级X锁
IS锁 兼容 兼容 兼容 不兼容
IX锁 兼容 兼容 不兼容 不兼容
表级S锁 兼容 不兼容 兼容 不兼容
表级X锁 不兼容 不兼容 不兼容 不兼容

注意:意向锁的存在是为了让数据库系统高效地管理行级锁,但是其不会影响行级S锁或X锁的添加。

记录锁(Record Lock)

记录锁(Record Lock)是数据库管理系统中一种针对单行记录的锁定方式,是一种X锁。
它会在对应的记录上加上记录锁,以阻止其他事务插入,更新,删除

记录锁是加在索引上的,即使一个表没有索引,InnoDB也会隐式的创建一个索引,并使用这个索引实施记录锁。它会阻塞其他事务对这行记录的插入、更新、删除。

间隙锁(Gap Lock)

间隙锁(Gap Lock)是InnoDB用来解决幻读问题的一种锁。
间隙锁是一种加在两个索引之间的锁,或者加在第一个索引之前,或最后一个索引之后的间隙。它锁住的是一个区间,而不仅仅是这个区间中的每一条数据。

当一个事务对某个索引区间使用范围查询并申请了间隙锁时,其他事务无法在这个区间内插入新的记录。这样可以防止事务在第一次读取范围后,由于新插入的数据满足原始查询条件而出现不一致的查询结果。

临键锁(Next-Key Lock)

Next-key锁记录锁和间隙锁的组合,它指的是加在某条记录以及这条记录前面间隙上的锁。说得更具体一点就是:临键锁会封锁索引记录本身,以及索引记录之前的区间,即它的锁区间是前开后闭,比如锁住了id=1,会锁住(x,1]。

插入意向锁

插入意向锁,是插入一行记录操作之前需要获得的一种间隙锁,这个锁释放了一种插入方式的信号。 其是行级别的锁的一种特殊形式,但它并不是直接锁定某个具体的行记录,而是锁定索引记录之间的间隙,表示事务打算在该间隙内插入新行。
作用:

  • 并发插入协调(提升效率):当多个事务同时尝试在同一个索引间隙中插入数据时,每个事务首先都会获取一个插入意向锁,而不是立即(使用写锁)阻塞彼此。这样可以允许多个事务在不同的间隙位置并发插入数据,因为插入意向锁之间是兼容的。

锁模式兼容矩阵

兼容矩阵:(一个事务对一条记录持有锁,另一个事务申请)

兼容性 申请间隙锁 申请插入意向锁锁 申请记录锁 申请临键锁
已持有间隙锁 兼容 冲突 兼容 兼容
已持有插入意向锁 兼容 兼容 兼容 兼容
已持有记录锁 兼容 兼容 冲突 冲突
已持有临键锁 兼容 冲突 冲突 冲突

说明:

  • 持有了间隙锁/临键锁后,不可插入,自然不支持获得插入意向锁。
  • 记录锁和临键锁不可共享,自然是冲突的。
  • 一个事务持有了插入意向锁,这意味着它计划在某个间隙内插入数据,但还没有实际执行插入操作。此时,其他事务仍有可能获取到该相同间隙的间隙锁。这是因为插入意向锁与间隙锁服务的目标不同,插入意向锁并不阻止其他事务锁定间隙,它仅仅表明了事务有在该间隙内插入数据的意向,而间隙锁则负责阻止其他事务在该范围内插入数据。

自增锁

自增锁是一种特殊的表级别锁。它是专门针对AUTO_INCREMENT类型的列,对于这种列,如果表中新增数据时就会去持有自增锁。简言之,如果一个事务正在往表中插入记录,所有其他事务的插入必须等待,以便第一个事务插入的行,是连续的主键值。

参数innodb_autoinc_lock_mode:

  • 0:传统锁模式,使用表级AUTO_INC锁。一个事务的INSERT-LIKE语句在语句执行结束后释放AUTO_INC表级锁,而不是在事务结束后释放。
  • 1:连续锁模式,连续锁模式对于Simple inserts不会使用表级锁,而是使用一个轻量级锁来生成自增值,因为InnoDB可以提前直到插入多少行数据。自增值生成阶段使用轻量级互斥锁来生成所有的值,而不是一直加锁直到插入完成。对于bulk inserts类语句使用AUTO_INC表级锁直到语句完成。
  • 2:交错锁模式,所有的INSERT-LIKE语句都不使用表级锁,而是使用轻量级互斥锁。

加锁

介绍完InnoDB的七种锁后,将讲解如何给一条SQL加锁。
主要分为9种情况:

组合一:查询条件是主键,RC隔离级别
组合二:查询条件是唯一索引,RC隔离级别
组合三:查询条件是普通索引,RC隔离级别
组合四:查询条件上没有索引,RC隔离级别
组合五:查询条件是主键,RR隔离级别
组合六:查询条件是唯一索引,RR隔离级别
组合七:查询条件是普通索引,RR隔离级别
组合八:查询条件上没有索引,RR隔离级别
组合九:Serializable隔离级别

RC隔离级别:主键为查询条件

如:在一个事务中执行删除操作:

1
2
3
create table t_A (id int,name varchar(16),primary key ( id));
insert into t_A values(1,'a');
delete from t_A where id = 1;

该记录(聚簇索引)会被添加X记录锁

RC隔离级别:唯一索引为查询条件

如:在一个事务中执行删除操作:

1
2
3
create table t_A (name varchar(16),id int,primary key (name),unique key(id));
insert into t_A values(1,'a');
delete from t_A where id = 1;

会加X记录锁,一把锁对应于id唯一索引上的id=1的记录,另一把锁对应于聚簇索引上的[name='a',id=1]的记录。

RC隔离级别:普通索引为查询条件

如:在一个事务中执行删除操作:

1
2
3
create table t_A (name varchar(16),id int,primary key (name),key(id));
insert into t_A values(1,'a'),(1,'b');
delete from t_A where id = 1;

会加X记录锁,几把锁对应于id普通索引上的id=1的记录,另外几把锁对应于聚簇索引上的[name='a',id=1][name='b',id=1]的记录。

RC隔离级别:无索引为查询条件

如:在一个事务中执行删除操作:

1
2
3
create table t_A (name varchar(16),id int,primary key (name));
insert into t_A values(1,'a'),(1,'b');
delete from t_A where id = 1;

会加X记录锁,所有锁对应于聚簇索引上的[name='a',id=1][name='b',id=1]的记录。

RR隔离级别:主键为查询条件

RC隔离级别:主键为查询条件一致。

RR隔离级别:唯一索引为查询条件

RC隔离级别:唯一索引为查询条件一致。

RR隔离级别:普通索引为查询条件

会添加X锁间隙锁Gap Lock

如:在一个事务中执行更新操作:

1
2
3
CREATE TABLE t5 ( id int(11) NOT NULL, c int(11) DEFAULT NULL, d int(11) DEFAULT NULL, PRIMARY KEY (id), KEY c (c)) ENGINE=InnoDB;
insert into t5 values(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25);
update t5 set d=d+1 where c = 10;

示意图:

c=10这个记录更新时,不仅会有两把X锁,还会把区间(10,15)加间隙锁,因此要插入(12,12,12)记录时,会阻塞。

RR隔离级别:无索引为查询条件

会添加X锁间隙锁Gap Lock

如:在一个事务中执行更新操作:

1
2
3
CREATE TABLE t5 ( id int(11) NOT NULL, c int(11) DEFAULT NULL, d int(11) DEFAULT NULL, PRIMARY KEY (id)) ENGINE=InnoDB;
insert into t5 values(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25);
update t5 set d=d+1 where c = 10;

示意图:

如果查询条件列没有索引,主键索引的所有记录,都将加上X锁,每条记录间也都加上间隙Gap锁。大家可以想象一下,任何加锁并发的SQL,都是不能执行的,全表都是锁死的状态。如果表的数据量大,那效率就更低。

Serializable串行化

在Serializable串行化的隔离级别下,对于写的语句,比如update account set balance= balance-10 where name=‘Jay’;,跟RC和RR隔离级别是一样的。不一样的地方是,在查询语句,如select balance from account where name = ‘Jay’;,在RC和RR是不会加锁的(仅MVCC),但是在Serializable串行化的隔离级别,即会加锁。


MySQL:并发
http://shoumingchilun.github.io/2024/03/22/backend/database/mysql/mysql_concurrent/
作者
寿命齿轮
发布于
2024年3月22日
许可协议