A.INSERT

插入操作在函数btr_cur_optimistic_insert->btr_cur_ins_lock_and_undo->lock_rec_insert_check_and_lock这里进行锁的判断,我们简单的看看这个函数的流程:

1.首先先看看欲插入记录之后的数据上有没有锁,

next_rec = page_rec_get_next_const(rec);

next_rec_heap_no = page_rec_get_heap_no(next_rec);

lock = lock_rec_get_first(block, next_rec_heap_no);

如果lock为空的话,对于非聚集索引,还需要更新page上的最大事务ID。

实际上这里是比较松散的检查,大并发插入的时候,可以大大的降低创建锁开销。

那么其他事务如何发现这些新插入的记录呢(重复插入同一条记录显然应该被阻塞),这里会有个判断,其他事务去看看

新插入记录的事务是否还是活跃的,如果还是活跃的,那么就为这个事务主动增加一个锁记录(所谓的隐式锁就是么有锁。。。。),这个判断是在检查是否存在冲突键的时候进行的(row_ins_duplicate_error_in_clust->row_ins_set_shared_rec_lock->lock_clust_rec_read_check_and_lock->lock_rec_convert_impl_to_expl

row_ins_set_shared_rec_lock的目的是为了向记录上加一个LOCK_REC_NOT_GAP的LOCK_S锁,也就是非GAP的记录S锁,如果发现记录上有X锁(隐式锁转换为LOCK_REC | LOCK_X | LOCK_REC_NOT_GAP),显然是需要等待的(返回DB_LOCK_WAIT)

这里设置inherit为FALSE,然后返回DB_SUCCESS;

至于inherit的作用,稍后再议!

2.如果lock不为空,这意味着插入记录的下一个记录上存在锁,设置inherit为TRUE.

检查下一个记录上的锁是否和LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION相互冲突

if (lock_rec_other_has_conflicting(

LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION,

block, next_rec_heap_no, trx)) {

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

err = lock_rec_enqueue_waiting(LOCK_X | LOCK_GAP

| LOCK_INSERT_INTENTION,

block, next_rec_heap_no,

index, the);

如果有别的事务在下一个记录上存在显式的锁请求,并且和锁模式( LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION) 冲突,那么

这时候当前事务就需要等待。

如果别的事务持有一个GAP类型的锁以等待插入,我们认为这个锁和当前插入不冲突。

如何判定锁之间是否冲突,在上一篇博客(http://mysqllover.com/?p=425)已经介绍过,不再赘述.

当检查到存在冲突的事务,我们就将一个锁模式为LOCK_X | LOCK_GAP|LOCK_X | LOCK_GAP 加入到请求队列中(调用函数lock_rec_enqueue_waiting),这里也会负责去检查死锁。

注意在加入等待队列的时候可能会返回DB_SUCCESS,例如死锁发生,但选择另外一个事务为牺牲者。

我们上面提到变量inherit,在存在下一个记录锁时会设置为TRUE,在上层函数btr_cur_optimistic_insert,会据此进行判断:

if (!(flags & BTR_NO_LOCKING_FLAG) && inherit) {

lock_update_insert(block, *rec);

}

注意当我们执行到这部分逻辑时err为DB_SUCCESS,表示锁检查已经通过了。

BTR_NO_LOCKING_FLAG表示不做记录锁检查

对于optimistic_insert, flags值为0

对于pessimistic_insert,flags值为BTR_NO_UNDO_LOG_FLAG | BTR_NO_LOCKING_FLAG | BTR_KEEP_SYS_FLAG

因此对于乐观更新(无需修改BTREE结构),当inherit被设置为TRUE时,总会调用lock_update_insert

根据注释,lock_update_insert用于继承下一条记录的GAP锁,流程如下

1.首先获取插入的记录的heap no和下一条记录的heap no

receiver_heap_no = rec_get_heap_no_new(rec);

donator_heap_no = rec_get_heap_no_new(

page_rec_get_next_low(rec, TRUE));

其中receiver_heap_no是当前记录,donator_heap_no是下一条记录

2.调用lock_rec_inherit_to_gap_if_gap_lock函数,将donator_heap_no上所有非INSERT INTENTION且非LOCK_REC_NOT_GAP的记录锁

转移给receiver_heap_no

遍历donator_heap_no上的所有记录锁,继承锁的判定条件如下:

if (!lock_rec_get_insert_intention(lock)

&& (heap_no == PAGE_HEAP_NO_SUPREMUM

|| !lock_rec_get_rec_not_gap(lock))) {

lock_rec_add_to_queue(LOCK_REC | LOCK_GAP

| lock_get_mode(lock),

block, heir_heap_no,

lock->index, lock->trx);

}

注意这里有对SUPREMUM记录的特殊处理。

也就是说,成功插入了一条记录,其他持有该记录的下一条记录上锁的事务也会持有新插入记录上的GAP锁。

说起INSERT,就不得不提到一个有趣的死锁案例。也就是bug#43210(http://bugs.mysql.com/bug.php?id=43210)

DROP TABLE t1;

CREATE TABLE `t1` (

`a` int(11) NOT NULL,

`b` int(11) DEFAULT NULL,

PRIMARY KEY (`a`),

KEY `b` (`b`)

) ENGINE=InnoDB;

insert into t1 values (1,19),(8,12);

Session 1:

set autocommit = 0;

insert into t1 values (6,12);

Session 2:

set autocommit = 0;

insert into t1 values (6,12);  //阻塞住,同时将session1的锁转换为显示锁。等待记录上的S锁 (查找dup key)

/****

session 1上的转为显式锁:lock_mode X locks rec but not gap

session 2等待的锁:lock mode S locks rec but not gap waiting

***/

Session 3:

set autocommit = 0;

insert into t1 values (6,12);  //阻塞住,和session2 同样等待S锁,lock mode S locks rec but not gap waiting

Session 1:

ROLLBACK;

Session 2:

执行插入成功

这时候Session 2持有的锁为主键记录上的:

lock mode S locks rec but not gap

lock mode S locks gap before rec

lock_mode X locks gap before rec insert intention

Session3:

被选为牺牲者,回滚掉。

很容易重现,当session 1回滚时,session2和session3提示死锁发生。

这里的关键是当ROLLBACK时,实际上是在做一次delete操作,backtrace如下:

trx_general_rollback_for_mysql->….->row_undo->row_undo_ins->row_undo_ins_remove_clust_rec->btr_cur_optimistic_delete->lock_update_delete->lock_rec_inherit_to_gap

我们来跟踪一下创建锁的轨迹

s1的事务0x7fdfd80265b8

s2的事务0x7fdfe0007c68

s3的事务0x7fdff00213f8

s1 , type_mode=1059     //s2为s1转换隐式锁为显式锁,

s2,  type_mode=1282    //检查重复键,需要加共享锁,被s1 block住,等待S锁

s3,  type_mode=1282    // 被s1 block住,等待S锁

s1, type_mode=547       //s1回滚,删除记录,lock_update_delete锁继承,

s2, type_mode=546        //创建s锁  LOCK_GAP | LOCK_REC | LOCK_S

s3, type_mode=546        //创建s锁   LOCK_GAP | LOCK_REC | LOCK_S

s2, type_mode=2819   // LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION

s3, type_mode=2819   //  LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION

看看show engine innodb status打印的死锁信息:

insert into t1 values (6,12)

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 137 page no 3 n bits 72 index `PRIMARY` of table `test`.`t1` trx id FE3BFA70 lock_mode X locks gap before rec insert intention waiting

*** (2) TRANSACTION:

TRANSACTION FE3BFA6F, ACTIVE 143 sec inserting, thread declared inside InnoDB 1

mysql tables in use 1, locked 1

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

MySQL thread id 791, OS thread handle 0x7fe2d4ea1700, query id 2613 localhost root update

insert into t1 values (6,12)

*** (2) HOLDS THE LOCK(S):

RECORD LOCKS space id 137 page no 3 n bits 72 index `PRIMARY` of table `test`.`t1` trx id FE3BFA6F lock mode S locks gap before rec

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 137 page no 3 n bits 72 index `PRIMARY` of table `test`.`t1` trx id FE3BFA6F lock_mode X locks gap before rec insert intention waiting

*** WE ROLL BACK TRANSACTION (2)

从上面的分析,我们可以很容易理解死锁为何发生。s1插入记录,s2插入同一条记录,主键冲突,s2将s1的隐式锁转为显式锁,同时s2向队列中加入一个s锁请求;

s3同样也加入一个s锁请求;

当s1回滚后,s2和s3获得s锁,但随后s2和s3又先后请求插入意向锁,因此锁队列为:

s2(S GAP)<—s3(S GAP)<—s2(插入意向锁)<–s3(插入意向锁)   s3,s2,s3形成死锁。

B.DELETE

Innodb的delete操作实际上只是做标记删除,而不是真正的删除记录;真正的删除是由Purge线程来完成的。

DELETE操作的记录加锁,是在查找记录时完成的。这一点,我们在上一节已经提到了。

上面我们有提到,对插入一条记录做回滚时,实际上是通过undo来做delete操作。这时候有一个lock_update_insert操作,我们来看看这个函数干了什么:

1.首先获取将被移除的记录HEAP NO和下一条记录的HEAP NO

heap_no = rec_get_heap_no_new(rec);

next_heap_no = rec_get_heap_no_new(page

+ rec_get_next_offs(rec,

TRUE));

2.然后获取kernel mutex锁,执行:

将被删除记录上的GAP锁转移到下一条记录上:

lock_rec_inherit_to_gap(block, block, next_heap_no, heap_no);

遍历heao_no上的锁对象,满足如下条件时为下一个记录上的事务创建新的锁对象:

if (!lock_rec_get_insert_intention(lock)

&& !((srv_locks_unsafe_for_binlog

|| lock->trx->isolation_level

<= TRX_ISO_READ_COMMITTED)

&& lock_get_mode(lock) == LOCK_X)) {

lock_rec_add_to_queue(LOCK_REC | LOCK_GAP

| lock_get_mode(lock),

heir_block, heir_heap_no,

lock->index, lock->trx);

}

条件1:锁对象不是插入意向锁(INSERT INTENTION LOCK)

条件2:srv_locks_unsafe_for_binlog被设置为FALSE且隔离级别大于READ COMMITTED, 或者锁类型为LOCK_S

和lock_update_insert类似,这里也会创建新的GAP锁对象

当完成锁表更新操作后,重置锁bit并释放等待的事务lock_rec_reset_and_release_wait(block, heap_no):

>>正在等待当前记录锁的(lock_get_wait(lock)),取消等待(lock_rec_cancel(lock))

>>已经获得当前记录锁的,重置对应bit位(lock_rec_reset_nth_bit(lock, heap_no);)

lock_update_delete主要在INSERT回滚及Purge线程中被调用到。

在查找数据时,DELETE会给记录加锁,在进行标记删除时,也会调用到锁检查函数:

聚集索引:

row_upd->row_upd_clust_step->row_upd_del_mark_clust_rec->btr_cur_del_mark_set_clust_rec->lock_clust_rec_modify_check_and_lock

这个backtrace,会从lock_clust_rec_modify_check_and_lock直接返回DB_SUCCESS,因为函数btr_cur_del_mark_set_clust_rec的参数flags总是

值为BTR_NO_LOCKING_FLAG

用户线程不做调用,但在btr_cur_upd_lock_and_undo则会继续走lock_clust_rec_modify_check_and_lock的流程。

二级索引:

row_upd->row_upd_sec_step->row_upd_sec_index_entry->btr_cur_del_mark_set_sec_rec->lock_sec_rec_modify_check_and_lock

用户线程里lock_sec_rec_modify_check_and_lock的flags参数为0,而在row_undo_mod_del_unmark_sec_and_undo_update、row_undo_mod_del_mark_or_remove_sec_low函数里则设置为BTR_NO_LOCKING_FLAG,表示不做检查。

lock_sec_rec_modify_check_and_lock用于检查是否有其他事务阻止当前修改一条二级索引记录(delete mark or delete unmark),

如果开始修改二级索引,则表示我们已经成功修改了聚集索引,因此不应该有其他事务在该记录上的隐式锁,也不应该有其他活跃事务修改了二级索引记录。该函数会调用:

err = lock_rec_lock(TRUE, LOCK_X | LOCK_REC_NOT_GAP,

block, heap_no, index, the);

第一个函数为TRUE,则当无需等待时,不会创建新的锁对象。

如果err返回值为DB_SUCCESS或者DB_SUCCESS_LOCKED_REC,就更新当前二级索引Page上的最大事务ID。

如果当前存在和LOCK_X|LOCK_REC_NOT_GAP相冲突的锁对象,则可能需要等待。

回到在之前博文提到的死锁,信息如下:

*** (1) TRANSACTION:

TRANSACTION 1E7D49CDD, ACTIVE 69 sec fetching rows

mysql tables in use 1, locked 1

LOCK WAIT 4 lock struct(s), heap size 1248, 4 row lock(s), undo log entries 1

MySQL thread id 1385867, OS thread handle 0x7fcebd956700, query id 837909262 10.246.145.78 im updating

delete    from        msg    WHERE     target_id = ‘Y25oaHVwYW7mmZbmmZblpKnkvb8=’      and         gmt_modified <= ’2012-12-14 15:07:14′

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 203 page no 475912 n bits 88 index `PRIMARY` of table `im`.`msg` trx id 1E7D49CDD lock_mode X locks rec but not gap waiting

*** (2) TRANSACTION:

TRANSACTION 1E7CE0399, ACTIVE 1222 sec fetching rows, thread declared inside InnoDB 272

mysql tables in use 1, locked 1

1346429 lock struct(s), heap size 119896504, 11973543 row lock(s), undo log entries 1

MySQL thread id 1090268, OS thread handle 0x7fcebf48c700, query id 837483530 10.246.145.78 im updating

delete    from        msg    WHERE     target_id = ‘Y25oaHVwYW7niLHkuZ3kuYU5OQ==’      and         gmt_modified <= ’2012-12-14 14:13:28′

*** (2) HOLDS THE LOCK(S):

RECORD LOCKS space id 203 page no 475912 n bits 88 index `PRIMARY` of table `im`.`msg` trx id 1E7CE0399 lock_mode X

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 203 page no 1611099 n bits 88 index `PRIMARY` of table `im`.`msg` trx id 1E7CE0399 lock_mode X waiting

表结构为:

CREATE TABLE `msg` (

`id` bigint(20) NOT NULL AUTO_INCREMENT,

`target_id` varchar(100) COLLATE utf8_bin NOT NULL ,

……

……

`flag` tinyint(4) NOT NULL ,

`gmt_create` datetime NOT NULL,

`gmt_modified` datetime NOT NULL,

`datablob` blob,

`nickname` varchar(64) COLLATE utf8_bin DEFAULT NULL ,

`source` tinyint(4) DEFAULT NULL ,

PRIMARY KEY (`id`),

KEY `idx_o_tid` (`target_id`,`gmt_modified`,`source`,`flag`)

) ENGINE=InnoDB

首先我们从死锁信息里来看,发生死锁的是两个delete语句,

delete    from        offmsg_0007    WHERE     target_id = ‘Y25oaHVwYW7mmZbmmZblpKnkvb8=’      and         gmt_modified <= ’2012-12-14 15:07:14′

delete    from        offmsg_0007    WHERE     target_id = ‘Y25oaHVwYW7niLHkuZ3kuYU5OQ==’      and         gmt_modified <= ’2012-12-14 14:13:28′

我们再看看这个表上的索引,一个主键索引(target_id),一个二级索引(`target_id`,`gmt_modified`,`source`,`flag`)

根据前缀索引的原则,理论上我们应该可以通过二级索引来查找数据,从上一节的分析,我们知道,如果根据二级索引查找数据:

>>二级索引上加X 锁,记录及GAP

>>聚集索引上加记录X锁

我们再看死锁信息:

第一条SQL等待聚集索引Page 475912上的lock_mode X locks rec but not gap, 这说明该锁请求等待是走二级索引的

第二条SQL持有聚集索引Page 475912上的lock_mode X锁,等待聚集索引Page 1611099上的 lock_mode X

因此我们大致可以认为第二条SQL总是在请求聚集索引上的LOCK_ORDINARY类型的锁,简单的gdb我们可以知道走聚集索引做范围删除,锁模式值为3,也就是LOCK_X

因此,可以推测delete操作走错了索引,导致出现资源的互相占用。从而死锁;至于为什么走错索引,这就是优化器的问题了,暂不明;

C.释放锁

在事务提交或回滚时,会释放记录锁,调用函数为lock_release_off_kernel

函数的逻辑很简单,遍历trx->trx_locks。

对于记录锁,调用lock_rec_dequeue_from_page(lock)

–>从lock_sys中删除

–>检查lock所在page上的等待的锁对象是否能被grant(lock_grant),如果可以,则唤醒等待的事务。

对于表锁,调用lock_table_dequeue(lock)

转载自: [MySQL学习] Innodb锁系统(4) Insert/Delete 锁处理及死锁示例分析

Innodb锁系统 Insert/Delete 锁处理及死锁示例分析相关推荐

  1. MySQL锁系统总结

    1 innoDB锁简介 innoDb支持多种粒度的锁,按照粒度来分,可分为表锁(LOCK_TABLE)和行锁(LOCK_REC) 一般的锁系统都会有共享锁和排他锁的分类,共享锁也叫读锁,排他锁也叫写锁 ...

  2. mysql 事务 innodb 锁表_MySQL性能优化之Innodb事务系统,值得收藏

    概述 今天主要分享下Innodb事务系统的一些优化相关,以下基于mysql 5.7. Innodb中的事务.视图.多版本 1.事务 在Innodb中,每次开启一个事务时,都会为该session分配一个 ...

  3. mysql 开发进阶篇系列 7 锁问题(innodb锁争用情况及锁模式)

    1 .获取innodb行锁争用情况 1.1 通过检查innodb_row_lock状态变量来分析系统上的行锁的争夺情况 SHOW STATUS LIKE 'innodb_row_lock%' 通过in ...

  4. MySQL怎么运行的系列(十)Innodb中的锁:记录锁、临键锁、间隙锁、意向锁

    本系列文章目录 展开/收起 MySQL怎么运行的系列(一)mysql体系结构和存储引擎 MySQL怎么运行的系列(二)Innodb缓冲池 buffer pool 和 改良版LRU算法 Mysql怎么运 ...

  5. InnoDB锁-共享锁、排他锁与意向锁

    InnoDB锁-共享锁.排他锁与意向锁 1.锁的分类 锁(Locking)是数据库在并发访问时保证数据一致性和完整性的主要机制.之前MyISAM锁章节已经讲过锁分类,而InnoDB锁按照粒度分为锁定整 ...

  6. mysql insert 乐观锁_【mysql】关于乐观锁

    一.乐观锁介绍 乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检,乐观锁适用于 ...

  7. MySQL数据库读现象 数据库锁机制 Innodb存储引擎行级锁

    数据库读现象 数据库管理软件的"读现象"指的是当多个事务并发执行时,在读取数据方面可能碰到的问题,包括有脏读.不可重复读和幻读. 创建数据表 # 创建数据表 create tabl ...

  8. MySQL数据库锁构建_MySQL数据库InnoDB存储引擎中的锁机制

    00 – 基本概念 当并发事务同时访问一个资源的时候,有可能导致数据不一致.因此需要一种致机制来将访问顺序化. 锁就是其中的一种机制.我们用商场的试衣间来做一个比喻.试衣间供许多消费者使用.因此可能有 ...

  9. mysql innodb 排他锁_MySQL 针对 InnoDB 引擎锁的种类:行锁(共享锁和排他锁)和表锁(意向共享锁和意向排他锁)...

    InnoDB 锁快速到底 行锁:共享锁(S).排他锁(X) 表锁:意向共享锁(IS).意向排他锁(IX) 下面主要针对 MySQL 中行级锁中的共享锁(S)与排他锁(X)进行分析 共享锁又称为读锁,简 ...

最新文章

  1. uni-app编译配置
  2. querystring java_java – 自定义枚举的QueryStringBindable
  3. 安装rocketmq并配置管理界面
  4. 重物码垛搬运机器人_搬运码垛机器人的特点及应用
  5. Android 中MVC实例之Activity,Window和View
  6. Python:使用SWIG编写C语言扩展
  7. 全国计算机等级考试题库二级C操作题100套(第46套)
  8. AcWing 126. 最大的和
  9. 5g理论速度_华为5G随行WiFi Pro 有多强 我们试一下NSA网络下的5G速度
  10. [转]MegCup2015初赛题
  11. [论文阅读] Self-supervised Correction Learning for Semi-supervised Biomedical Image Segmentation
  12. GitHub 标星 1.6w+,前方宝藏项目出没!| 原力计划
  13. sqlite 按拼音排序
  14. 地理信息系统GIS在城市生活垃圾管理中的应用时间
  15. 电子计算机为什么123安不出来,方正软件常见问题及其解决办法-精.doc
  16. TP-Link 886nV6 刷第三方系统回忆
  17. java md5 16位解密_Java md5加密解密数据
  18. python开源报表工具_12个最好的开源报表工具
  19. PHP-SDK实现微信付款码支付
  20. 劳务派遣能解决哪些用工难题?企业关心的都在这里!

热门文章

  1. java和net共同点,Java和.NET中的垃圾回收机制比较
  2. python统计学书籍推荐_一位90后统计学硕士的深悟:统计其实有门道!AI还能这样学!(精荐40本书+20视频资源...
  3. html5调盒子边框大小,CSS3 - 盒子大小(CSS3 - Box Sizing)
  4. switch语句可以被代替吗_大空间建筑内的消防水炮可以代替喷淋装置吗
  5. java怎么读取文件夹下的_java怎么读取读取文件夹下的所有文件夹和文件?
  6. mysql数据被截断_有关Mysql数据截断问题的处理方法
  7. linux如何判断网线插入_斜口钳和网线钳制作网线!
  8. IDEA安装class文件分析工具(binEd 和 JClassLib)
  9. C#三层架构之第三次课 业务逻辑层
  10. 重磅:JDK11正式发布!史上最全所有特性完整解读!