[转载] 数据库分析手记 —— InnoDB锁机制分析

作者:倪煜

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): Transaction T intends to set S locks on individual rows in table t.)

(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): Transaction T intends to set X 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锁。

看一下官方定义:


InnoDB has several types of record-level locks including record locks, gap locks, and next-key locks. For information about shared locks, exclusive locks, and intention locks, see Section 14.2.3, “InnoDB Lock Modes”.

  • Record lock: This is a lock on an index record.
  • Gap lock: This is a lock on a gap between index records, or a lock on the gap before the first or after the last index record.
  • Next-key lock: This is a combination of a record lock on the index record and a gap lock on the gap before the index record.
  • INSERT sets an exclusive lock on the inserted row. This lock is an index-record lock, not a next-key lock (that is, there is no gap lock) and does not prevent other sessions from inserting into the gap before the inserted row.

行锁在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;;


解析:

  • TABLE LOCK table `test`.`A` trx id 720 lock mode IX

事务720对表A加IX锁,如前面所述,对行加X锁之前先对表加IX

  • lock_mode X locks gap before rec

对索引项加X锁,类型为gap锁

  • heap no 19 PHYSICAL RECORD

表示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;;


解析:

  • lock_mode X

在索引项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;;


分析:

  • lock_mode X locks rec but not gap

只有一个对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;;

时间: 2024-10-05 05:07:07

[转载] 数据库分析手记 —— InnoDB锁机制分析的相关文章

InnoDB锁机制分析

InnoDB锁机制常常困扰大家,不同的条件下往往表现出不同的锁竞争,在实际工作中经常要分析各种锁超时.死锁的问题.本文通过不同条件下的实验,利用InnoDB系统给出的各种信息,分析了锁的工作机制.通过本文可以帮助大家了解InnoDB锁的基本原理,常见的冲突.死锁,以及对InnoDB事务日志信息的解读. 1. 索引基本原理 InnoDB主要使用行级锁(row lock),其行锁是通过在索引项上加锁而实现的,如果MySQL的执行计划没有用到索引,那么行锁也就无意义了,所以了解锁之前需要了解一点索引原

linux RCU锁机制分析

openVswitch(OVS)源代码之linux RCU锁机制分析 分类: linux内核  |  标签: 云计算,openVswitch,linux内核,RCU锁机制  |  作者: yuzhihui_no1 相关  |  发布日期 : 2014-10-19  |  热度 : 1044° 前言 本来想继续顺着数据包的处理流程分析upcall调用的,但是发现在分析upcall调用时必须先了解linux中内核和用户空间通信接口Netlink机制,所以就一直耽搁了对upcall的分析.如果对ope

MySQL- InnoDB锁机制

InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION):二是采用了行级锁.行级锁与表级锁本来就有许多不同之处,另外,事务的引入也带来了一些新问题.下面我们先介绍一点背景知识,然后详细讨论InnoDB的锁问题. 背景知识 事务(Transaction)及其ACID属性 事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性. 原子性(Atomicity):事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行. 一致性

Linux 线程实现机制分析 Linux 线程实现机制分析 Linux 线程模型的比较:LinuxThreads 和 NPTL

Linux 线程实现机制分析 Linux 线程实现机制分析  Linux 线程模型的比较:LinuxThreads 和 NPTL http://www.ibm.com/developerworks/cn/linux/kernel/l-thread/ 自从多线程编程的概念出现在 Linux 中以来,Linux 多线应用的发展总是与两个问题脱不开干系:兼容性.效率.本文从线程模型入手,通过分析目前 Linux 平台上最流行的 LinuxThreads 线程库的实现及其不足,描述了 Linux 社区是

innodb 锁机制

InnoDB锁问题 InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION):二是采用了行级锁.行级锁与表级锁本来就有许多不同之处,另外,事务的引入也带来了一些新问题.下面我们先介绍一点背景知识,然后详细讨论InnoDB的锁问题. 背景知识 1.事务(Transaction)及其ACID属性 事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性. l         原子性(Atomicity):事务是一个原子操作单元,其对数据的

转载 深入浅出mysql事务处理和锁机制

1. 事务处理和并发性 1.1. 基础知识和相关概念 1 )全部的表类型都可以使用锁,但是只有 InnoDB 和 BDB 才有内置的事务功能. 2 )使用 begin 开始事务,使用 commit 结束事务,中间可以使用 rollback 回滚事务. 3 )在默认情况下, InnoDB 表支持一致读. SQL 标准中定义了 4 个隔离级别: read uncommited , read commited , repeatable read , serializable . read uncomm

【转载】Java中的锁机制 synchronized &amp; Lock

参考文章: http://blog.csdn.net/chen77716/article/details/6618779 目前在Java中存在两种锁机制:synchronized和Lock,Lock接口及其实现类是JDK5增加的内容,其作者是大名鼎鼎的并发专家Doug Lea.本文并不比较synchronized与Lock孰优孰劣,只是介绍二者的实现原理. 数据同步需要依赖锁,那锁的同步又依赖谁?synchronized给出的答案是在软件层面依赖JVM,而Lock给出的方案是在硬件层面依赖特殊的

mysql innodb的锁机制分析

线上生产环境在某些时候经常性的出现数据库操作死锁,导致业务人员无法进行操作.经过DBA的分析,是某一张表的insert操作和delete操作发生了死锁.简单介绍下数据库的情况(因为涉及到真实数据,这里做了模拟,不影响具体的分析和分析的结果.)假设存在如下2张表: Order 表的数据如下: Customer表的数据如下: Order和Customer 在实体关系上存在一个关联,即order实体拥有一个指向customer实体的指针.在数据库设计的时候,order表的customer_id没有被设

MySQL InnoDB锁机制之Gap Lock、Next-Key Lock、Record Lock解析

MySQL InnoDB支持三种行锁定方式: l   行锁(Record Lock):锁直接加在索引记录上面,锁住的是key. l   间隙锁(Gap Lock):锁定索引记录间隙,确保索引记录的间隙不变.间隙锁是针对事务隔离级别为可重复读或以上级别而已的. l   Next-Key Lock :行锁和间隙锁组合起来就叫Next-Key Lock. 默认情况下,InnoDB工作在可重复读隔离级别下,并且会以Next-Key Lock的方式对数据行进行加锁,这样可以有效防止幻读的发生.Next-K