【MySQL】MySQL锁和隔离级别浅析二 之 INSERT
最近在整理线上性能时,发现一台线上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的更多相关文章
- 【MySQL】MySQL锁和隔离级别浅析一
<MySQL技术内幕InnoDB存储引擎>第一版中对于MySQL的InnoDB引擎锁进行了部分说明,第二版有部分内容更新. 与MySQL自身MyISAM.MSSQL及其他平台BD锁的对比: ...
- 理解MySql的锁&事务隔离级别
这几篇文章是从网上(http://www.hollischuang.com)看到的一系列文章,也是重温了一下数据库的相关知识.下面是对这些文章的一些前后行文逻辑的说明: 我们知道,在DBMS的多个事业 ...
- MySQL学习【第十二篇事务中的锁与隔离级别】
一.事务中的锁 1.啥是锁? 顾名思义,锁就是锁定的意思 2.锁的作用是什么? 在事务ACID的过程中,‘锁’和‘隔离级别’一起来实现‘I’隔离性的作用 3.锁的种类 共享锁:保证在多事务工作期间,数 ...
- MySQL事务隔离级别(二)
搞清楚MySQL事务隔离级别 首先创建一个表 account.创建表的过程略过(由于 InnoDB 存储引擎支持事务,所以将表的存储引擎设置为 InnoDB).表的结构如下: 为了说明问题,我们打开两 ...
- (7)MySQL进阶篇SQL优化(InnoDB锁-事务隔离级别 )
1.概述 在我们在学习InnoDB锁知识点之前,我觉得有必要让大家了解它的背景知识,因为这样才能让我们更系统地学习好它.InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION ...
- 谈谈MySQL支持的事务隔离级别,以及悲观锁和乐观锁的原理和应用场景?
在日常开发中,尤其是业务开发,少不了利用 Java 对数据库进行基本的增删改查等数据操作,这也是 Java 工程师的必备技能之一.做好数据操作,不仅仅需要对 Java 语言相关框架的掌握,更需要对各种 ...
- Mysql数据库事务的隔离级别和锁的实现原理分析
Mysql数据库事务的隔离级别和锁的实现原理分析 找到大神了:http://blog.csdn.net/tangkund3218/article/details/51753243 InnoDB使用MV ...
- 第36讲 谈谈MySQL支持的事务隔离级别,以及悲观锁和乐观锁的原理和应用场景
在日常开发中,尤其是业务开发,少不了利用 Java 对数据库进行基本的增删改查等数据操作,这也是 Java 工程师的必备技能之一.做好数据操作,不仅仅需要对 Java 语言相关框架的掌握,更需要对各种 ...
- 事务,Oracle,MySQL及Spring事务隔离级别
一.什么是事务: 事务逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败. 二.事务特性(4种): 原子性 (atomicity):强调事务的不可分割:一致性 (consiste ...
随机推荐
- JavaScript面向对象编程指南
引言 面向对象程序设计 基本数据类型.数组.循环及条件表达式 基本数据类型 函数 函数Function 预定义函数 变量的作用域 函数也是数据 闭包 对象 原型 原型 继承 原型链 浅拷贝与深拷贝 原 ...
- centos7扩展磁盘空间
[root@hn ~]# fdisk /dev/sdb The device presents a logical sector size that is smaller thanthe physic ...
- Hololens开发笔记之使用Unity开发一个简单的应用
一.Hololens概述 Hololens有以下特性 1.空间映射借助微软特殊定制的全息处理单元(HPU),HoloLens 实现了对周边环境的快速扫描和空间匹配.这保证了 HoloLens能够准确地 ...
- vim使用快捷键
vim使用快捷键 索引 1. 关于Vim 1.1 Vim的几种模式 2. 启动Vim 3. 文档操作 4. 光标的移动 4.1 基本移动 4.2 翻屏 4.3 标记 5. 插入文本 5.1 基本插入 ...
- android实现 服务器功能
package com.weijia.tests; import java.io.IOException; import java.net.InetSocketAddress; import java ...
- PHPStorm+XDebug进行调试图文教程以及解析wamp的php.ini设置不生效的原因
这篇文章主要为大家详细介绍了PHPStorm+XDebug进行调试图文教程,内容很丰富,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 笔者的开发环境如下:Windows8.1+Apache+P ...
- Java多线程之DaemonThreadFactory
通过DaemonThreadFactory创建后台线程池 另外:如果是后台线程创建的线程,将都是后台线程. package wzh.daemon; import java.util.concurren ...
- Python应用02 Python服务器进化
作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! **注意,在Python 3.x中,BaseHTTPServer, SimpleH ...
- Sql Server Text 类型列 查询和更新
Text(ntext.image)类型为大数据字段,因为存储方式不同,也决定了其查询和更新不同于一般方法. 1.表定义: 2.查询: Like查询是可用的: select * from dbo.nod ...
- 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 ...