Waiting Auto-INC LOCK导致死锁

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介:

今天下午在看死锁相关的文档,到线上查看一生产数据库的时候,正好发现了show engine innodb status有一个死锁的信息:

LATEST DETECTED DEADLOCK
------------------------
120626 20:00:30
*** (1) TRANSACTION:
TRANSACTION 3 3052385643, ACTIVE 0 sec, process no 3898, OS thread id 1356507456 inserting
mysql TABLES IN USE 1, locked 1
LOCK WAIT 16 LOCK struct(s), heap SIZE 3024, undo log entries 56
MySQL thread id 32282264, query id 9170497209 172.24.52.77 product_db UPDATE
INSERT INTO                     occur_dead_lock_table(xx_id1,xx_id2,xx_cloumn1,STATUS,quantity,xxx_id3,price,gmt_create,gmt_modified) VALUES(19273026495,10378,0x313632373230373A36303039323B32303530333A33323637393435,1,1902,723417070,5600,now(),now())
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS SPACE id 879 page no 241 n bits 808 INDEX `ind_occur_dead_lock_table` OF TABLE `product_db/occur_dead_lock_table` trx id 3 3052385643 lock_m
ode X locks gap BEFORE rec INSERT intention waiting
Record LOCK, heap no 291 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len 8; hex 800000000000288b; ASC       ( ;; 1: len 8; hex 80000000000c400d; ASC       @ ;;
 
*** (2) TRANSACTION:
TRANSACTION 3 3052385652, ACTIVE 0 sec, process no 3898, OS thread id 1663498560 setting auto-inc LOCK
mysql TABLES IN USE 1, locked 1
15 LOCK struct(s), heap SIZE 3024, undo log entries 43
MySQL thread id 32231290, query id 9170497216 172.24.36.165 product_db UPDATE
INSERT INTO         occur_dead_lock_table(xx_id1,xx_id2,xx_cloumn1,STATUS,quantity,xxx_id3,price,gmt_create,gmt_modified) VALUES(19400057961,10379,0x313632373230373A333233323438333B32303530333A3235303333333336,1,19713,723417070,5600,now(),now())
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS SPACE id 879 page no 241 n bits 808 INDEX `ind_occur_dead_lock_table` OF TABLE `product_db/occur_dead_lock_table` trx id 3 3052385652 lock_m
ode X
Record LOCK, heap no 251 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len 8; hex 800000000000288b; ASC       ( ;; 1: len 8; hex 80000000000c4028; ASC       @(;;
......省略部分内容。。。。。
 
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
TABLE LOCK TABLE `product_db/occur_dead_lock_table` trx id 3 3052385652 LOCK mode AUTO-INC waiting
*** WE ROLL BACK TRANSACTION (2)

我们首先来分析一下LATEST DETECTED DEADLOCK的信息中,出现死锁的表为occur_dead_lock_table,数据库版本:

$mysql -V
mysql Ver 14.12 Distrib 5.0.81, for unknown-linux-gnu (x86_64) using EditLine wrapper

表结构:

CREATE TABLE occur_dead_lock_table (
id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
xx_id1 BIGINT(20) NOT NULL,
xx_id2 BIGINT(20) NOT NULL,
xx_cloumn1 VARCHAR(512) NOT,
STATUS tinyint(4) NOT NULL ,
quantity INT(11) NOT NULL ,
xxx_id3 BIGINT(20) NOT NULL ,
price BIGINT(20) NOT NULL ,
gmt_create datetime NOT NULL COMMENT '记录创建时间',
gmt_modified datetime NOT NULL COMMENT '记录最后修改时间',
PRIMARY KEY (id),
KEY ind_occur_dead_lock_table (xx_id2)
) ENGINE=InnoDB AUTO_INCREMENT=842353 DEFAULT CHARSET=gbk ;

死锁出现的场景:
(一).通常发出死锁的时候,两个事务通常都会形成一个‘圈’的逻辑,事务1持有事务2所需要的锁,同时事务2又持有事务1所需要的锁,这样就造成了死锁,这是非常常见的一种死锁;

(二).还有一种情况是负责mysql死锁检测的是一个递归函数lock_deadlock_recursive(),当递归的深度depth超过LOCK_MAX_DEPTH_IN_DEADLOCK_CHECK和cost超过LOCK_MAX_N_STEPS_IN_DEADLOCK_CHECK两个内部变量的时候,mysql就会抛出死锁信息;

从show innodb status中dead lock的信息来看:

(1).transaction1显示了正在向occur_dead_lock_table中插入一行数据,它正在等待获得表上的索引ind_occur_dead_lock_table的一个x锁(lock_mode X locks gap before rec insert intention waiting);
(2).transaction2的信息包括两部分:已经持有的锁和正在等待的锁;已持有的锁为一个向occur_dead_lock_table表中插入记录的时候,在其索引上获得的X锁;正在等待的锁为在表occur_dead_lock_table上的一个AUTO-INC锁;

上面的一个信息中很重要的一个锁等待为事务2在等待auto-inc锁,下面我们来分析一下在mysql中auto-increment列的实现:

5.0(5.1.22以下)和5.1(5.1.22以上)在自增列上的实现方式:

a.在5.0(5.1.22以下),auto_increment的实现机制为在innodb存储引擎的内存结构中维护一个自增长计数器,该计数器的值由:
Select max(auto_inc_col) from t for update得到,插入操作首先从这个计数器中得到值,然后赋予自增长列(auto_inc locking),可以看到这个锁本质上是一种表锁,那么其缺点就是必须等待前一个插入完成后,这样在大并发下,其插入性能的并发性不然较差;
b.在5.1(5.1.22以上)中增加了另外一种实现机制,在innodb引擎中提供了一种轻量级互斥量(mutex)的自动增长机制,对于普通的insert 操作,innodb用一个mutex去对内存中的计数器进行累加,去掉了对原表的表锁机制,无疑会对部分插入提高较大的性能,该机制在5.1(5.1.22以上)中为自增长值实现的默认方式;

从上面可以看到,在mysql 5.0(5.1.22以下)中自增的id的实现采用的是table lock的方式,这样无疑是加大了死锁出现的概率,该bug已经在5.1.22中修复,新加入了参数就innodb_autoinc_lock_mode(默认为innodb_autoinc_lock_mode = 1 ):

There is a new innodb_autoinc_lock_mode system variable to configure the locking behavior thatInnoDB uses for generating auto-increment values. The default behavior now is slightly different from before,which involves a minor incompatibility for multiple-row inserts that specify anexplicit value for the auto-increment column in some but not all rows.

同时在大并发插入的情况,还会抛出dead lock的(TOO DEEP OR LONG SEARCH IN THE LOCK TABLE WAITS-FOR GRAPH—场景2),该bug已经修复:

“SHOW INNODB STATUS deadlock info incorrect when deadlock detection aborts”. Print the correctlock owner when recursive function lock_deadlock_recursive() exceeds its maximum depth LOCK_MAX_DEPTH_IN_DEADLOCK_CHECK.

综上所述,将版本从5.0升级到5.1的版本(5.1.48或者5.1.61)是不错的选择,同时在业务上可以考虑选择由应用程序产生了一个序列值来插入到数据库中,代替数据库的auto_incement。

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
锁、C#中Monitor和Lock以及区别
1.Monitor.Enter(object)方法是获取锁,Monitor.Exit(object)方法是释放锁,这就是Monitor最常用的两个方法,当然在使用过程中为了避免获取锁之后因为异常,致锁无法释放,所以需要在try{} catch(){}之后的finally{}结构体中释放锁(Monitor.Exit())。
2500 0
|
Oracle 关系型数据库 数据库
innodb_lock_wait_timeout参数的了解
前言:在管理ORACLE的工作中,经常发现因为锁等待的原因导致应用宕机了。Mysql考虑到自身的性能和架构等因素,InnoDB数据库引擎增加了参数innodb_lock_wait_timeout,避免在资源有限的情况下产生太多的锁等待; 一、innodb_...
3008 0
|
SQL 存储 关系型数据库
Mysql事物锁等待超时 Lock wait timeout exceeded; try restarting transaction
Mysql事物锁等待超时 Lock wait timeout exceeded; try restarting transaction
607 0
|
关系型数据库 数据库 索引
|
Java API