最近在整理线上性能时,发现一台线上DB出现两个insert产生的死锁问题

------------------------
LATEST DETECTED DEADLOCK
------------------------
150119 10:55:08
*** (1) TRANSACTION:
TRANSACTION 578E79C8, ACTIVE 0 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 7 lock struct(s), heap size 1248, 4 row lock(s), undo log entries 5
MySQL thread id 32094912, query id 2210940713 10.10.10.2 database_1 update
insert into table_1
(DATA_KEY,JOB_TYPE,FAILURE_QTY,OPT_STATUS,WAVE_NO,BIZ_TYPE,ORG_NO,DISTRIBUTE_NO,WAREHOUSE_NO,CREATE_TIME,UPDATE_TIME,CREATE_USER,UPDATE_USER,YN, REGION)
values
('',1009,0,0,'BC38015011900000062',10,'','','',now(),null,'taskAssign-sys',null,0,6)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 2784161 n bits 376 index `unique` of table `database_1`.`table_1` trx id 578E79C8 lock mode S waiting
Record lock, heap no 308 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 10; hex 38323034353933393534; asc 8204593954;;
1: len 19; hex 42433338303135303131393030303030303632; asc BC38015011900000062;;
2: len 4; hex 800003f1; asc ;;
3: len 8; hex 8000000000894c76; asc Lv;; *** (2) TRANSACTION:
TRANSACTION 578E79CA, ACTIVE 0 sec inserting, thread declared inside InnoDB 500
mysql tables in use 1, locked 1
7 lock struct(s), heap size 1248, 4 row lock(s), undo log entries 8
MySQL thread id 32094907, query id 2210940717 10.10.10.2 database_1 update
insert into table_1
(DATA_KEY,JOB_TYPE,FAILURE_QTY,OPT_STATUS,WAVE_NO,BIZ_TYPE,ORG_NO,DISTRIBUTE_NO,WAREHOUSE_NO,CREATE_TIME,UPDATE_TIME,CREATE_USER,UPDATE_USER,YN, REGION)
values
('',1009,0,0,'BC38015011900000062',10,'','','',now(),null,'taskAssign-sys',null,0,8)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 0 page no 2784161 n bits 376 index `unique` of table `database_1`.`table_1` trx id 578E79CA lock_mode X locks rec but not gap
Record lock, heap no 308 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 10; hex 38323034353933393534; asc 8204593954;;
1: len 19; hex 42433338303135303131393030303030303632; asc BC38015011900000062;;
2: len 4; hex 800003f1; asc ;;
3: len 8; hex 8000000000894c76; asc Lv;; *** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 2784161 n bits 376 index `index_otm_unique` of table `database_1`.`table_1` trx id 578E79CA lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 308 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 10; hex 38323034353933393534; asc 8204593954;;
1: len 19; hex 42433338303135303131393030303030303632; asc BC38015011900000062;;
2: len 4; hex 800003f1; asc ;;
3: len 8; hex 8000000000894c76; asc Lv;; *** WE ROLL BACK TRANSACTION (1)

表结构

primary(id)
unique(DATA_KEY,WAVE_NO,JOB_TYPE)
idx_update_time(update_time)
idx_create_time(create_time)

死锁本质原因是由于两个事务以相反顺序锁住了相同的数据,如下图:

对于上面两个insert产生的死锁,分析一下insert锁上面的逻辑,但是对于上面的死锁原因暂时还没有头绪。

姜承尧《MySQL内核.InnoDB存储引擎》中对于插入的说明(目录节点9.7.1 插入):

对InnoDB存储引擎表进行插入操作时,需进行如下步骤的操作:
1.首先对表加上IX表。(应该是IX锁)
2.根据查询模式PAGE_CUR_LE定位记录next_rec。
3.判断记录next_rec是否有锁,有的话等待锁的释放,否则直接插入。
插入操作需要定位插入记录的下一条记录,这是next-key locking算法所要求,因为该算法下锁定的不仅仅是记录本身,锁定的是区间。
例如下面的记录:
1、2、3、4、5、7、8
若要插入6这个记录,首先根据查询模式PAGE_CUR_LE定位到记录5,接着判断5这条记录的下一条记录是否有锁,因为如果有锁,则根据next-key locking算法,其表示锁定的范围是:(5,7]或者是(5,7)(gap标志位为1)。因此若记录7上有锁,则不允许在这个范围内进行插入操作。所以插入记录6的操作将被阻塞。对于InnoDB存储引擎而言,若记录next_rec上没有锁,则直接插入,不产生任何的锁对象。否则调用函数lock_rec_enqueue_waiting,等待记录next_rec上锁的释放,这时会产生锁的对象,锁定的记录为next_rec,锁的类型为LOCK_X|LOCK_GAP。
此外,若下一条记录next_rec上有锁,不管持有该锁是否为插入操作事务本身,当插入操作完成后(无需事务提交),需要调用函数lock_update_insert来更新锁定的范围。例如上面的例子,若插入了6这条记录,则原来锁定的范围从(5,7]更新为了(5,6),(6,7]。这样就阻止了其他事物在(5,6)的范围内进行插入操作。
还需要注意的是,若插入的表上有辅助索引,那么还需要对辅助索引记录进行锁的判断,其方法与步骤2、步骤3相同。只是在判断可以进行插入后,还需要更新辅助索引页page header中PAGE_MAX_TRX_ID的值。
函数lock_rec_insert_check_and_lock用来判断next_rec上的锁,参数inherit用来判断是否在插入完成后调用函数lock_update_insert来对已经锁定的范围进行更新。

对于上面的描述,做以下几组测试:

1、插入的rec列中没有索引

CREATE TABLE `test1` (
`ID` bigint(20) NOT NULL AUTO_INCREMENT,
`c1` int(10) DEFAULT NULL,
PRIMARY KEY (`ID`)
) ## SESSION 1
mysql> begin;
Query OK, 0 rows affected (0.00 sec) mysql> select * from test1 where id between 1 and 3 for update ; ## 条件使用c1会锁住全部数据以及全部间隙,所以使用主键上锁
+----+------+
| ID | c1 |
+----+------+
| 1 | 1 |
| 2 | 2 |
| 3 | 10 |
| 4 | 11 |
+----+------+
4 rows in set (0.01 sec) ## SESSION 2
mysql> begin;
Query OK, 0 rows affected (0.00 sec) mysql> insert into test1(c1) values (9);
Query OK, 1 row affected (0.00 sec) mysql> select * from test1;
+----+------+
| ID | c1 |
+----+------+
| 1 | 1 |
| 2 | 2 |
| 3 | 10 |
| 4 | 11 |
| 5 | 9 |
+----+------+
7 rows in set (0.00 sec) ## SESSION 1/2
mysql> commit; ### 上面这个情况和之前的理解有出入,因为之前认为作为聚集索引的主键区域(或next-rec)被锁住会影响到对应的数据页,但实际上不是。
从上面的测试看,插入的rec列中如果没有索引,不受next_rec的主键(辅助索引)上锁影响(除next-key locking将自增字段的最大值区域锁住是LOCK_X和LOCK_GAP!)。
“对于InnoDB存储引擎而言,若记录next_rec上没有锁,则直接插入,不产生任何的锁对象。”测试结果next-rec和间隙都不会有影响

2、插入的rec中有普通索引

若下一条记录next_rec上有锁,不管持有该锁是否为插入操作事务本身,当插入操作完成后(无需事务提交),需要调用函数lock_update_insert来更新锁定的范围。例如上面的例子,若插入了6这条记录,则原来锁定的范围从(5,7]更新为了(5,6),(6,7]。这样就阻止了其他事物在(5,6)的范围内进行插入操作。
### 但是测试并不是书中描述的样子
mysql> alter table test1 add index idx_c1(c1);

## SESSION 1
mysql> select * from test1 where c1=9 for update ;
## SESSION 2
mysql> insert into test1(c1) values (8);
## SESSION 3
mysql> insert into test1(c1) values (7);
按书中描述,SESSION2会被SESSION1阻塞,SESSION3会被SESSION2阻塞,但实际测试SESSION1释放锁以后,SESSION2和3同时插入完成。
另外不受next_rec的主键上的锁影响。
还需要注意的是,若插入的表上有辅助索引,那么还需要对辅助索引记录进行锁的判断,其方法与步骤2、步骤3相同。只是在判断可以进行插入后,还需要更新辅助索引页page header中PAGE_MAX_TRX_ID的值。

姜承尧《MySQL技术内幕.InnoDB存储引擎》中对于自增长字段与锁的说明(第一版6.2.4,第二版6.3.4):

自增长在数据库中是非常常见的一种属性,也是很多DBA或开发人员首选的主键方式。在InnoDB存储引擎的内存结构中,对每个含有自增长值的表都有一个自增长计数器(auto-increment counter)。当对含有自增长计数器的表进行插入时,这个计数器会被初始化,会执行如下的语句来得到计数器的值:
”SELECT MAX(auto_inc_col) FROM t FOR UPDATE;”
插入操作会更具这个自增长的计数器值加1赋予自增长列,这个实现方式叫做AUTO-INC Locking。这种锁其实是采用一种特殊的表锁机制,为了提高插入的性能,锁不是在一个事务完成后才释放,而是在完成对自增长值插入的SQL语句后立即释放。
虽然AUTO-INC Locking从一定程度上提高了并发插入的效率,但这里还是存在一些问题。首先,对于有自增长值的列的并发插入性能较差,所以必须等待前一个插入的完成(虽然不用等待事务的完成)。其次,对于INSERT...SELECT的大数据量的插入,会影响插入的性能,因为另一个事务中的插入会被阻塞。
从MySQL 5.1.22版本开始,InnoDB存储引擎提供了一种轻量级互斥量的自增长实现机制,这种机制大大提高了自增长值插入的性能。并且从MySQL 5.1.22开始,InnoDB存储引擎提供了一个参数 innodb_autoinc_lock_mode,默认值为1。在继续讨论新的自增长实现方式之前,我们需要对自增长的插入进行分类:
~INSERT-like:INSERT-like指所有的插入语句,如INSERT、REPLACE、INSERT...SELECT、RELPACE...SELECT、LOAD DATA等
~Simple inserts:Simple inserts指能在插入前就确定插入行数的语句。这些语句包括INSERT、REPLACE等。需要注意的是:Simple inserts不包含INSERT...ON DUPLICATE KEY UPDATE这类SQL语句。
~Bulk inserts:Bulk inserts指在插入前不能确定得到插入行数的语句,如INSERT...SELECT,REPLACE...SELECT,LOAD DATA。
~Mixed-mode inserts:Mixed-mode inserts指插入中有一部分的值时自增长的。有一部分是确定的,如:INSERT INTO t1(c1,c2) VALUES (1,'a'),(NULL,'b'),(5,'c'),(NULL,'d'),也可以是指INSERT...ON DUPLICATE KEY UPDATE这类SQL语句。
参数innodb_autoinc_lock_mode有三个可选值,无法动态修改:
##innodb_autoinc_lock_mode=0 这是5.1.22版本之前自增长的实现方式,即通过表锁的AUTO-INC Locking方式。因为有了新的自增长实现方式,所以0这个选项不应该是你的首选项。
##innodb_autoinc_lock_mode=1 这是该参数的默认值。对于“SIMPLE INSERT”,该值会用互斥量(mutex)去对内存中的计数器进行累加的操作。对于“BULK INSERT”,还是使用传统的表锁的AUTO-INC Locking方式。这样做,如果不考虑回滚操作,对于自增长的增长还是连续的。而且这种方式下,Statement-Based方式的Replication还是能很好的工作,需要注意的是,如果已经使用AUTO-INC Locking的方式产生自增长的值,而这时需要再进行“SIMPLE INSERT”的操作时,还是要等待AUTO-INC Locking的释放。
##innodb_autoinc_lock_mode=2 在这个模式下,对于所有的“INSERT-like”自增长值的产生都是通过互斥量,而不是AUTO-INC Locking的方式。显然,这是最高性能的方式。然而,这会带来一定的问题。因为并发插入的存在,所以每次插入时,自增长的值可能不是连续的。此外,最重要的是,基于Statement-Based Replication会出现问题。因此,使用这个模式,任何时候都应该使用Row-Base Replication。这样才能保证最大的并发性能和Replication数据的同步。
对于自增长另外需要注意的是,InnoDB存储引擎中的实现和MyISAM不同,MyISAM是表锁的,自增长不用考虑并发插入的问题,因此在Master用InnoDB存储引擎,Slave用MyISAM存储引擎的Replication架构下你必须考虑这种情况。
另外,InnoDB存储引擎下,自增长值的列必须是索引,并且是索引的第一列,如果是第二个列则会报错;而MyISAM存储引擎则没有这个问题。

【MySQL】MySQL锁和隔离级别浅析二 之 INSERT的更多相关文章

  1. 【MySQL】MySQL锁和隔离级别浅析一

    <MySQL技术内幕InnoDB存储引擎>第一版中对于MySQL的InnoDB引擎锁进行了部分说明,第二版有部分内容更新. 与MySQL自身MyISAM.MSSQL及其他平台BD锁的对比: ...

  2. 理解MySql的锁&事务隔离级别

    这几篇文章是从网上(http://www.hollischuang.com)看到的一系列文章,也是重温了一下数据库的相关知识.下面是对这些文章的一些前后行文逻辑的说明: 我们知道,在DBMS的多个事业 ...

  3. MySQL学习【第十二篇事务中的锁与隔离级别】

    一.事务中的锁 1.啥是锁? 顾名思义,锁就是锁定的意思 2.锁的作用是什么? 在事务ACID的过程中,‘锁’和‘隔离级别’一起来实现‘I’隔离性的作用 3.锁的种类 共享锁:保证在多事务工作期间,数 ...

  4. MySQL事务隔离级别(二)

    搞清楚MySQL事务隔离级别 首先创建一个表 account.创建表的过程略过(由于 InnoDB 存储引擎支持事务,所以将表的存储引擎设置为 InnoDB).表的结构如下: 为了说明问题,我们打开两 ...

  5. (7)MySQL进阶篇SQL优化(InnoDB锁-事务隔离级别 )

    1.概述 在我们在学习InnoDB锁知识点之前,我觉得有必要让大家了解它的背景知识,因为这样才能让我们更系统地学习好它.InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION ...

  6. 谈谈MySQL支持的事务隔离级别,以及悲观锁和乐观锁的原理和应用场景?

    在日常开发中,尤其是业务开发,少不了利用 Java 对数据库进行基本的增删改查等数据操作,这也是 Java 工程师的必备技能之一.做好数据操作,不仅仅需要对 Java 语言相关框架的掌握,更需要对各种 ...

  7. Mysql数据库事务的隔离级别和锁的实现原理分析

    Mysql数据库事务的隔离级别和锁的实现原理分析 找到大神了:http://blog.csdn.net/tangkund3218/article/details/51753243 InnoDB使用MV ...

  8. 第36讲 谈谈MySQL支持的事务隔离级别,以及悲观锁和乐观锁的原理和应用场景

    在日常开发中,尤其是业务开发,少不了利用 Java 对数据库进行基本的增删改查等数据操作,这也是 Java 工程师的必备技能之一.做好数据操作,不仅仅需要对 Java 语言相关框架的掌握,更需要对各种 ...

  9. 事务,Oracle,MySQL及Spring事务隔离级别

    一.什么是事务: 事务逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败. 二.事务特性(4种): 原子性 (atomicity):强调事务的不可分割:一致性 (consiste ...

随机推荐

  1. JavaScript面向对象编程指南

    引言 面向对象程序设计 基本数据类型.数组.循环及条件表达式 基本数据类型 函数 函数Function 预定义函数 变量的作用域 函数也是数据 闭包 对象 原型 原型 继承 原型链 浅拷贝与深拷贝 原 ...

  2. centos7扩展磁盘空间

    [root@hn ~]# fdisk /dev/sdb The device presents a logical sector size that is smaller thanthe physic ...

  3. Hololens开发笔记之使用Unity开发一个简单的应用

    一.Hololens概述 Hololens有以下特性 1.空间映射借助微软特殊定制的全息处理单元(HPU),HoloLens 实现了对周边环境的快速扫描和空间匹配.这保证了 HoloLens能够准确地 ...

  4. vim使用快捷键

    vim使用快捷键 索引 1. 关于Vim 1.1 Vim的几种模式 2. 启动Vim 3. 文档操作 4. 光标的移动 4.1 基本移动 4.2 翻屏 4.3 标记 5. 插入文本 5.1 基本插入 ...

  5. android实现 服务器功能

    package com.weijia.tests; import java.io.IOException; import java.net.InetSocketAddress; import java ...

  6. PHPStorm+XDebug进行调试图文教程以及解析wamp的php.ini设置不生效的原因

    这篇文章主要为大家详细介绍了PHPStorm+XDebug进行调试图文教程,内容很丰富,具有一定的参考价值,感兴趣的小伙伴们可以参考一下   笔者的开发环境如下:Windows8.1+Apache+P ...

  7. Java多线程之DaemonThreadFactory

    通过DaemonThreadFactory创建后台线程池 另外:如果是后台线程创建的线程,将都是后台线程. package wzh.daemon; import java.util.concurren ...

  8. Python应用02 Python服务器进化

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! **注意,在Python 3.x中,BaseHTTPServer, SimpleH ...

  9. Sql Server Text 类型列 查询和更新

    Text(ntext.image)类型为大数据字段,因为存储方式不同,也决定了其查询和更新不同于一般方法. 1.表定义: 2.查询: Like查询是可用的: select * from dbo.nod ...

  10. Intellisense for Xrm.Page in CRM 2011

    Intellisense for Xrm.Page in CRM 2011 In CRM 2011 javascripts for crm forms can be stored externally ...