mysql 间隙锁和临键锁原理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mysql 间隙锁和临键锁原理相关的知识,希望对你有一定的参考价值。

参考技术A

以下面的表为例子进行说明

间隙锁的产生来自于 InnboDB 引擎在可重复读的级别基础上执行当前读时出现的幻读问题。下面来分析一下幻读的例子, 假如没有间隙锁的话 ,那么会出现下面的现象:

如上表如示,是基于没有间隙锁的假设,sessionA 事务内执行两次相同的当前读返回的数据不一样,出现幻读的现象。因为(2,2,10)这条记录在原本的数据并不存在,行锁就锁不住,因此诞生间隙锁。

首先看看sessionA的执行计划,发现用到覆盖索引

场景 1:插入到索引 a 时,要插入是索引是(11,5),属于(a=10,id=10)和(a=30,id=30)之间的锁范围,所以阻塞

场景 2、3、4 同理分析得出结论

本例子跟《查询条件走二级索引例子》区别在于 sessionA 是 select * ,因此需要回到主键索引查询所有字段,扫描了主键索引,所以也会在扫描到的索引进行加 next-key lock。该语句回表一次,扫描到是行是 id=10,所以加锁是(0,10],(10,20),因此 sessionA 一共加了锁是索引 a 的(10,30)和主键索引的(0,20)。

MySQL_InnoDB的锁和事务模型

想要实现高吞吐、高可靠的大型数据库应用系统; 想要从其他数据库迁移到MySQL; 想要进行MySQL性能调优; 那么学习和掌握InnoDB的锁和事务模型就非常有用。

文章目录


14.7.1 InnoDB中的锁

本节介绍 InnoDB 中各种类型的锁。

路过的小伙伴, 请帮忙点小星星Star支持: https://github.com/cncounter/translation/

InnoDB的锁和事务模型 - Github版本

共享锁与排他锁

InnoDB实现了标准的行级锁(row-level locking), 包括两种类型: 共享锁(shared lock, 简称S锁)和排他锁(exclusive lock, 简称X锁)。 【排他锁】有时候也被称为【互斥锁】。

  • 共享锁(S): 允许持有锁的事务读取这一行。
  • 排他锁(X): 允许持有锁的事务更新或删除这一行。

如果事务 T1 持有了行 r 的共享锁(S), 则另一个事务 T2 请求对行 r 上锁时, 将按以下方式进行处理:

  • 如果T2请求S锁, 则可以立即获得授予。 结果为: T1T2 都持有行 r 上的 S 锁。
  • 如果T2请求X锁, 则不能立即授予, 需要进入队列排队等待。

如果事务 T1 在行 r 上持有排他锁(X), 则另一个事务 T2r 行的任何上锁请求, 都不能立即授予。 此时, 事务 T2 必须等事务 T1 释放行 r 的锁。

意向锁

InnoDB支持多粒度锁(multiple granularity locking), 允许行锁(row lock)和表锁(table lock)并存。
例如, LOCK TABLES ... WRITE 语句对指定的表设置排他锁(X锁)。
为了使多粒度级别的锁变得切实可行, InnoDB使用了【意向锁】(intention lock), 或者称为【意图锁】。
意向锁是表级锁(table-level lock), 表明当前事务稍后要对表中的行进行哪种类型的锁定(想要上共享锁还是排他锁)。
意向锁分为两种类型:

  • 共享意向锁(IS, intention shared lock): 表明事务打算在表中的某些行上设置共享锁。
  • 排他意向锁(IX, intention exclusive lock): 表明事务打算对表中的某些行设置排他锁。

例如, SELECT ... LOCK IN SHARE MODE 设置的是 IS 锁, 而 SELECT ... FOR UPDATE 设置的是 IX 锁。

意向锁的协议如下:

  • 事务在获取表中某行的共享锁之前, 必须先获取该表的IS锁, 或者限制更强的锁。
  • 事务在获取表中某行的排它锁之前, 必须先获取该表的IX锁。

意向锁与其他锁的兼容性汇总如下:

XIXSIS
X冲突冲突冲突冲突
IX冲突兼容冲突兼容
S冲突冲突兼容兼容
IS冲突兼容兼容兼容

如果请求的锁与现有锁兼容, 则立即授予请求的事务;
但如果与现有的锁冲突, 则不授予该锁。 请求的事务需要等待, 直到有冲突的锁被释放。
如果锁请求与现有锁冲突, 并因为会导致 “死锁” 而无法授予, 则会直接报错。

意向锁除了全表请求之外(例如 LOCK TABLES ... WRITE ), 不阻塞任何其他请求。 意向锁的主要目的是表明某个事务正在锁定表中的行, 或者要锁定表中的行。

SHOW ENGINE INNODB STATUS 语句和 InnoDB monitor 输出的事务信息中, 意向锁类似这样:

TABLE LOCK table `test`.`t` trx id 10080 lock mode IX

记录锁

记录锁(Record Lock), 也是对索引记录(index record)上的锁。 例如, SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE; 语句会阻止其他事务插入/更新/或删除 t.c1 值为 10的行。

记录锁始终锁定索引记录, 即使没有定义索引的表也是如此。 对于没有设置索引的表, InnoDB会自动创建一个隐藏的聚集索引(clustered index, 也称为聚簇索引), 并使用此索引来执行记录锁。 具体的情况请查看Section 14.6.2.1, “Clustered and Secondary Indexes”

SHOW ENGINE INNODB STATUS 语句和 InnoDB monitor 输出的事务信息中, 记录锁类似这样:

RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10078 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 8000000a; asc     ;;
 1: len 6; hex 00000000274f; asc     ''O;;
 2: len 7; hex b60000019d0110; asc        ;;

间隙锁

间隙锁(Gap Lock):

  • 是对索引记录之间的间隙进行锁定,
  • 或者是对第一个索引记录之前的间隙进行锁定,
  • 或者是对最后一个索引记录之后的间隙进行锁定。

例如, SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE; 会阻止其他事务将 15 这个值插入到 t.c1 列中, 无论该列中是否存在这个值, 因为这个范围内所有值之间的间隙都被锁定了。

一个间隙可能跨越单个索引值, 多个索引值, 甚至为空。

间隙锁是性能和并发之间的一种权衡, 只会在某些事务隔离级别中使用。

对于使用唯一索引(unique index)来锁定唯一行的语句, 不需要使用间隙锁。 (如果是多列组成的唯一索引, 并且搜索条件中仅包含部分列, 这时也会产生间隙锁。)
例如, 如果id列具有唯一索引, 则下面的SQL语句只会对 id = 100 的行使用记录锁, 而不关心其他会话是否在前面的间隙中插入新行:

SELECT * FROM child WHERE id = 100;

如果 id 列没有索引, 或者不是唯一索引, 则该语句会把前面的间隙一起锁了。

值得注意的是, 不同的事务可以在同一个间隙上持有冲突锁。例如, 事务A可以在间隙上持有一个共享间隙锁(gap S-lock), 而事务B可以在同一间隙上持有排他间隙锁(gap X-lock)。允许冲突的间隙锁的原因是, 如果从索引中清除一条记录, 则必须将不同事务在该记录上持有的间隙锁进行合并。

InnoDB 中的间隙锁是“纯抑制性的(purely inhibitive)”, 唯一目的是阻止其他事务在间隙中插入。间隙锁可以共存(co-exist)。一个事务持有的间隙锁, 并不会阻止其他事务对相同的间隙进行锁定。 共享间隙锁和排他间隙锁之间也没有区别。彼此不冲突, 作用也一样。

间隙锁可以被显式禁用。 将事务隔离级别设置为 READ COMMITTED, 或者启用了系统变量 innodb_locks_unsafe_for_binlog(已废弃), 则会发生这种情况。 间隙锁在搜索和索引扫描时会被禁用, 只用于外键约束检查和重复键检查。

使用 READ COMMITTED 隔离级别或启用 innodb_locks_unsafe_for_binlog 时还有其他效果。 MySQL在 WHERE 条件计算完成后, 会立即释放不匹配行的记录锁。 对于 UPDATE 语句, InnoDB执行“半一致性读(semi-consistent)”, 将最新的提交版本返回给MySQL, 以便MySQL确定该行是否与 UPDATEWHERE 条件匹配。

临键锁

临键锁(Next-Key Lock), 是索引记录锁加上前面的间隙锁组合而成的。

InnoDB行级锁的执行方式, 是搜索或扫描索引时, 会在遇到的索引记录上设置共享锁或互斥锁。 因此, 行级锁本质上是索引记录锁。 索引记录上的临键锁也会影响该索引记录之前的“间隙”。 即: 临键锁=记录锁+间隙锁。
如果一个会话(session)在索引记录R上持有共享临键锁或排他临键锁, 按照索引的排序方向, 其他会话无法在 R 之前的间隙中插入新的索引记录。

假定一个索引包含 10, 11, 13, 和 20 这4个值; 此时该索引的临键锁可以包括这些区间(interval):

(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)

其中, 小括号表示开区间, 不包含端点; 方括号表示闭区间,包括端点。

最后一组区间, 临键锁的范围是: 大于当前的最大值, 一直到正无穷(positive infinity)。 无穷大并不包含真正的索引记录, 实际上, 这个临键锁仅锁定当前最大索引值之后的间隙。

默认情况下, InnoDB 在 REPEATABLE READ 事务隔离级别下运行。 在这种隔离级别下, InnoDB使用临键锁来进行搜索和索引扫描, 防止幻影行。

SHOW ENGINE INNODB STATUS 语句和 InnoDB monitor 输出的事务信息中, 临键锁类似这样:

RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10080 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 8000000a; asc     ;;
 1: len 6; hex 00000000274f; asc     ''O;;
 2: len 7; hex b60000019d0110; asc        ;;

插入意向锁

插入意向锁(Insert Intention Lock)是在插入新行之前, 由 INSERT 操作设置的一种间隙锁。 这个锁表明插入的意图信号, 执行方式为: 如果多个事务想在同一间隙中插入记录, 只要不在同一个位置, 则不需要阻塞或等待。
例如, 索引中有2个值分别为 47。 假设有两个事务, 分别插入 56时, 在获取要插入行的排他锁之前, 每个事务都使用插入意向锁来锁定47之间的间隙, 但他们不会互相阻塞, 因为具体的行是不冲突的。

下面通过示例演示了一个事务在获得插入记录的排他锁之前, 获取插入意向锁。 这个示例涉及两个客户端: A和B。

A客户端创建一个的表, 包含两条索引记录(90102), 然后开启事务, 将排他锁放置在ID大于100的索引记录上。 排他锁包含一个在记录102之前的间隙锁:

# A客户端
mysql> CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB;
mysql> INSERT INTO child (id) values (90),(102);

mysql> START TRANSACTION;
mysql> SELECT * FROM child WHERE id > 100 FOR UPDATE;
+-----+
| id  |
+-----+
| 102 |
+-----+

B客户端开启事务后, 尝试将记录101插入间隙。 在等待获得排他锁时, 会先获取插入意向锁。

# B客户端
mysql> START TRANSACTION;
mysql> INSERT INTO child (id) VALUES (101);

SHOW ENGINE INNODB STATUS 语句和 InnoDB monitor 输出的事务信息中, 插入意向锁类似这样:

RECORD LOCKS space id 31 page no 3 n bits 72 index `PRIMARY` of table `test`.`child`
trx id 8731 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 80000066; asc    f;;
 1: len 6; hex 000000002215; asc     "" ;;
 2: len 7; hex 9000000172011c; asc     r  ;;...

自增锁

自增锁(AUTO-INC Lock)是一种特殊的表级锁, 具有 AUTO_INCREMENT 列的表, 由需要插入记录的事务获取。 在最简陋的场景下, 如果一个事务正往表中插入值, 那么其他事务必须等待他完成之后才能往该表中插入新值, 以便第一个事务插入的行取得连续的自增主键值。

innodb_autoinc_lock_mode 选项, 用来控制自增锁的算法。 允许我们自己权衡, 是选择可预测的自增序列呢, 还是选择insert操作的高并发性能。

更多信息请参考: Section 14.6.1.6, “AUTO_INCREMENT Handling in InnoDB”

空间索引使用的谓词锁

InnoDB 支持地理空间列的 SPATIAL 索引。 详情请参考 Section 11.4.8, “Optimizing Spatial Analysis”

SPATIAL 索引记录上锁时, 临键锁并不能很好地支持 REPEATABLE READSERIALIZABLE 事务隔离级别。 因为多维数据中没有绝对的排序顺序, 因此无法判定谁是 “下一个” 键值。

为了在事务隔离级别中支持具有 SPATIAL 索引的表, InnoDB使用了谓词锁(Predicate Lock)。
SPATIAL 索引记录包含MBR值(minimum bounding rectangle, 最小边界矩形), 因此 InnoDB 在匹配MBR值的索引记录上设置谓词锁, 来对索引强制执行一致性读。 其他事务不能插入或修改匹配查询条件的行。

14.7.2 InnoDB事务模型

InnoDB的事务模型(transaction model), 目标是将多版本数据库(multi-versioning database)的最佳属性与传统的两阶段锁定(two-phase locking)相结合。
默认情况下, InnoDB使用行级锁, 并以非锁定一致性读(nonlocking consistent read)的方式来执行查询, 类似Oracle数据库。
InnoDB中的锁信息, 以节省空间的方式存储, 因此不需要锁升级(lock escalation)。
支持多个用户锁定InnoDB表中的每一行, 或者任意多行, 都不会让InnoDB的内存耗尽。

14.7.2.1 事务隔离级别

事务隔离(Transaction isolation)是数据库的基础特征。 隔离(Isolation)就是ACID中的I; 隔离级别是一个可配置项, 用于在多个事务进行并发修改和并发查询时, 调节性能、可靠性(reliability)、一致性(consistency)和可重复性(reproducibility)之间的平衡。

InnoDB支持《SQL:1992标准》中定义的四个事务隔离级别:

  • READ UNCOMMITTED(读未提交),
  • READ COMMITTED(读已提交),
  • REPEATABLE READ(可重复读), InnoDB 默认的隔离级别。
  • SERIALIZABLE(串行化)。

用户可以改变当前会话的隔离级别(控制自己会话的可见性), 也可以更改后续所有连接的隔离级别, 使用 SET TRANSACTION 语句即可。
要设置服务器的默认隔离级别, 可在命令行或配置文件中使用 --transaction-isolation 选项。 设置隔离级别的详细信息, 请参考 Section 13.3.6, “SET TRANSACTION Statement”

InnoDB对每个事务隔离级别使用不同的锁策略。

  • 可使用默认的 REPEATABLE READ 级别来实现一致性, 比如 ACID 规范很重要的关键数据处理。
  • 在批处理报表之类的场景下, 可以使用 READ COMMITTED 甚至 READ UNCOMMITTED 来放宽一致性约束, 这时候精确的一致性和可重复的结果, 相对来说不如降低锁的开销重要。
  • SERIALIZABLEREPEATABLE READ 的限制更严格, 主要用于特殊情况, 例如 XA 事务, 或者对并发和死锁问题进行故障诊断和排查等场景。

下面对MySQL的各种事务隔离级别进行详细描述。 我们先介绍最常用的隔离级别, 最后介绍最少使用的隔离级别。

REPEATABLE READ

【可重复读】是InnoDB的默认隔离级别。 可重复读隔离级别, 同一事务中的一致性读, 使用第一次读取时创建的快照。
这意味着, 在同一事务中执行多个普通的 SELECT语句(nonlocking), 则这些 SELECT 语句之间彼此是能保证一致性的。
详情请查看 14.7.2.3 非锁定一致性读

对于UPDATE语句, DELETE语句, 以及锁定读(locking read, 即 SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODE语句), 根据过滤条件是否使用了唯一索引, 还是使用范围条件来确定使用的锁:

  • 对于使用了唯一索引的唯一查询条件, InnoDB只会锁定查找到的索引记录, 而不锁定前面的间隙。

  • 对于其他查询条件, InnoDB 会锁定扫描到的索引范围, 通过间隙锁或临键锁来阻止其他会话在这个范围中插入新值。 关于间隙锁和临键锁的信息, 请参考前面的内容: 14.7.1 InnoDB中的锁

READ COMMITTED

在【读已提交】隔离级别下, 即使在同一事务中, 每次一致性读都会设置和读取自己的新快照。 有关一致性读的信息, 请参考 14.7.2.3 非锁定一致性读

对于锁定读(SELECT ... FOR UPDATE 或者 SELECT ... LOCK IN SHARE MODE), UPDATE 语句和 DELETE 语句, InnoDB这时候仅锁定索引记录, 而不锁定它们之间的间隙, 因此, 其他事务可以在锁定记录旁边插入新记录。 这时候间隙锁仅用于外键约束检查和重复键检查。

由于禁用了间隙锁, 有可能会产生幻读问题(phantom problem), 因为其他会话可能会在间隙中插入新行。 有关幻读的信息, 请参考 14.7.4 幻影行

READ COMMITTED 隔离级别仅支持基于行的bin-log。 如果将 READ COMMITTEDbinlog_format=MIXED 一起使用, 则服务器会自动切换到基于行的bin-log。

使用 READ COMMITTED 还会有其他效果:

对于UPDATEDELETE语句, InnoDB仅持有需要更新或删除行的锁。 MySQL计算完 WHERE 条件后, 会释放不匹配行的记录锁。这大大降低了死锁的可能性, 但还是有可能会发生。

对于 UPDATE 语句, 如果某行已被锁定, 则InnoDB会执行半一致读(“semi-consistent” read), 将最新的提交版本返给MySQL, 让MySQL确定该行是否符合 UPDATEWHERE条件。 如果该行匹配(表示需要更新), 则MySQL再次读取该行, 这一次 InnoDB 要么锁定它, 要么就等待上面的锁先释放。

请看下面的示例, 我们先从这张表开始:

CREATE TABLE t (a INT NOT NULL, b INT) ENGINE = InnoDB;
INSERT INTO t VALUES (1,2),(2,3),(3,2),(4,3),(5,2);
COMMIT;

这种情况下, 因为没有索引, 所以查询和索引扫描时, 会使用隐藏的聚集索引来作为记录锁。

假设某个会话A通过下面的语句执行更新操作:

# 会话A
START TRANSACTION;
UPDATE t SET b = 5 WHERE b = 3;

这时会话A还没有提交事务, 接下来第二个会话B通过下面的语句执行更新操作:

# 会话B
UPDATE t SET b = 4 WHERE b = 2;

InnoDB 执行 UPDATE 时, 会为其读取到的每一行先设置一个排他锁(exclusive lock), 然后再确定是否需要对其进行修改。 如果 InnoDB不需要修改, 则会释放该行的锁。 否则, InnoDB将保留这个行锁直到事务结束。 这会影响事务的处理过程, 如下所示。

假如使用默认的 REPEATABLE READ 隔离级别时, 第一个 UPDATE 会先在其扫描读取到的每一行上设置X锁, 并且不会释放任何一个:

# REPEATABLE READ 隔离级别; 会话A
x-lock(1,2); retain x-lock
x-lock(2,3); update(2,3) to (2,5); retain x-lock
x-lock(3,2); retain x-lock
x-lock(4,3); update(4,3) to (4,5); retain x-lock
x-lock(5,2); retain x-lock

因为第一个 UPDATE 在所有行上都保留了锁, 第二个 UPDATE 尝试获取任何一个锁时都会立即阻塞, 直到第一个UPDATE提交或回滚之后才能继续执行:

# REPEATABLE READ 隔离级别; 会话B
x-lock(1,2); block and wait for first UPDATE to commit or roll back

如果使用 READ COMMITTED 隔离级别, 则第一个 UPDATE 会在扫描读取到的每一行上获取X锁, 然后释放不需要修改行上的X锁:

# READ COMMITTED 隔离级别; 会话A
x-lock(1,2); unlock(1,2)
x-lock(2,3); update(2,3) to (2,5); retain x-lock
x-lock(3,2); unlock(3,2)
x-lock(4,3); update(4,3) to (4,5); retain x-lock
x-lock(5,2); unlock(5,2)

对于第二个UPDATE, InnoDB会执行半一致读(“semi-consistent” read), 将最新的提交版本返给MySQL, 让MySQL确定该行是否符合 UPDATEWHERE条件:

# READ COMMITTED 隔离级别; 会话B
x-lock(1,2); update(1,2) to (1,4); retain x-lock
x-lock(2,3); unlock(2,3)
x-lock(3,2); update(3,2) to (3,4); retain x-lock
x-lock(4,3); unlock(4,3)
x-lock(5,2); update(5,2) to (5,4); retain x-lock

但是, 如果 WHERE 条件中包括了索引列, 并且 InnoDB 使用了这个索引, 则获取和保留记录锁时只考虑索引列。
在下面的示例中, 第一个 UPDATE 在所有 b = 2 的行上获取并保留一个X锁。
第二个 UPDATE 尝试获取同一记录上的X锁时会阻塞, 因为也使用了 b 这列上面定义的索引。

CREATE TABLE t (a INT NOT NULL, b INT, c INT, INDEX (b)) ENGINE = InnoDB;
INSERT INTO t VALUES (1,2,3),(2,2,4);
COMMIT;

# 会话 A
START TRANSACTION;
UPDATE t SET b = 3 WHERE b = 2 AND c = 3;

# 会话 B
UPDATE t SET b = 4 WHERE b = 2 AND c = 4;

使用 READ COMMITTED 隔离级别, 与设置 innodb_locks_unsafe_for_binlog 选项的效果基本一样【该选项已废弃】, 但也有一些不同:

  • innodb_locks_unsafe_for_binlog 是一个全局设置, 会影响所有会话, 而隔离级别既可以对所有会话进行全局设置, 也可以对每个会话单独设置。
  • innodb_locks_unsafe_for_binlog 只能在服务器启动时设置, 而隔离级别可以在启动时设置, 也可以在运行过程中更改。

因此, READ COMMITTEDinnodb_locks_unsafe_for_binlog 更方便, 也更灵活。

READ UNCOMMITTED

【读未提交】隔离级别下, SELECT语句以非锁定的方式执行, 但可能会用到某一行的早期版本。 所以使用此隔离级别时, 不能保证读取的一致性, 这种现象称为脏读(dirty read)。 其他情况下, 此隔离级别类似于 READ COMMITTED

SERIALIZABLE

【串行化】这个隔离级别类似于 REPEATABLE READ, 如果禁用了 autocommit, 则 InnoDB 会隐式地将所有普通的 SELECT 语句转换为 SELECT ... LOCK IN SHARE MODE

如果启用了自动提交(autocommit), 则 SELECT 就单独在一个事务中。 因此被认为是只读的, 如果以一致性非锁定读取方式执行, 不需要阻塞其他事务就可以实现串行化。

如果要强制普通的 SELECT 语句在其他事务修改选定行时进行阻塞等待, 请禁用 autocommit

14.7.2.2 autocommit, 提交以及回滚

在InnoDB中, 所有用户活动都在事务中执行。 如果启用了自动提交模式(autocommit), 则每条SQL语句都会自己形成一个事务。
MySQL中的每个新会话连接, 默认都是自动提交模式, 一个SQL语句如果没有产生错误, 则会在其执行完后自动提交。
如果某条SQL语句返回错误, 则根据具体的错误来决定是提交还是回滚。 详情请参考 Section 14.22.4, “InnoDB Error Handling”

启用了自动提交的会话, 也可以执行多语句事务, 通过显式的 START TRANSACTION 或者 BEGIN 语句开始, 然后以 COMMIT 或者 ROLLBACK 语句结束。 具体情况请参考 Section 13.3.1, “START TRANSACTION, COMMIT, and ROLLBACK Statements”

如果通过 SET autocommit = 0 禁用了自动提交模式, 则该会话会始终有一个打开的事务。 COMMIT 或者 ROLLBACK 语句则会结束当前事务并开启一个新的事务。

禁用了自动提交模式的会话, 在没有明确提交事务的情况下, 如果连接断开或者会话结束, 则MySQL会执行事务回滚。

有些语句会隐式地结束事务, 效果类似于在这种语句之前自动增加了一条 COMMIT 语句。 详细信息请参考Section 13.3.3, “Statements That Cause an Implicit Commit”

COMMIT 表示需要将当前事务所做的更改进行持久化(permanent), 并对其他会话可见。 而 ROLLBACK 语句则取消当前事务中的修改。 COMMITROLLBACK 都会释放所有在当前事务期间设置的 InnoDB 锁。

DML操作分组和事务

MySQL数据库的客户端连接, 默认开启自动提交模式, 每个SQL语句执行完都会自动提交。 用过其他数据库系统的用户, 可能对这种操作模式不太习惯, 因为他们更常用的方式, 是执行一连串的DML语句, 然后再一起提交, 或者一起回滚。

想要使用多语句事务:

  • 可以通过 SET autocommit = 0 语句关闭自动提交模式, 并在适当的时机以 COMMIT 或者 ROLLBACK 结束事务。
  • 处于自动提交状态, 可以通过 START TRANSACTION 开启一个事务, 并以 COMMIT 或者 ROLLBACK 结束。

下面通过示例来演示两个事务。 其中, 第一个事务被提交; 第二个事务被回滚。

# 连接数据库
shell> mysql test

执行的SQL:

-- 建表
mysql> CREATE TABLE customer (a INT, b CHAR (20), INDEX (a));
Query OK, 0 rows affected (0.00 sec)
mysql> -- 在 autocommit 开启的状态下, 启动事务:
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO customer VALUES (10, 'Heikki');
Query OK, 1 row affected (0.00 sec)
mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)
mysql> -- 关闭 autocommit 状态; 启动事务:
mysql> SET autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO customer VALUES (15, 'John');
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO customer VALUES (20, 'Paul');
Query OK, 1 row affected (0.00 sec)
mysql> DELETE FROM customer WHERE b = 'Heikki';
Query OK, 1 row affected (0.00 sec)
mysql> -- 回滚事务:
mysql> ROLLBACK;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM customer;
+------+--------+
| a    | b      |
+------+--------+
|   10 | Heikki |
+------+--------+
1 row in set (0.00 sec)
mysql>
客户端编程语言中的事务

在MySQL客户端API中, 例如 PHP, Perl DBI, JDBC, ODBC, 或者标准C调用接口, 可以将事务控制语句(如COMMIT)当做字符串发送给MySQL服务器, 就像普通的SQL语句(SELECTINSERT)一样。 某些API还单独提供了提交事务和回滚的函数/方法。

14.7.2.3 非锁定一致性读

一致性读(consistent read), 意味着 InnoDB 通过多版本技术, 为一个查询呈现出某个时间点上的数据库快照。 查询能看到这个时间点之前所有已提交事务的更改, 而看不到这个时间点之后新开的事务、或者未提交的事务所做的更改。
例外是查询可以看到同一事务中前面执行的语句所做的更改。 这种例外会引起一些异常: 如果更新了表中的某些行, 则 SELECT 将看到该行被更新之后的最新版本, 但其他的行可能看到的还是旧版本。 如果其他会话也更新了这张表, 则这种异常意味着我们可能会看到某种并不存在的状态。

如果是默认的 REPEATABLE READ 隔离级别, 则同一事务中的所有一致读, 都会读取该事务中第一次读取时所创建的快照。 可以提交当前事务, 并在此之后执行新的查询语句来获取最新的数据快照。

使用 READ COMMITTED 隔离级别时, 事务中的每次一致性读, 都会设置并读取自己的新快照。

READ COMMITTEDREPEATABLE READ 隔离级别下, 一致性读是InnoDB 处理 SELECT 语句的默认模式。
一致性读不会在读取的表上设置任何锁, 所以在读取时, 其他会话可以自由对这些表执行修改。

使用默认的 REPEATABLE READ 隔离级别, 执行普通的 SELECT 一致性读时, InnoDB 会为当前事务指定一个时间点, 根据这个时间点来确定事务中的所有查询可以看到哪些数据。 如果在这个给定的时间点之后,

以上是关于mysql 间隙锁和临键锁原理的主要内容,如果未能解决你的问题,请参考以下文章

详解 MySql InnoDB 中的三种行锁(记录锁间隙锁与临键锁)

JVM集合之类加载子系统

MySQL-加锁规则(间隙锁临键锁行锁表锁)

MySQL-加锁规则(间隙锁临键锁行锁表锁)

详解 MySql InnoDB 中的三种行锁(记录锁间隙锁与临键锁)

锁--04---锁的分类2----行锁----(记录锁间隙锁临键锁)