mysql 插入加锁_MySQL-Insert-操作加锁分析

背景

看微博上丁奇大牛转发的一条微博,看作者分析的过程有疑问,Insert操作会有插入意向锁的判断,文中没有提到, 故查看源码试着分析一下。

问题描述1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23CREATE TABLE `z` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`b` int(11) DEFAULT NULL,

PRIMARY KEY (`id`),

KEY `b` (`b`)

) ENGINE=InnoDB

INSERT INTO z (id, b)

VALUES (1,2),

(3,4),

(5,6),

(7,8),

(9,10);

/* session A */

begin;

select * from z where b=6 for update;

/* session B*/

insert into z values(0,4); /*blocked*/

insert into z values(-1,4); /*success*/

insert into z values(4,8); /*blocked*/

insert into z values(8,8); /*success*/

文中提到的(id=5,b=6)前面有Gap Lock这点觉得有疑问。

Innodb打印出的锁信息

设置参数开启Innodb打印SQL加锁详情1

2set global innodb_status_output_locks=1;

set global innodb_status_output=1;

在RR模式下运行session A1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24## select * from z where b=6 for update

4 lock struct(s), heap size 1248, 3 row lock(s)

MySQL thread id 1, OS thread handle 0x7fff65ce9700, query id 15 localhost root cleaning up

TABLE LOCK table `test`.`z` trx id 102152 lock mode IX

### 索引b上的Next-key

RECORD LOCKS space id 57 page no 4 n bits 80 index `b` of table `test`.`z` trx id 102152 lock_mode X

Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0

0: len 4; hex 80000006; asc ;; ### b字段,值为6

1: len 4; hex 80000005; asc ;; ### 主键id字段,值为5

### 主键索引上记录锁

RECORD LOCKS space id 57 page no 3 n bits 80 index `PRIMARY` of table `test`.`z` trx id 102152 lock_mode X locks rec but not gap

Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 0

0: len 4; hex 80000005; asc ;; ### 主键id字段,值为5

1: len 6; hex 00000001890b; asc ;; ### 事务id,此行最近一次被更新的事务id,占6个字节

2: len 7; hex 8b0000013d0128; asc = (;; ### 回滚段指针DB_ROLL_PTR,用于MVCC

3: len 4; hex 80000006; asc ;; ### b字段,值为6

### 索引b上的GAP锁

RECORD LOCKS space id 57 page no 4 n bits 80 index `b` of table `test`.`z` trx id 102152 lock_mode X locks gap before rec

Record lock, heap no 5 PHYSICAL RECORD: n_fields 2; compact format; info bits 0

0: len 4; hex 80000008; asc ;; ## b字段,值为8

1: len 4; hex 80000007; asc ;; ## 主键id,值为7

可以看出此SQL语句加锁为索引b上的(4,8) = (4,6]的Next-key锁,(6,8)之间的GAP锁,主键上[5]。

这里就是疑问点。

在RR模式下运行session B

插入(0,4)1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17---TRANSACTION 102153, ACTIVE 689 sec inserting

mysql tables in use 1, locked 1

LOCK WAIT 2 lock struct(s), heap size 376, 1 row lock(s), undo log entries 1

MySQL thread id 2, OS thread handle 0x7fff65ca8700, query id 26 localhost root update

insert into z values(0,4)

------- TRX HAS BEEN WAITING 7 SEC FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 57 page no 4 n bits 80 index `b` of table `test`.`z` trx id 102153 lock_mode X locks gap before rec insert intention waiting

Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0

0: len 4; hex 80000006; asc ;;

1: len 4; hex 80000005; asc ;;

------------------

TABLE LOCK table `test`.`z` trx id 102153 lock mode IX

RECORD LOCKS space id 57 page no 4 n bits 80 index `b` of table `test`.`z` trx id 102153 lock_mode X locks gap before rec insert intention waiting

Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0

0: len 4; hex 80000006; asc ;;

1: len 4; hex 80000005; asc ;;

可以看出sessionB阻塞的原因是插入意向锁,等待b索引上(b=6,id=5)之前的GAP锁。

插入(2,8)1

2

3

4

5

6insert into z values(2,8)

------- TRX HAS BEEN WAITING 7 SEC FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 57 page no 4 n bits 80 index `b` of table `test`.`z` trx id 102698 lock_mode X locks gap before rec insert intention waiting

Record lock, heap no 5 PHYSICAL RECORD: n_fields 2; compact format; info bits 0

0: len 4; hex 80000008; asc ;;

1: len 4; hex 80000007; asc ;;

等待b索引上(b=8,id=7)之前的GAP锁。

[space, page_no] 可以确定锁对应哪个页,参考下上个月月报最后两个小节,页上每行数据紧接着存放,内部使用一个 heap_no 来表示是第几行数据。因此[space, page_no, heap_no]可以唯一确定一行。

尽管都是同一个page,但是heap_no不同。

Insert Debug调试

Insert操作的锁判断

给lock_rec_insert_check_and_lock函数添加断点。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60/*********************************************************************//**

Checks if locks of other transactions prevent an immediate insert of

a record. If they do, first tests if the query thread should anyway

be suspended for some reason; if not, then puts the transaction and

the query thread to the lock wait state and inserts a waiting request

for a gap x-lock to the lock queue.

@return DB_SUCCESS, DB_LOCK_WAIT, DB_DEADLOCK, or DB_QUE_THR_SUSPENDED */

UNIV_INTERN

dberr_t

lock_rec_insert_check_and_lock(

/*===========================*/

ulint flags, /*!< in: if BTR_NO_LOCKING_FLAG bit is

set, does nothing */

const rec_t* rec, /*!< in: record after which to insert */

buf_block_t* block, /*!< in/out: buffer block of rec */

dict_index_t* index, /*!< in: index */

que_thr_t* thr, /*!< in: query thread */

mtr_t* mtr, /*!< in/out: mini-transaction */

ibool* inherit)/*!< out: set to TRUE if the new

inserted record maybe should inherit

LOCK_GAP type locks from the successor

record */

{

...

//当前insert记录的逻辑下一条记录

next_rec = page_rec_get_next_const(rec);

next_rec_heap_no = page_rec_get_heap_no(next_rec);

...

//判断当前记录的下一条逻辑记录值上是否存在GAP锁或者插入意向锁

//如果存在,返回DB_LOCK_WAIT,当前insert进行等待队列

/* If another transaction has an explicit lock request which locks

the gap, waiting or granted, on the successor, the insert has to wait.

An exception is the case where the lock by the another transaction

is a gap type lock which it placed to wait for its turn to insert. We

do not consider that kind of a lock conflicting with our insert. This

eliminates an unnecessary deadlock which resulted when 2 transactions

had to wait for their insert. Both had waiting gap type lock requests

on the successor, which produced an unnecessary deadlock. */

if (lock_rec_other_has_conflicting(

¦ static_cast(

¦ LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION),

¦ block, next_rec_heap_no, trx)) {

/* Note that we may get DB_SUCCESS also here! */

trx_mutex_enter(trx);

err = lock_rec_enqueue_waiting(

LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION,

block, next_rec_heap_no, index, thr);

trx_mutex_exit(trx);

} else {

err = DB_SUCCESS;

}

...

两条Insert操作对应的下一条记录情况

insert into z values(0,4)1

2

3

4

5

6

7

8

9

10(gdb) p *next_rec

$5 = 128 '\200'

(gdb) p next_rec_heap_no

$6 = 4

TABLE LOCK table `test`.`z` trx id 102153 lock mode IX

RECORD LOCKS space id 57 page no 4 n bits 80 index `b` of table `test`.`z` trx id 102153 lock_mode X locks gap before rec insert intention waiting

Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0

0: len 4; hex 80000006; asc ;;

1: len 4; hex 80000005; asc ;;

打印这个下一行其加锁信息:

1

2(gdb)p lock->un_member->rec_lock

$21 = {space = 57, page_no = 4, n_bits = 80}

在没有加锁的情况,此语句插入的记录是(id=10,b=4),而不是(id=0,b=4)。这是因为主键是Auto_Increment。

insert into z values(-1,4)1

2

3

4

5

6(gdb) p next_rec

$7 = (const rec_t *) 0x7fff6e00808c "\200"

(gdb) p *next_rec

$8 = 128 '\200'

(gdb) p next_rec_heap_no

$9 = 3

这里就是为啥插入(-1,4)记录不会阻塞,而(0,4)会阻塞的根本原因。

思考插入意向锁作用是啥?

insert的时候为啥需要判断逻辑的下一条记录加锁情况呢?

插入意向锁是针对主键,还是二级索引?

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值