作者:倪煜
InnoDB锁机制常常困扰大家,不同的条件下往往表现出不同的锁竞争,在实际工作中经常要分析各种锁超时、死锁的问题。本文通过不同条件下的实验,利用InnoDB系统给出的各种信息,分析了锁的工作机制。通过本文可以帮助大家了解InnoDB锁的基本原理,常见的冲突、死锁,以及对InnoDB事务日志信息的解读。
1. 索引基本原理
InnoDB主要使用行级锁(row lock),其行锁是通过在索引项上加锁而实现的,如果MySQL的执行计划没有用到索引,那么行锁也就无意义了,所以了解锁之前需要了解一点索引原理。InnoDB索引是由改进的B+树实现,基本特点就是一颗快速查找树同时叶子节点由双向链表连接,索引项都在叶子节点上,而且分为两种类型:
- 聚簇索引(clustered index )(或主键索引)
- 辅助索引(secondary index)(或二级索引、非聚簇索引)
聚簇索引的特点是叶子节点中除了存储索引key值,还存储了真实的记录内容,同时还会存储事务ID和回滚指针。因此聚簇索引就等于表的真实内容,所以每张表都会有一个聚簇索引。通常聚簇索引就是主键索引,如果建表时没有显示的定义主键,则会首先选择“非空的唯一索引(unique not null)”作为聚簇索引。如果没有的话则会自动创建一个6字节大小的隐藏主键作为主键索引值,随着记录增加而单调递增。一张表只有一个聚簇索引,否则该多浪费。其他的索引都属于辅助索引,辅助索引只存储辅助键和主键,查询时要再通过主键索引二次查找定位记录。由于辅助索引的键值是可以重复的,所以为了唯一标识B+树键值,需要重复存储主键值。
InnoDB按聚簇索引的形式存储数据,大致如下:(《高性能MySQL》配图)
聚簇索引中的每个叶子节点包含primary key的值、事务ID、回滚指针(rollback pointer)和余下的列。下文分析中会看到这几个字段的具体展现。
辅助索引类似如下:
记录的是辅助key值和主key值。
上面的示意图相当于索引的逻辑结构,在实际中B+树中所有叶子节点和非叶子节点都是通过page结构管理,一个page单元通常含有多个节点数据。每个page中有一个Infimum表示最小,Supremum表示最大。通常的一个主键索引详细结构类似于Jeremy Cole的博客中提供InnoDB结构图:
(关于InnoDB索引的实现细节请参考另一篇wiki《InnoDB索引实现机制》)
2. InnoDB锁的模式和类型
InnoDB锁有两个纬度,一个是锁模式,一个是锁类型。
2.1 锁模式:
S共享锁:读锁(shared lock permits the transaction that holds the lock to read a row.)
(select …where … lock in share mode)显示的加S锁。允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
- X排它锁:写锁(exclusive lock permits the transaction that holds the lock to update or delete a row)
(select … where … for update)显示的加X锁。允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。
为了允许行锁和表锁共存,实现多粒度锁机制,同时还有两种内部使用的表级意向锁(都是表锁),所谓意向就是想做但没真做。
-
IS锁:事务对记录加S锁之前必须先获取表的IS锁(Intention shared (
IS
): TransactionT
intends to setS
locks on individual rows in tablet
.)
(Before a transaction can acquire an S lock on a row in table t, it must first acquire an IS or stronger lock on t. )
-
IX锁:事务对记录加X锁之前必须先获取表的IX锁(Intention exclusive (
IX
): TransactionT
intends to setX
locks on those rows.)
(Before a transaction can acquire an X lock on a row, it must first acquire an IX lock on t. )
关于意向锁的官方补充解释:Thus, intention locks do not block anything except full table requests (for example, LOCK TABLES ... WRITE
). The main purpose of IX
and IS
locks is to show that someone is locking a row, or going to lock a row in the table.
意向锁是InnoDB自动加的,不需用户干预,对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及到的数据集加排他锁X,对于普通SELECT语句,InnoDB不会加任何锁。
SELECT ... LOCK IN SHARE MODE 会先设置一个IS锁
SELECT ... FOR UPDATE 会先设置一个IX锁
不同的锁有不同的兼容性。
四种锁的兼容矩阵如下:
请求模式 当前模式 |
X |
IX |
S |
IS |
X |
冲突 |
冲突 |
冲突 |
冲突 |
IX |
冲突 |
兼容 |
冲突 |
兼容 |
S |
冲突 |
冲突 |
兼容 |
兼容 |
IS |
冲突 |
兼容 |
兼容 |
兼容 |
意向锁之间没有任何冲突,S和X锁之间的关系显而易见。只有X、S锁和意向锁之间的关系比较特殊,后文详细举例分析。
2.2 锁类型
除了有锁模式概念,还有锁的类型,总体分为表级锁和行级锁。
2.2.1 表锁
lock table XXX read;对表XXX加S读锁。
lock table XXX write;对表XXX加X写锁。
意向锁就是表级锁,会跟表锁之间有冲突。
2.2.2 行锁
- 间隙锁(Gap Lock),只锁间隙。表现为锁住一个区间(注意这里的区间都是开区间,也就是不包括边界值)。
- 记录锁(Record Lock),只锁记录。表现为仅仅锁着单独的一行记录。
- Next-Key锁(源码中称为Ordinary Lock),同时锁住记录和间隙。从实现的角度为record lock+gap lock,而且两种锁有可能只成功一个,所以next-key是半开半闭区间,且是下界开,上界闭。一张表中的next-key锁包括:(负无穷大,最小的第一条记录],(记录之间],(最大的一条记录,正无穷大)。
- 插入意图锁(Insert Intention Lock),插入操作时使用的锁。在代码中,插入意图锁实际上是Gap锁上加了一个LOCK_INSERT_INTENTION的标记。也就是说insert语句会对插入的行加一个X记录锁,但是在插入这个行的过程之前,会设置一个Insert intention的Gap锁,叫做Insert intention锁。
看一下官方定义:
|
---|
行锁在X锁上做了一些精确的细分,在代码中称作Precise Mode。这些精确的模式使的锁的粒度更细小,可以减少冲突。而且在事务级别RC或者innodb_locks_unsafe_for_binlog打开的情况下GAP锁会失效。这个很重要,后面会说到。
3. 主键索引锁分析
分析锁之前一定要确认前提条件。
锁分析前提条件: 隔离级别为RR: tx_isolation = REPEATABLE-READ 关闭binlog不安全写: innodb_locks_unsafe_for_binlog = OFF |
同时打开InnoDB监控:create table innodb_lock_monitor(x int) engine=InnoDB;
(关于监控的官方文档:http://dev.MySQL.com/doc/refman/5.1/en/innodb-standard-monitor.html)
3.1 主键索引锁测试
下面的实验建立在一张简单的表A上,我们通过实例观察InnoDB锁机制。假设我们有这样一张表A:
建表语句和数据集如下:
| A | CREATE TABLE `A` ( `id` int(11) NOT NULL, `name` varchar(1024) DEFAULT NULL, `t` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `i_name` (`name`(255)) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 | |
MySQL> select * from A; +----+------+ | id | name | +----+------+ | 2 | aa | | 6 | eee | | 7 | aa | | 8 | adf | | 9 | aa | | 11 | a | | 12 | bbb | +------+------+ 7 rows in set (0.00 sec) |
下面通过针对不同的where条件,观察主键索引加锁情况,注意彩色的内容,先把要测试的条件列出来:
where条件
|
---|
=1 |
<2 |
=2 |
<=2 |
>2 and <6 |
>=2 and <6 |
>=2 and <=6 |
=4 |
=6 |
>12 |
>=12 |
=12 |
<=12 and >11 |
<12 and >11 |
下面逐条分析:
case1:=1 |
MySQL> select * from A where id=1 for update; Empty set (0.00 sec) ------------ TRANSACTIONS ------------ Trx id counter 721 Purge done for trx's n:o < 703 undo n:o < 0 History list length 43 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 71A, not started MySQL thread id 2, OS thread handle 0x41743960, query id 163 localhost root ---TRANSACTION 720, ACTIVE 3 sec 2 lock struct(s), heap size 376, 1 row lock(s) MySQL thread id 1, OS thread handle 0x415b9960, query id 178 localhost root show engine InnoDB status TABLE LOCK table `test`.`A` trx id 720 lock mode IX RECORD LOCKS space id 0 page no 306 n bits 88 index `PRIMARY` of table `test`.`A` trx id 720lock_mode X locks gap before rec //行锁模式与类型 Record lock, heap no 19 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 //上锁的记录 0: len 4; hex 80000002; asc ;; 1: len 6; hex 000000000714; asc ;; 2: len 7; hex 94000001960110; asc ;; 3: len 2; hex 6161; asc aa;; |
解析:
事务720对表A加IX锁,如前面所述,对行加X锁之前先对表加IX
对索引项加X锁,类型为gap锁
表示gap锁加在哪个索引项上,19可以先理解为索引项的物理地址,InnoDB使用Page no. +Heap no.来做行的唯一识别。我们可以将Heap no.理解为页面上的一个自增数值。每条物理记录在被创建时,都会分配一个唯一的heap no。 键值可以理解为一个逻辑值,page no. + heap no. 可以理解为物理地址。 从这里也可以看出gap锁实际是加在索引项上的,不同的索引项之间并没有其他数据结构管理gap锁。 |
一条行记录可由(space_id, page_no, heap_no)唯一标识,记录项字段包含四个部分:
0: len 4; hex 80000002; asc 聚簇值字段
1: len 6; hex 000000000714; asc 事务ID:48位整型的ID值,由最近一次修改该字段的事务决定。
2: len 7; hex 94000001960110; asc 回滚指针:包含最近一次修改该字段的undo记录,长度为7字节(1-bit“is insert”标记;7-bit回滚段ID;4字节页号;2字节undo log的页偏移)
3: len 2; hex 6161; asc aa;; 非主键字段:
case2:<2 |
select * from A where id<2 for update; |
---TRANSACTION 718, ACTIVE 36 sec 2 lock struct(s), heap size 376, 1 row lock(s) MySQL thread id 1, OS thread handle 0x415b9960, query id 134 localhost root show engine InnoDB status TABLE LOCK table `test`.`A` trx id 718 lock mode IX RECORD LOCKS space id 0 page no 306 n bits 88 index `PRIMARY` of table `test`.`A` trx id 718lock_mode X //后面未标明锁类型的是默认类型,在源码中是LOCK_ORDINARY Record lock, heap no 19 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000002; asc ;; 1: len 6; hex 000000000714; asc ;; 2: len 7; hex 94000001960110; asc ;; 3: len 2; hex 6161; asc aa;; |
解析:
在索引项2上加next-key锁,其他两处解释同上 |
case3:=2 |
MySQL> select * from A where id=2 for update; +----+------+ | id | name | +----+------+ | 2 | aa | +----+------+ 1 row in set (0.00 sec) |
------------ TRANSACTIONS ------------ Trx id counter 721 Purge done for trx's n:o < 703 undo n:o < 0 History list length 43 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 71A, not started MySQL thread id 2, OS thread handle 0x41743960, query id 163 localhost root ---TRANSACTION 720, ACTIVE 3 sec 2 lock struct(s), heap size 376, 1 row lock(s) MySQL thread id 1, OS thread handle 0x415b9960, query id 178 localhost root show engine InnoDB status TABLE LOCK table `test`.`A` trx id 720 lock mode IX RECORD LOCKS space id 0 page no 306 n bits 88 index `PRIMARY` of table `test`.`A` trx id 720lock_mode X locks rec but not gap //X模式的记录锁 Record lock, heap no 19 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000002; asc ;; 1: len 6; hex 000000000714; asc ;; 2: len 7; hex 94000001960110; asc ;; 3: len 2; hex 6161; asc aa;; |
分析:
只有一个对2这条记录(heap no 19)的记录锁,符合常识。 |
case4:<=2 |
MySQL> select * from A where id<=2 for update; +----+------+ | id | name | +----+------+ | 2 | aa | +----+------+ 1 row in set (0.00 sec) |
------------ TRANSACTIONS ------------ Trx id counter 71F Purge done for trx's n:o < 703 undo n:o < 0 History list length 43 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 71A, not started MySQL thread id 2, OS thread handle 0x41743960, query id 163 localhost root ---TRANSACTION 71E, ACTIVE 2 sec 2 lock struct(s), heap size 376, 2 row lock(s) MySQL thread id 1, OS thread handle 0x415b9960, query id 168 localhost root show engine InnoDB status TABLE LOCK table `test`.`A` trx id 71E lock mode IX RECORD LOCKS space id 0 page no 306 n bits 88 index `PRIMARY` of table `test`.`A` trx id 71Elock_mode X Record lock, heap no 14 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000006; asc ;; //主key值6 1: len 6; hex 000000000536; asc 6;; 2: len 7; hex ae0000014f0110; asc O ;; 3: len 3; hex 656565; asc eee;; //记录中非主建字段 Record lock, heap no 19 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000002; asc ;; 1: len 6; hex 000000000714; asc ;; 2: len 7; hex 94000001960110; asc ;; 3: len 2; hex 6161; asc aa;; |
解析: 表示给heap no 14和heap no 19分别加一个X模式的next-key锁。这里heap no 19是指key为2的记录(看前面的hex值),heap no 14是指key为6的记录,也就是说<=2时不仅会加锁2,还会加锁2后面的一条记录6. 这个条件会导致后面的很多特殊的锁冲突。原因暂时没想到,应该跟索引扫描有关。 |
InnoDB锁系统有1个全局对象lock_sys(type lock_sys_t),而行锁的hash table就存储在其中 struct lock_sys_t { ib_mutex_t mutex; hash_table_t* rec_hash; --行锁hash表,以(space_id, page_no)为hash key,即同一页的所有锁均在一个hash bucket上, ulint n_lock_max_wait_time; // more ... }; |
case5:>2 and <6 |
MySQL> select * from A where id>2 and id<6 for update; Empty set (0.00 sec) |
------------ TRANSACTIONS ------------ Trx id counter 723 Purge done for trx's n:o < 703 undo n:o < 0 History list length 43 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 71A, not started MySQL thread id 2, OS thread handle 0x41743960, query id 163 localhost root ---TRANSACTION 722, ACTIVE 3 sec 2 lock struct(s), heap size 376, 1 row lock(s) //2个锁结构,1个行锁 MySQL thread id 1, OS thread handle 0x415b9960, query id 188 localhost root show engine InnoDB status TABLE LOCK table `test`.`A` trx id 722 lock mode IX RECORD LOCKS space id 0 page no 306 n bits 88 index `PRIMARY` of table `test`.`A` trx id 722 lock_mode X Record lock, heap no 14 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000006; asc ;; 1: len 6; hex 000000000536; asc 6;; 2: len 7; hex ae0000014f0110; asc O ;; 3: len 3; hex 656565; asc eee;; |
只在记录6上加了一个X的next-key锁 |
case6::>=2 and <6 |
MySQL> select * from A where id>=2 and id<6 for update; +----+------+ | id | name | +----+------+ | 2 | aa | +----+------+ 1 row in set (0.00 sec) |
TABLE LOCK table `test`.`A` trx id 71D lock mode IX RECORD LOCKS space id 0 page no 306 n bits 88 index `PRIMARY` of table `test`.`A` trx id 71Dlock_mode X locks rec but not gap Record lock, heap no 19 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000002; asc ;; 1: len 6; hex 000000000714; asc ;; 2: len 7; hex 94000001960110; asc ;; 3: len 2; hex 6161; asc aa;; RECORD LOCKS space id 0 page no 306 n bits 88 index `PRIMARY` of table `test`.`A` trx id 71Dlock_mode X Record lock, heap no 14 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000006; asc ;; 1: len 6; hex 000000000536; asc 6;; 2: len 7; hex ae0000014f0110; asc O ;; 3: len 3; hex 656565; asc eee;; |
记录2上加了一个X的记录锁;记录6上加了一个X的next-key锁 |
case7: >=2 and <=6 |
select * from A where id>=2 and id<=6 for update; |
------------ TRANSACTIONS ------------ Trx id counter 729 Purge done for trx's n:o < 703 undo n:o < 0 History list length 43 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 727, not started MySQL thread id 2, OS thread handle 0x41743960, query id 207 localhost root ---TRANSACTION 728, ACTIVE 2 sec 3 lock struct(s), heap size 376, 3 row lock(s) MySQL thread id 1, OS thread handle 0x415b9960, query id 212 localhost root show engine InnoDB status TABLE LOCK table `test`.`A` trx id 728 lock mode IX RECORD LOCKS space id 0 page no 306 n bits 88 index `PRIMARY` of table `test`.`A` trx id 728lock_mode X locks rec but not gap Record lock, heap no 19 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000002; asc ;; 1: len 6; hex 000000000714; asc ;; 2: len 7; hex 94000001960110; asc ;; 3: len 2; hex 6161; asc aa;; RECORD LOCKS space id 0 page no 306 n bits 88 index `PRIMARY` of table `test`.`A` trx id 728lock_mode X Record lock, heap no 14 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000006; asc ;; 1: len 6; hex 000000000536; asc 6;; 2: len 7; hex ae0000014f0110; asc O ;; 3: len 3; hex 656565; asc eee;; Record lock, heap no 18 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000007; asc ;; 1: len 6; hex 0000000005cd; asc ;; 2: len 7; hex a7000001900110; asc ;; 3: len 2; hex 6161; asc aa;; |
记录2加了记录锁;记录6和7加了next-key锁 |
case8: =4(记录不存在) |
MySQL> select * from A where id=4 for update; Empty set (0.01 sec) |
------------ TRANSACTIONS ------------ Trx id counter 720 Purge done for trx's n:o < 703 undo n:o < 0 History list length 43 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 71A, not started MySQL thread id 2, OS thread handle 0x41743960, query id 163 localhost root ---TRANSACTION 71F, ACTIVE 3 sec 2 lock struct(s), heap size 376, 1 row lock(s) MySQL thread id 1, OS thread handle 0x415b9960, query id 172 localhost root show engine InnoDB status TABLE LOCK table `test`.`A` trx id 71F lock mode IX RECORD LOCKS space id 0 page no 306 n bits 88 index `PRIMARY` of table `test`.`A` trx id 71Flock_mode X locks gap before rec Record lock, heap no 14 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000006; asc ;; 1: len 6; hex 000000000536; asc 6;; 2: len 7; hex ae0000014f0110; asc O ;; 3: len 3; hex 656565; asc eee;; |
对一个不存在的记录加锁,锁住间隙,所以在记录6上加了一个X模式的gap锁 |
case9: =6 |
MySQL> select * from A where id=6 for update; +----+------+ | id | name | +----+------+ | 6 | eee | +----+------+ 1 row in set (0.00 sec) |
------------ TRANSACTIONS ------------ Trx id counter 722 Purge done for trx's n:o < 703 undo n:o < 0 History list length 43 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 71A, not started MySQL thread id 2, OS thread handle 0x41743960, query id 163 localhost root ---TRANSACTION 721, ACTIVE 2 sec 2 lock struct(s), heap size 376, 1 row lock(s) MySQL thread id 1, OS thread handle 0x415b9960, query id 182 localhost root show engine InnoDB status TABLE LOCK table `test`.`A` trx id 721 lock mode IX RECORD LOCKS space id 0 page no 306 n bits 88 index `PRIMARY` of table `test`.`A` trx id 721 lock_mode X locks rec but not gap Record lock, heap no 14 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000006; asc ;; 1: len 6; hex 000000000536; asc 6;; 2: len 7; hex ae0000014f0110; asc O ;; 3: len 3; hex 656565; asc eee;; |
只加一个记录锁 |
对于记录的末尾会不会有什么不同吗?我们继续验证一下:
case10:>12 |
MySQL> select * from A where id>12 for update; Empty set (0.00 sec) |
------------ TRANSACTIONS ------------ Trx id counter 72A Purge done for trx's n:o < 703 undo n:o < 0 History list length 43 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 727, not started MySQL thread id 2, OS thread handle 0x41743960, query id 207 localhost root ---TRANSACTION 729, ACTIVE 19 sec 2 lock struct(s), heap size 376, 1 row lock(s) MySQL thread id 1, OS thread handle 0x415b9960, query id 217 localhost root show engine InnoDB status Trx read view will not see trx with id >= 72A, sees < 72A TABLE LOCK table `test`.`A` trx id 729 lock mode IX RECORD LOCKS space id 0 page no 306 n bits 88 index `PRIMARY` of table `test`.`A` trx id 729 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;; |
不太一样,对无穷大supremum加了一个X的next-key锁 |
case11:>=12 |
MySQL> select * from A where id>=12 for update; +----+------+ | id | name | +----+------+ | 12 | bbb | +----+------+ 1 row in set (0.00 sec) |
------------ TRANSACTIONS ------------ Trx id counter 72A Purge done for trx's n:o < 703 undo n:o < 0 History list length 43 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 727, not started MySQL thread id 2, OS thread handle 0x41743960, query id 207 localhost root ---TRANSACTION 729, ACTIVE 62 sec 3 lock struct(s), heap size 376, 2 row lock(s) MySQL thread id 1, OS thread handle 0x415b9960, query id 219 localhost root show engine InnoDB status Trx read view will not see trx with id >= 72A, sees < 72A TABLE LOCK table `test`.`A` trx id 729 lock mode IX RECORD LOCKS space id 0 page no 306 n bits 88 index `PRIMARY` of table `test`.`A` trx id 729 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 LOCKS space id 0 page no 306 n bits 88 index `PRIMARY` of table `test`.`A` trx id 729lock_mode X locks rec but not gap Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 8000000c; asc ;; 1: len 6; hex 000000000542; asc B;; 2: len 7; hex b900000156011c; asc V ;; 3: len 3; hex 626262; asc bbb;; |
除了等于的记录12加记录所,还要给sup加next-key |
case12: =12 |
MySQL> select * from A where id=12 for update; +----+------+ | id | name | +----+------+ | 12 | bbb | +----+------+ 1 row in set (0.01 sec) |
------------ TRANSACTIONS ------------ Trx id counter 78E Purge done for trx's n:o < 78D undo n:o < 0 History list length 63 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 789, not started MySQL thread id 10, OS thread handle 0x415b9960, query id 510 localhost root ---TRANSACTION 78D, ACTIVE 17 sec 2 lock struct(s), heap size 376, 1 row lock(s) MySQL thread id 11, OS thread handle 0x41743960, query id 522 localhost root show engine InnoDB status Trx read view will not see trx with id >= 78E, sees < 78E TABLE LOCK table `test`.`A` trx id 78D lock mode IX RECORD LOCKS space id 0 page no 420 n bits 80 index `PRIMARY` of table `test`.`A` trx id 78D lock_mode X locks rec but not gap /*后面值的操作由于我做了表内容改动,所以heap no变了,但不影响结论*/ Record lock, heap no 8 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 8000000c; asc ;; 1: len 6; hex 00000000077f; asc ;; 2: len 7; hex ef000001750158; asc u X;; 3: len 3; hex 626262; asc bbb;; |
不多说 |
case13: <=12 and >11 |
MySQL> select * from A where id<=12 and id>11 for update; +----+------+ | id | name | +----+------+ | 12 | bbb | +----+------+ 1 row in set (0.00 sec) |
------------ TRANSACTIONS ------------ Trx id counter 78F Purge done for trx's n:o < 78D undo n:o < 0 History list length 63 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 789, not started MySQL thread id 10, OS thread handle 0x415b9960, query id 510 localhost root ---TRANSACTION 78E, ACTIVE 9 sec 2 lock struct(s), heap size 376, 2 row lock(s) MySQL thread id 11, OS thread handle 0x41743960, query id 527 localhost root show engine InnoDB status TABLE LOCK table `test`.`A` trx id 78E lock mode IX RECORD LOCKS space id 0 page no 420 n bits 80 index `PRIMARY` of table `test`.`A` trx id 78E 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 8 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 8000000c; asc ;; 1: len 6; hex 00000000077f; asc ;; 2: len 7; hex ef000001750158; asc u X;; 3: len 3; hex 626262; asc bbb;; |
sup和记录12加X的next-key锁 |
case14: <12 and >11 |
MySQL> select * from A where id<12 and id>11 for update; Empty set (0.00 sec) |
------------ TRANSACTIONS ------------ Trx id counter 790 Purge done for trx's n:o < 78D undo n:o < 0 History list length 63 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 789, not started MySQL thread id 10, OS thread handle 0x415b9960, query id 510 localhost root ---TRANSACTION 78F, ACTIVE 4 sec 2 lock struct(s), heap size 376, 1 row lock(s) MySQL thread id 11, OS thread handle 0x41743960, query id 531 localhost root show engine InnoDB status TABLE LOCK table `test`.`A` trx id 78F lock mode IX RECORD LOCKS space id 0 page no 420 n bits 80 index `PRIMARY` of table `test`.`A` trx id 78F lock_mode X Record lock, heap no 8 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 8000000c; asc ;; 1: len 6; hex 00000000077f; asc ;; 2: len 7; hex ef000001750158; asc u X;; 3: len 3; hex 626262; asc bbb;; |
从上面的case实验可以得出一张表,表示不同条件下分别在哪些索引项上,加何种锁:
左边一列是where条件,G表是GAP锁加在哪个索引项上,N是next-key锁,R是记录锁。有了这个表,就可以知道在不同条件的事务并发下,哪些会产生锁等待。
比如,如果有下面两个并发事务发生:
t1 |
t2 |
select * from A where id<2 for update; |
select * from A where id=2 for update; |
t1事务需要<2的,t2事务需要=2的,表面上两个条件没有重合之处,但是由于他们都是在索引key=2上加锁,所以就会产生冲突:
------------ TRANSACTIONS ------------ Trx id counter 71A Purge done for trx's n:o < 703 undo n:o < 0 History list length 43 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 719, ACTIVE 13 sec starting index read MySQL tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 376, 1 row lock(s) MySQL thread id 2, OS thread handle 0x41743960, query id 133 localhost root statistics select * from A where id=2 for update ------- TRX HAS BEEN WAITING 13 SEC FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 0 page no 306 n bits 88 index `PRIMARY` of table `test`.`A` trx id 719 lock_mode X locks rec but not gap waiting Record lock, heap no 19 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000002; asc ;; 1: len 6; hex 000000000714; asc ;; 2: len 7; hex 94000001960110; asc ;; 3: len 2; hex 6161; asc aa;; ------------------ TABLE LOCK table `test`.`A` trx id 719 lock mode IX RECORD LOCKS space id 0 page no 306 n bits 88 index `PRIMARY` of table `test`.`A` trx id 719 lock_mode X locks rec but not gap waiting Record lock, heap no 19 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000002; asc ;; 1: len 6; hex 000000000714; asc ;; 2: len 7; hex 94000001960110; asc ;; 3: len 2; hex 6161; asc aa;; ---TRANSACTION 718, ACTIVE 36 sec 2 lock struct(s), heap size 376, 1 row lock(s) MySQL thread id 1, OS thread handle 0x415b9960, query id 134 localhost root show engine InnoDB status TABLE LOCK table `test`.`A` trx id 718 lock mode IX RECORD LOCKS space id 0 page no 306 n bits 88 index `PRIMARY` of table `test`.`A` trx id 718 lock_mode X Record lock, heap no 19 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000002; asc ;; 1: len 6; hex 000000000714; asc ;; 2: len 7; hex 94000001960110; asc ;; 3: len 2; hex 6161; asc aa;; |
解析: 先执行了TRANSACTION 718,锁住heap no 19 PHYSICAL RECORD,后执行的TRANSACTION 719就会发生lock_mode X locks rec but not gap waiting。注意观察上面锁等待的信息 |
从上表还可知,同样的事情还会发生在下面类似的例子:
t1 |
t2 |
select * from A where id<=2 for update; |
select * from A where id>2 and id<6 for update; |
3.2 锁类型的精确模式
那么对于下面这种呢:
t1 |
t2 |
select * from A where id<2 for update; |
select * from A where id=1 for update; |
按照表来说两个事务都会对key=2加锁,而且都是for update的X锁,应该会有冲突,我们看下结果。
------------ TRANSACTIONS ------------ Trx id counter 71A Purge done for trx's n:o < 703 undo n:o < 0 History list length 43 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 719, ACTIVE 216 sec 2 lock struct(s), heap size 376, 1 row lock(s) MySQL thread id 2, OS thread handle 0x41743960, query id 136 localhost root TABLE LOCK table `test`.`A` trx id 719 lock mode IX RECORD LOCKS space id 0 page no 306 n bits 88 index `PRIMARY` of table `test`.`A` trx id 719 lock_mode X locks gap before rec Record lock, heap no 19 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000002; asc ;; 1: len 6; hex 000000000714; asc ;; 2: len 7; hex 94000001960110; asc ;; 3: len 2; hex 6161; asc aa;; ---TRANSACTION 718, ACTIVE 239 sec 2 lock struct(s), heap size 376, 1 row lock(s) MySQL thread id 1, OS thread handle 0x415b9960, query id 137 localhost root show engine InnoDB status TABLE LOCK table `test`.`A` trx id 718 lock mode IX RECORD LOCKS space id 0 page no 306 n bits 88 index `PRIMARY` of table `test`.`A` trx id 718 lock_mode X Record lock, heap no 19 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000002; asc ;; 1: len 6; hex 000000000714; asc ;; 2: len 7; hex 94000001960110; asc ;; 3: len 2; hex 6161; asc aa;; |
结果毫无冲突,两个X锁都成功了,而且都锁住heap no 19,为什么呢? |
为了解释上面的case就要引入一种行锁的精确模式,我先贴一下源码中对于4中锁类型的注释:
#define LOCK_S 4 /* shared */ #define LOCK_X 5 /* exclusive */ ... /* Waiting lock flag */ #define LOCK_WAIT 256 /* this wait bit should be so high that it can be ORed to the lock mode and type; when this bit is set, it means that the lock has not yet been granted, it is just waiting for its turn in the wait queue */ ... /* Precise modes */ #define LOCK_ORDINARY 0 /* this flag denotes an ordinary next-key lock in contrast to LOCK_GAP or LOCK_REC_NOT_GAP */ #define LOCK_GAP 512 /* this gap bit should be so high that it can be ORed to the other flags; when this bit is set, it means that the lock holds only on the gap before the record; for instance, an x-lock on the gap does not give permission to modify the record on which the bit is set; locks of this type are created when records are removed from the index chain of records */ #define LOCK_REC_NOT_GAP 1024 /* this bit means that the lock is only on the index record and does NOT block inserts to the gap before the index record; this is used in the case when we retrieve a record with a unique key, and is also used in locking plain SELECTs (not part of UPDATE or DELETE) when the user has set the READ COMMITTED isolation level */ #define LOCK_INSERT_INTENTION 2048 /* this bit is set when we place a waiting gap type record lock request in order to let an insert of an index record to wait until there are no conflicting locks by other transactions on the gap; note that this flag remains set when the waiting lock is granted, or if the lock is inherited to a neighboring record */ |
精确模式就是从源码中导出的。大家都知到S锁和X锁的兼容关系,但这只是锁的模式,上面说的InnoDB行锁有四种类型:G(gap锁)、R(记录锁)、N(next-key锁)、I(插入意向锁)。那么对于不同类型的锁在X模式下有怎样的兼容关系呢?(S模式下没有什么冲突,不用解释)
有人从源码发掘出一个行锁兼容矩阵,这个在官方文档中并没有。
兼容性 |
G |
I |
R |
N |
当前持有的X锁类型 |
G |
+ |
+ |
+ |
+ |
要加的X锁类型 |
I |
- |
+ |
+ |
- |
|
R |
+ |
+ |
- |
- |
|
N |
+ |
+ |
- |
- |
+ 代表兼容, -代表不兼容。S锁和S锁是完全兼容的,因此在判别兼容性时不需要对比精确模式,精确模式的检测,用在S、X和X、X之间。从这个精确模式可以看出,<2是N锁,=1是G锁,这两种锁匙完全兼容的,所以即使都是X锁也没有冲突,而=2是R锁,N和R是不兼容的,所以<2和=2冲突。同时大家要注意这个矩阵不是对称的,这点在I锁的兼容性上,大家可以通过类似实验验证。其实上表中的N锁应该分解成G+R锁来看会好理解一些。
这种新的兼容性是为了带来更好的事务并发性,但也会带来一些其他问题呢?比如下面的例子:
t1 |
t2 |
MySQL> select * from A where id>2 and id<6 for update; Empty set (0.00 sec) |
MySQL> select * from A where id=4 for update; Empty set (0.00 sec) |
两个事务都加锁成功: ------------ TRANSACTIONS ------------ Trx id counter 792 Purge done for trx's n:o < 78D undo n:o < 0 History list length 63 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 791, ACTIVE 58 sec 2 lock struct(s), heap size 376, 1 row lock(s) MySQL thread id 11, OS thread handle 0x41743960, query id 539 localhost root show engine InnoDB status TABLE LOCK table `test`.`A` trx id 791 lock mode IX RECORD LOCKS space id 0 page no 420 n bits 80 index `PRIMARY` of table `test`.`A` trx id 791 lock_mode X locks gap before rec Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000006; asc ;; 1: len 6; hex 00000000077f; asc ;; 2: len 7; hex ef00000175011c; asc u ;; 3: len 3; hex 656565; asc eee;; ---TRANSACTION 790, ACTIVE 95 sec 2 lock struct(s), heap size 376, 1 row lock(s) MySQL thread id 10, OS thread handle 0x415b9960, query id 537 localhost root TABLE LOCK table `test`.`A` trx id 790 lock mode IX RECORD LOCKS space id 0 page no 420 n bits 80 index `PRIMARY` of table `test`.`A` trx id 790 lock_mode X Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000006; asc ;; 1: len 6; hex 00000000077f; asc ;; 2: len 7; hex ef00000175011c; asc u ;; 3: len 3; hex 656565; asc eee;; |
|
MySQL> insert into A values(3,'abc'); |
|
出现锁等待: ------------ TRANSACTIONS ------------ Trx id counter 792 Purge done for trx's n:o < 78D undo n:o < 0 History list length 63 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 791, ACTIVE 226 sec 2 lock struct(s), heap size 376, 1 row lock(s) MySQL thread id 11, OS thread handle 0x41743960, query id 543 localhost root show engine InnoDB status TABLE LOCK table `test`.`A` trx id 791 lock mode IX RECORD LOCKS space id 0 page no 420 n bits 80 index `PRIMARY` of table `test`.`A` trx id 791 lock_mode X locks gap before rec Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000006; asc ;; 1: len 6; hex 00000000077f; asc ;; 2: len 7; hex ef00000175011c; asc u ;; 3: len 3; hex 656565; asc eee;; ---TRANSACTION 790, ACTIVE 263 sec inserting MySQL tables in use 1, locked 1 LOCK WAIT 3 lock struct(s), heap size 376, 2 row lock(s) MySQL thread id 10, OS thread handle 0x415b9960, query id 542 localhost root update insert into A values(3,'abc') ------- TRX HAS BEEN WAITING 22 SEC FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 0 page no 420 n bits 80 index `PRIMARY` of table `test`.`A` trx id 790 lock_mode X locks gap before rec insert intention waiting Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000006; asc ;; 1: len 6; hex 00000000077f; asc ;; 2: len 7; hex ef00000175011c; asc u ;; 3: len 3; hex 656565; asc eee;; ------------------ TABLE LOCK table `test`.`A` trx id 790 lock mode IX RECORD LOCKS space id 0 page no 420 n bits 80 index `PRIMARY` of table `test`.`A` trx id 790 lock_mode X Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000006; asc ;; 1: len 6; hex 00000000077f; asc ;; 2: len 7; hex ef00000175011c; asc u ;; 3: len 3; hex 656565; asc eee;; RECORD LOCKS space id 0 page no 420 n bits 80 index `PRIMARY` of table `test`.`A` trx id 790 lock_mode X locks gap before rec insert intention waiting Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000006; asc ;; 1: len 6; hex 00000000077f; asc ;; 2: len 7; hex ef00000175011c; asc u ;; 3: len 3; hex 656565; asc eee;; 原因很明显,I锁和G锁不兼容,需要等待,你虽然通过条件>2 and <6加了for update锁,但是并没有真正锁住区间,这时insert 3时会先加I锁,于是要等待t2的G锁冲突了 |
|
这时如果t2认为锁住了4记录,然后执行 MySQL> insert into A values(4,'abc'); 会怎样呢? 直接死锁了。 ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction |
|
很明显,因为t1等待t2的G锁,t2等待t1的N锁。 下面是死锁日志: ------------------------ LATEST DETECTED DEADLOCK ------------------------ 141216 14:54:55 *** (1) TRANSACTION: TRANSACTION 790, ACTIVE 556 sec inserting MySQL tables in use 1, locked 1 LOCK WAIT 3 lock struct(s), heap size 1248, 2 row lock(s) MySQL thread id 10, OS thread handle 0x415b9960, query id 544 localhost root update insert into A values(3,'abc') *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 0 page no 420 n bits 80 index `PRIMARY` of table `test`.`A` trx id 790lock_mode X locks gap before rec insert intention waiting Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000006; asc ;; 1: len 6; hex 00000000077f; asc ;; 2: len 7; hex ef00000175011c; asc u ;; 3: len 3; hex 656565; asc eee;; *** (2) TRANSACTION: TRANSACTION 791, ACTIVE 519 sec inserting MySQL tables in use 1, locked 1 3 lock struct(s), heap size 376, 2 row lock(s) MySQL thread id 11, OS thread handle 0x41743960, query id 545 localhost root update insert into A values(4,'abc') *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 0 page no 420 n bits 80 index `PRIMARY` of table `test`.`A` trx id 791 lock_mode X locks gap before rec Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000006; asc ;; 1: len 6; hex 00000000077f; asc ;; 2: len 7; hex ef00000175011c; asc u ;; 3: len 3; hex 656565; asc eee;; *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 0 page no 420 n bits 80 index `PRIMARY` of table `test`.`A` trx id 791 lock_mode X locks gap before rec insert intention waiting Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000006; asc ;; 1: len 6; hex 00000000077f; asc ;; 2: len 7; hex ef00000175011c; asc u ;; 3: len 3; hex 656565; asc eee;; *** WE ROLL BACK TRANSACTION (2) |
同样的原因还会有下面这种死锁的例子:
t1 |
t2 |
select * from A where id=3 for update; insert into A values(3,'abc'); |
select * from A where id=4 for update; insert into A values(4,'def'); |
“InnoDB locks all index records found by the WHERE clause with an exclusive lock and the gaps between them with a shared gap lock.” 网上会有上面这种说法,其实是因为X的gap锁之间是兼容的,其底层实现可能是通过S锁的方式实现的。
精确模式在InnoDB中非常重要,从中也可以观察到一些特性:
- GAP锁基本上跟所有锁都兼容
- Next-key锁和Record锁之间都冲突
- 持有Insert锁的记录可以兼容所有锁,但是Insert锁却不能加到GAP和Next-key锁上。
想想这些特性为什么。
4. 无索引锁
对于无索引的表,由于无法利用索引,因此会对所有记录加Next-key锁,可以观察一下实验结果:
建一个无索引的表B: | B | CREATE TABLE `B` ( `id` int(11) NOT NULL, `name` varchar(1024) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8 | MySQL> select * from B; +----+------+ | id | name | +----+------+ | 3 | dd | | 4 | t | | 5 | dd | | 4 | t | +----+------+ 4 rows in set (0.00 sec) |
执行下面语句: MySQL> select * from A where id=2 for update; +----+------+ | id | name | +----+------+ | 2 | aa | +----+------+ 1 row in set (0.00 sec) 由于没有索引,会锁全部索引项。而且全都是N锁。(4条记录和一个无穷大) |
------------ TRANSACTIONS ------------ Trx id counter 72F Purge done for trx's n:o < 703 undo n:o < 0 History list length 43 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 72C, not started MySQL thread id 1, OS thread handle 0x415b9960, query id 248 localhost root show engine InnoDB status ---TRANSACTION 72E, ACTIVE 3 sec 2 lock struct(s), heap size 376, 5 row lock(s) MySQL thread id 2, OS thread handle 0x41743960, query id 247 localhost root TABLE LOCK table `test`.`B` trx id 72E lock mode IX RECORD LOCKS space id 1 page no 3 n bits 72 index `GEN_CLUST_INDEX` of table `test`.`B` trx id 72E 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 5; compact format; info bits 0 0: len 6; hex 000000000203; asc ;; //InnoDB自动生成的6字节ID(索引相关文章有介绍) 1: len 6; hex 0000000005be; asc ;; 2: len 7; hex 1a0000018d0110; asc ;; 3: len 4; hex 80000003; asc ;; //记录字段 4: len 2; hex 6464; asc dd;; Record lock, heap no 3 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 0: len 6; hex 000000000204; asc ;; 1: len 6; hex 0000000005c5; asc ;; 2: len 7; hex 200000018e0110; asc ;; 3: len 4; hex 80000004; asc ;; 4: len 1; hex 74; asc t;; Record lock, heap no 4 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 0: len 6; hex 000000000205; asc ;; 1: len 6; hex 0000000005be; asc ;; 2: len 7; hex 1a0000018d0154; asc T;; 3: len 4; hex 80000005; asc ;; 4: len 2; hex 6464; asc dd;; Record lock, heap no 5 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 0: len 6; hex 000000000206; asc ;; 1: len 6; hex 0000000005c5; asc ;; 2: len 7; hex 200000018e0130; asc 0;; 3: len 4; hex 80000004; asc ;; 4: len 1; hex 74; asc t;; |
还有执行计划中没有使用到索引的也是类似上面这种情形,可以自行验证。
5. 辅助索引锁
下面再观察一下辅助索引的情况:
---------+ | transfer | CREATE TABLE `transfer` ( `id` int(11) NOT NULL AUTO_INCREMENT, `trans_id` int(11) NOT NULL, `name` varchar(256) NOT NULL, PRIMARY KEY (`id`), KEY `trans_id` (`trans_id`), KEY `name` (`name`(255)) ) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8 | MySQL> select * from transfer; +----+----------+------+ | id | trans_id | name | +----+----------+------+ | 1 | 101 | aaa | | 4 | 103 | bbb | | 10 | 104 | ddd | +----+----------+------+ 3 rows in set (0.01 sec) |
针对上表做一下一种条件的测试:
case1:=103
MySQL> select * from transfer where trans_id=103 for update; +----+----------+------+ | id | trans_id | name | +----+----------+------+ | 4 | 103 | bbb | +----+----------+------+ 1 row in set (0.00 sec) |
--TRANSACTION 771, ACTIVE 4 sec 4 lock struct(s), heap size 1248, 3 row lock(s) MySQL thread id 6, OS thread handle 0x415fa960, query id 421 localhost root TABLE LOCK table `test`.`transfer` trx id 771 lock mode IX RECORD LOCKS space id 22 page no 4 n bits 72 index `trans_id` of table `test`.`transfer` trx id 771lock_mode X //辅助索引page 4。加的是N锁,不同于主键索引哦 Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000067; asc g;; 辅助key hex67 == 103 1: len 4; hex 80000004; asc ;; 主key hex4 == 4 RECORD LOCKS space id 22 page no 3 n bits 72 index `PRIMARY` of table `test`.`transfer` trx id 771lock_mode X locks rec but not gap //主索引page 3 ,加了记录锁 Record lock, heap no 3 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 0: len 4; hex 80000004; asc ;; 主key 1: len 6; hex 0000000005e0; asc ;; trx_id 2: len 7; hex b8000001930110; asc ;; roll_ptr 3: len 4; hex 80000067; asc g;; 对应的辅助key 4: len 3; hex 626262; asc bbb;; name 字段 bbb RECORD LOCKS space id 22 page no 4 n bits 72 index `trans_id` of table `test`.`transfer` trx id 771lock_mode X locks gap before rec Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000068; asc h;; 在103后面的一条记录上加了gap锁 1: len 4; hex 8000000a; asc ;; 这里给104又加了一个gap锁,因为辅助键是可能重复的,所以可能会在其后插入相同的记录,因此这里要对其后的间隙加gap锁 如果在这里继续给101加锁,会不会也给103加一个gap锁呢? |
继续: MySQL> select * from transfer where trans_id=101 for update; +----+----------+------+ | id | trans_id | name | +----+----------+------+ | 1 | 101 | aaa | +----+----------+------+ 1 row in set (0.00 sec) |
---TRANSACTION 771, ACTIVE 403 sec 4 lock struct(s), heap size 1248, 5 row lock(s) MySQL thread id 6, OS thread handle 0x415fa960, query id 423 localhost root TABLE LOCK table `test`.`transfer` trx id 771 lock mode IX RECORD LOCKS space id 22 page no 4 n bits 72 index `trans_id` of table `test`.`transfer` trx id 771 lock_mode X Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000065; asc e;; 1: len 4; hex 80000001; asc ;; Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000067; asc g;; 1: len 4; hex 80000004; asc ;; RECORD LOCKS space id 22 page no 3 n bits 72 index `PRIMARY` of table `test`.`transfer` trx id 771 lock_mode X locks rec but not gap Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 0: len 4; hex 80000001; asc ;; 1: len 6; hex 0000000005d3; asc ;; 2: len 7; hex ac000001910110; asc ;; 3: len 4; hex 80000065; asc e;; 4: len 3; hex 616161; asc aaa;; Record lock, heap no 3 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 0: len 4; hex 80000004; asc ;; 1: len 6; hex 0000000005e0; asc ;; 2: len 7; hex b8000001930110; asc ;; 3: len 4; hex 80000067; asc g;; 4: len 3; hex 626262; asc bbb;; RECORD LOCKS space id 22 page no 4 n bits 72 index `trans_id` of table `test`.`transfer` trx id 771 lock_mode X locks gap before rec Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000068; asc h;; 1: len 4; hex 8000000a; asc ;; 没有!因为103上已经有N锁了,而N锁比G锁级别高,所以没有必要再加个G锁了。 所以如果是单独给101加锁,就肯定会给103加gap锁了,验证一下: |
单独加锁101 MySQL> select * from transfer where trans_id=101 for update; +----+----------+------+ | id | trans_id | name | +----+----------+------+ | 1 | 101 | aaa | +----+----------+------+ 1 row in set (0.00 sec) ---TRANSACTION 772, ACTIVE 4 sec 4 lock struct(s), heap size 1248, 3 row lock(s) MySQL thread id 6, OS thread handle 0x415fa960, query id 428 localhost root TABLE LOCK table `test`.`transfer` trx id 772 lock mode IX RECORD LOCKS space id 22 page no 4 n bits 72 index `trans_id` of table `test`.`transfer` trx id 772 lock_mode X Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000065; asc e;; 1: len 4; hex 80000001; asc ;; RECORD LOCKS space id 22 page no 3 n bits 72 index `PRIMARY` of table `test`.`transfer` trx id 772 lock_mode X locks rec but not gap Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 0: len 4; hex 80000001; asc ;; 1: len 6; hex 0000000005d3; asc ;; 2: len 7; hex ac000001910110; asc ;; 3: len 4; hex 80000065; asc e;; 4: len 3; hex 616161; asc aaa;; RECORD LOCKS space id 22 page no 4 n bits 72 index `trans_id` of table `test`.`transfer` trx id 772 lock_mode X locks gap before rec Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000067; asc g;; 1: len 4; hex 80000004; asc ;; 的确会给后面一条记录加G锁。 |
加锁不存在记录102,在103记录上加gap锁,主索引无锁 ---TRANSACTION 773, ACTIVE 3 sec 2 lock struct(s), heap size 376, 1 row lock(s) MySQL thread id 6, OS thread handle 0x415fa960, query id 434 localhost root TABLE LOCK table `test`.`transfer` trx id 773 lock mode IX RECORD LOCKS space id 22 page no 4 n bits 72 index `trans_id` of table `test`.`transfer` trx id 773 lock_mode X locks gap before rec Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000067; asc g;; 1: len 4; hex 80000004; asc ;; |
加锁<101,粒度比=101要少一个后一条记录的G锁,很好理解 MySQL> select * from transfer where trans_id<101 for update; Empty set (0.00 sec) ---TRANSACTION 774, ACTIVE 3 sec 3 lock struct(s), heap size 376, 2 row lock(s) MySQL thread id 6, OS thread handle 0x415fa960, query id 440 localhost root TABLE LOCK table `test`.`transfer` trx id 774 lock mode IX RECORD LOCKS space id 22 page no 4 n bits 72 index `trans_id` of table `test`.`transfer` trx id 774 lock_mode X Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000065; asc e;; 1: len 4; hex 80000001; asc ;; RECORD LOCKS space id 22 page no 3 n bits 72 index `PRIMARY` of table `test`.`transfer` trx id 774 lock_mode X locks rec but not gap Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 0: len 4; hex 80000001; asc ;; 1: len 6; hex 0000000005d3; asc ;; 2: len 7; hex ac000001910110; asc ;; 3: len 4; hex 80000065; asc e;; 4: len 3; hex 616161; asc aaa;; |
<=101 把101和103主辅索引都锁住了 MySQL> select * from transfer where trans_id<=101 for update; +----+----------+------+ | id | trans_id | name | +----+----------+------+ | 1 | 101 | aaa | +----+----------+------+ 1 row in set (0.00 sec) ---TRANSACTION 775, ACTIVE 17 sec 3 lock struct(s), heap size 376, 4 row lock(s) MySQL thread id 6, OS thread handle 0x415fa960, query id 444 localhost root TABLE LOCK table `test`.`transfer` trx id 775 lock mode IX RECORD LOCKS space id 22 page no 4 n bits 72 index `trans_id` of table `test`.`transfer` trx id 775 lock_mode X Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000065; asc e;; 1: len 4; hex 80000001; asc ;; Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000067; asc g;; 1: len 4; hex 80000004; asc ;; RECORD LOCKS space id 22 page no 3 n bits 72 index `PRIMARY` of table `test`.`transfer` trx id 775 lock_mode X locks rec but not gap Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 0: len 4; hex 80000001; asc ;; 1: len 6; hex 0000000005d3; asc ;; 2: len 7; hex ac000001910110; asc ;; 3: len 4; hex 80000065; asc e;; 4: len 3; hex 616161; asc aaa;; Record lock, heap no 3 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 0: len 4; hex 80000004; asc ;; 1: len 6; hex 0000000005e0; asc ;; 2: len 7; hex b8000001930110; asc ;; 3: len 4; hex 80000067; asc g;; 4: len 3; hex 626262; asc bbb;; |
所以,如果有下面两个并发,会冲突,虽然id=4跟trans_id<=101没有什么关系:
MySQL> select * from transfer where trans_id<=101 for update; +----+----------+------+ | id | trans_id | name | +----+----------+------+ | 1 | 101 | aaa | +----+----------+------+ 1 row in set (0.00 sec) |
MySQL> update transfer set name='ccc' where id=4; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction |
6. 表级锁
前面在说锁模式时提到IX和IS锁,官方定义是表级锁,而且IX和IS之间是全兼容,所以对于IX、IS和X、S锁之间的关系比较好奇,官方定义的兼容性:
|
X |
S |
IX |
IS |
X |
Conflict |
Conflict |
Conflict |
Conflict |
S |
Conflict |
Compatible |
Conflict |
Compatible |
IX |
Conflict |
Conflict |
Compatible |
Compatible |
IS |
Conflict |
Compatible |
Compatible |
Compatible |
S,X之间和IS,IX之间很好理解,重点关注的就是黄色区域的关系。
构造两个事务的状态:
---TRANSACTION 76C, not started MySQL thread id 2, OS thread handle 0x41743960, query id 371 localhost root ---TRANSACTION 76A, ACTIVE 838 sec 1 lock struct(s), heap size 376, 0 row lock(s) MySQL thread id 1, OS thread handle 0x415b9960, query id 375 localhost root show engine InnoDB status TABLE LOCK table `test`.`A` trx id 76A lock mode IX 76A事务目前持有A表的IX锁 |
IX和S关系 事务76C对A加表级S锁: lock table A read; 无冲突。(官方说有冲突) ---TRANSACTION 76C, not started MySQL tables in use 1, locked 1 MySQL thread id 2, OS thread handle 0x41743960, query id 378 localhost root ---TRANSACTION 76A, ACTIVE 925 sec 1 lock struct(s), heap size 376, 0 row lock(s) MySQL thread id 1, OS thread handle 0x415b9960, query id 379 localhost root show engine InnoDB status TABLE LOCK table `test`.`A` trx id 76A lock mode IX 注意:上面实验由于没有设置autocommit=0,必须为0时才生效!重做实验后: MySQL> set autocommit=0; Query OK, 0 rows affected (0.00 sec) MySQL> show variables like '%autocommit%'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | OFF | +---------------+-------+ 1 row in set (0.00 sec) MySQL> lock table A read; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction 有冲突,符合官方定义 ---TRANSACTION 7A2, ACTIVE 7 sec setting table lock LOCK WAIT 1 lock struct(s), heap size 376, 0 row lock(s) MySQL thread id 10, OS thread handle 0x415b9960, query id 584 localhost root System lock lock table A read ------- TRX HAS BEEN WAITING 7 SEC FOR THIS LOCK TO BE GRANTED: TABLE LOCK table `test`.`A` trx id 7A2 lock mode S waiting ------------------ TABLE LOCK table `test`.`A` trx id 7A2 lock mode S waiting ---TRANSACTION 7A1, ACTIVE 164 sec 1 lock struct(s), heap size 376, 0 row lock(s) MySQL thread id 11, OS thread handle 0x41743960, query id 585 localhost root show engine InnoDB status TABLE LOCK table `test`.`A` trx id 7A1 lock mode IX |
IX和X关系 事务76C对A加表级X锁: lock table A write; 有冲突 ---TRANSACTION 76C, not started MySQL thread id 2, OS thread handle 0x41743960, query id 380 localhost root Waiting for table metadata lock lock table A write ---TRANSACTION 76A, ACTIVE 970 sec 1 lock struct(s), heap size 376, 0 row lock(s) MySQL thread id 1, OS thread handle 0x415b9960, query id 381 localhost root show engine InnoDB status TABLE LOCK table `test`.`A` trx id 76A lock mode IX |
IS 和 S关系 无冲突 ---TRANSACTION 76E, not started MySQL tables in use 1, locked 1 MySQL thread id 2, OS thread handle 0x41743960, query id 405 localhost root ---TRANSACTION 76F, ACTIVE 727 sec 1 lock struct(s), heap size 376, 0 row lock(s) MySQL thread id 1, OS thread handle 0x415b9960, query id 406 localhost root show engine InnoDB status TABLE LOCK table `test`.`A` trx id 76F lock mode IS |
IS 和 X关系 有冲突 ---TRANSACTION 76E, not started MySQL thread id 2, OS thread handle 0x41743960, query id 400 localhost root Waiting for table metadata lock lock table A write ---TRANSACTION 76F, ACTIVE 651 sec 1 lock struct(s), heap size 376, 0 row lock(s) MySQL thread id 1, OS thread handle 0x415b9960, query id 401 localhost root show engine InnoDB status TABLE LOCK table `test`.`A` trx id 76F lock mode IS |
对于delete、update等各种操作以及lock in share mode下或各种查询条件下锁的情况都可以用上面的方法通过实验分析。
附录
A. InnoDB几项常用行锁变量:
MySQL> show status like 'innodb_row%';
+-------------------------------+--------+
| Variable_name | Value |
+-------------------------------+--------+
| Innodb_row_lock_current_waits | 0 |
| Innodb_row_lock_time | 191673 |累计锁等待时间(毫秒)
| Innodb_row_lock_time_avg | 31945 |平均行锁等待时间
| Innodb_row_lock_time_max | 50965 |最大行锁等待时间
| Innodb_row_lock_waits | 6 |
| Innodb_rows_deleted | 0 |
| Innodb_rows_inserted | 17 |
| Innodb_rows_read | 45 |
| Innodb_rows_updated | 2 |
+-------------------------------+--------+
| Innodb_lock_wait_timeout | 50 |锁等待超时时间(秒)
B. 问题案例
举两个实际环境中的案例,由于锁的冲突而导致的故障
案例一:
首先把某实际系统中的数据库操作shell脚本简化如下,省略号略去一些无关信息:
MySQL.core -q -e " insert into bfb_analytics.yc_dashboard_cust_temp ……; " & MySQL.core -q -e " drop table if exists bfb_analytics.yc_dashboard_cust_cat_${i_date}; create table bfb_analytics.yc_dashboard_cust_cat_${i_date}( select f_buyer_user_id …… from bfb_analytics.yc_dashboard_cust_temp ); " |
大致逻辑是第一条语句往yc_dashboard_cust_temp表中插入数据,但会放入后台运行。第二条语句从yc_dashboard_cust_temp表select数据导入新表。但在运行过程中该脚本频繁出现新表yc_dashboard_cust_cat_${i_date}创建失败,同时会有对该表的lock wait超时。从通常的InnoDB锁分析看select f_buyer_user_id ,…… from bfb_analytics.yc_dashboard_cust_temp这种语句应该会通过MVCC方式读取表的快照,而不会对表加锁。那么这里为何会加锁呢?首先要确认一下当前MySQL环境:
+---------------+-----------------+ | Variable_name | Value | +---------------+-----------------+ | tx_isolation | REPEATABLE-READ | +---------------+-----------------+ +--------------------------------+-------+ | Variable_name | Value | +--------------------------------+-------+ | innodb_locks_unsafe_for_binlog | OFF | +--------------------------------+-------+ +---------------+-----------+ | Variable_name | Value | +---------------+-----------+ | binlog_format | STATEMENT | +---------------+-----------+ | log_bin | ON | |
上面几个变量值会对InnoDB锁行为有所影响。
原因分析:
在RR隔离级别下,同时开启了bin-log时,系统首先认为你是需要进行数据恢复和主从同步的。 为了保证事务在主从数据一致,对于create … B select * from A;这种情况必须对A表加锁,否则可能会存在另一个事务在对A做update操作,当这两个事务写入bin-log时就会由于事务完成时间的不确定而写入 顺序不同,那么当同步或者恢复时就会造成数据不一致。
案例二:
另外一个案例跟上面差不多,有类似下面的shell脚本:
create table bfb_analytics.yc_dashboard_rec_${i_date}( select f_trans_id …… from bfb_db.t_recvables where f_create_time<=’2014-10-11’ and f_create_time>’2014-09-01’ ); |
同时每天会有bfb_db.t_recvables表的主从同步在进行。 |
该 脚本的运行几乎每天都会发生因为锁等待超时而导致主从同步停止,所以一定是上面操作bfb_db.t_recvables的语句问题。按案例一中的分析可 知create … select …类型需要对select的查询表加锁,但是因为这里加了where限定条件,而且recvables表在f_create_time上也有索引,所以锁 应该是加在where限定范围内的。主从同步的时间都是当前时间,跟脚本中的时间范围跟主从同步的时间没有任何交集,为何会锁等待呢?
原因分析:
通过查看该语句的执行计划(explain)才知,由于结果集数据量较大,当夸天超过15天时就已经不再使用f_create_time上的索引了,当InnoDB不能使用索引时就只能锁全部记录,这样就演变成案例一中的情形了。
上面的两个case可以通过下面的测试验证:
select * from A where id=2 for update; |
create table D select * from A; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction |
------------ TRANSACTIONS ------------ Trx id counter 79B Purge done for trx's n:o < 796 undo n:o < 0 History list length 65 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 79A, ACTIVE 3 sec starting index read MySQL tables in use 2, locked 2 LOCK WAIT 2 lock struct(s), heap size 376, 1 row lock(s) MySQL thread id 10, OS thread handle 0x415b9960, query id 557 localhost root Sending data create table D select * from A ------- TRX HAS BEEN WAITING 3 SEC FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 0 page no 420 n bits 80 index `PRIMARY` of table `test`.`A` trx id 79A lock mode S waiting Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000002; asc ;; 1: len 6; hex 00000000077f; asc ;; 2: len 7; hex ef000001750110; asc u ;; 3: len 2; hex 6161; asc aa;; ------------------ TABLE LOCK table `test`.`A` trx id 79A lock mode IS RECORD LOCKS space id 0 page no 420 n bits 80 index `PRIMARY` of table `test`.`A` trx id 79A lock mode S waiting Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000002; asc ;; 1: len 6; hex 00000000077f; asc ;; 2: len 7; hex ef000001750110; asc u ;; 3: len 2; hex 6161; asc aa;; ---TRANSACTION 798, ACTIVE 18 sec 2 lock struct(s), heap size 376, 1 row lock(s) MySQL thread id 11, OS thread handle 0x41743960, query id 558 localhost root show engine InnoDB status TABLE LOCK table `test`.`A` trx id 798 lock mode IX RECORD LOCKS space id 0 page no 420 n bits 80 index `PRIMARY` of table `test`.`A` trx id 798 lock_mode X locks rec but not gap Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000002; asc ;; 1: len 6; hex 00000000077f; asc ;; 2: len 7; hex ef000001750110; asc u ;; 3: len 2; hex 6161; asc aa;; |
确实是要加锁,原因正如前面所述,由于binlog记录是有序的,要保证数据恢复和同步必须加锁。但如果将innodb_locks_unsafe_for_binlog设置off,也就是不要binlog的安全功能了,这里就不会加锁了。当然如果关闭binlog也不用加锁。InnoDB在默认情况下不得不设置锁定,因为在从一个备份的回滚恢复中,每个SQL语句不得不以与它最初被执行的方式完全同样的方式执行。
C. LOG中事务ID的解释
顺便提一下开头提到的聚簇索引的结构,主键key值下面是TID,这个值记录的是最后一次修改该字段的事务ID。
------------ TRANSACTIONS ------------ Trx id counter 7A6 Purge done for trx's n:o < 79D undo n:o < 0 History list length 66 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 7A4, not started MySQL thread id 16, OS thread handle 0x415fa960, query id 604 localhost root ---TRANSACTION 7A5, ACTIVE 9 sec //当前的事务ID 2 lock struct(s), heap size 376, 8 row lock(s) MySQL thread id 17, OS thread handle 0x41743960, query id 610 localhost root show engine InnoDB status TABLE LOCK table `test`.`A` trx id 7A5 lock mode IX RECORD LOCKS space id 0 page no 420 n bits 80 index `PRIMARY` of table `test`.`A` trx id 7A5 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 4; compact format; info bits 0 0: len 4; hex 80000002; asc ;; //主key值 1: len 6; hex 00000000077f; asc ;; //事务ID,其实是产生这条记录的事务ID,类似于一个数据的版本号,用于MVCC中。 2: len 7; hex ef000001750110; asc u ;; //回滚LOG指针 3: len 2; hex 6161; asc aa;; //其他字段值 Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000006; asc ;; 1: len 6; hex 00000000077f; asc ;; //此时所有数据的事务ID都是一样的。 2: len 7; hex ef00000175011c; asc u ;; 3: len 3; hex 656565; asc eee;; Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000007; asc ;; 1: len 6; hex 00000000077f; asc ;; 2: len 7; hex ef000001750128; asc u (;; 3: len 2; hex 6161; asc aa;; Record lock, heap no 5 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000008; asc ;; 1: len 6; hex 00000000077f; asc ;; 2: len 7; hex ef000001750134; asc u 4;; 3: len 3; hex 616466; asc adf;; Record lock, heap no 6 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000009; asc ;; 1: len 6; hex 00000000077f; asc ;; 2: len 7; hex ef000001750140; asc u @;; 3: len 2; hex 6161; asc aa;; Record lock, heap no 7 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 8000000b; asc ;; 1: len 6; hex 00000000077f; asc ;; 2: len 7; hex ef00000175014c; asc u L;; 3: len 1; hex 61; asc a;; Record lock, heap no 8 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 8000000c; asc ;; 1: len 6; hex 00000000077f; asc ;; 2: len 7; hex ef000001750158; asc u X;; 3: len 3; hex 626262; asc bbb;; |
上面所有数据的事务ID都是一样的,如果我们这时候修改某条数据,他的ID应该会变成当前事务ID MySQL> update A set name='new' where id=8; |
------------ TRANSACTIONS ------------ Trx id counter 7A6 Purge done for trx's n:o < 79D undo n:o < 0 History list length 66 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 7A4, not started MySQL thread id 16, OS thread handle 0x415fa960, query id 604 localhost root ---TRANSACTION 7A5, ACTIVE 75 sec 2 lock struct(s), heap size 376, 8 row lock(s), undo log entries 1 MySQL thread id 17, OS thread handle 0x41743960, query id 613 localhost root show engine InnoDB status TABLE LOCK table `test`.`A` trx id 7A5 lock mode IX RECORD LOCKS space id 0 page no 420 n bits 80 index `PRIMARY` of table `test`.`A` trx id 7A5 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 4; compact format; info bits 0 0: len 4; hex 80000002; asc ;; 1: len 6; hex 00000000077f; asc ;; 2: len 7; hex ef000001750110; asc u ;; 3: len 2; hex 6161; asc aa;; Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000006; asc ;; 1: len 6; hex 00000000077f; asc ;; 2: len 7; hex ef00000175011c; asc u ;; 3: len 3; hex 656565; asc eee;; Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000007; asc ;; 1: len 6; hex 00000000077f; asc ;; 2: len 7; hex ef000001750128; asc u (;; 3: len 2; hex 6161; asc aa;; Record lock, heap no 5 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000008; asc ;; 1: len 6; hex 0000000007a5; asc ;; //变成当前事务ID了 2: len 7; hex 0f0000018801ca; asc ;; 3: len 3; hex 6e6577; asc new;; Record lock, heap no 6 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 80000009; asc ;; 1: len 6; hex 00000000077f; asc ;; 2: len 7; hex ef000001750140; asc u @;; 3: len 2; hex 6161; asc aa;; Record lock, heap no 7 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 8000000b; asc ;; 1: len 6; hex 00000000077f; asc ;; 2: len 7; hex ef00000175014c; asc u L;; 3: len 1; hex 61; asc a;; Record lock, heap no 8 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 4; hex 8000000c; asc ;; 1: len 6; hex 00000000077f; asc ;; 2: len 7; hex ef000001750158; asc u X;; 3: len 3; hex 626262; asc bbb;; |