我有一个java应用程序,它在下面的sql上发生死锁异常:
insert into voucher (
id,
order_id,
voucher_code
) SELECT
#{id},
#{orderId},
#{voucherCode}
FROM DUAL WHERE NOT EXISTS (SELECT id FROM voucher where order_id = #{orderId})
order_id
是唯一键。
而且我确信当sql在并发上执行时它将会死锁。
但是,我没有足够的权限来执行show engine innodb status
,所以我无法获得有关死锁异常的信息。
我尝试在实验室环境中重现问题。 表测试如下:
Create Table: CREATE TABLE `test` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`info` varchar(128) NOT NULL DEFAULT '',
`order_id` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `uni_order_id` (`order_id`)
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8
我在两个不同的会话中执行两个sqls:
insert into test(info,order_id) select '12345',sleep(10) from dual where not exists (select info from test where info='12345');
insert into test(info,order_id) select '12345',234 from dual where not exists (select info from test where info='12345');
死锁日志如下:
------------------------
LATEST DETECTED DEADLOCK
------------------------
2016-06-20 17:26:54 700000a83000
*** (1) TRANSACTION:
TRANSACTION 2321, ACTIVE 9 sec inserting
mysql tables in use 2, locked 2
LOCK WAIT 5 lock struct(s), heap size 1184, 2 row lock(s)
MySQL thread id 1, OS thread handle 0x700000a3f000, query id 29 localhost root executing
insert into test(info,order_id) select '12345',234 from dual where not exists (select info from test where info='12345')
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 9 page no 3 n bits 72 index `PRIMARY` of table `test`.`test` trx id 2321 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
*** (2) TRANSACTION:
TRANSACTION 2320, ACTIVE 10 sec setting auto-inc lock
mysql tables in use 2, locked 2
3 lock struct(s), heap size 360, 1 row lock(s)
MySQL thread id 2, OS thread handle 0x700000a83000, query id 28 localhost root User sleep
insert into test(info,order_id) select '12345',sleep(10) from dual where not exists (select info from test where info='12345')
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 9 page no 3 n bits 72 index `PRIMARY` of table `test`.`test` trx id 2320 lock mode S
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
TABLE LOCK table `test`.`test` trx id 2320 lock mode AUTO-INC waiting
*** WE ROLL BACK TRANSACTION (2)
我认为子查询select info from test where info=‘12345’
可以持有SLock,insert into … select
想要XLock。
但我没有找到支持我的观点的官方文件。
所以我的问题如下:
1.我的复制品设计对吗?
2.是我的supposal(子查询select info from test where info=‘12345’
可能持有SLock)对吗?任何官方文件都可以支持我的支持吗?
答案 0 :(得分:1)
运行INSERT ... SELECT
时,默认情况下,MySQL会锁定SELECT中的所有行。
如果将隔离级别更改为READ-COMMITTED,则SELECT
中的行不会被锁定。在你的情况下,这应该解决死锁问题。
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
PS:我建议阅读有关隔离级别并了解它们之间的差异。
http://dev.mysql.com/doc/refman/5.7/en/innodb-locks-set.html
INSERT INTO T SELECT ... FROM S WHERE ...设置一个独占索引 记录锁定(没有间隙锁定)插入T的每一行。如果 事务隔离级别是READ COMMITTED,或 启用innodb_locks_unsafe_for_binlog和事务 隔离级别不是SERIALIZABLE,InnoDB在S上进行搜索 一致读(无锁)。否则,InnoDB设置共享的下一个密钥 锁定来自S.的InnoDB必须在后一种情况下设置锁定:In 从备份前滚恢复,每个SQL语句必须是 以与原先完成相同的方式执行。
CREATE TABLE ... SELECT ...使用共享的下一个键执行SELECT INSERT ... SELECT。
在构造中使用SELECT时REPLACE INTO t SELECT ... FROM s WHERE ...或UPDATE t ... WHERE col IN(SELECT ... FROM s ...), InnoDB在表的行上设置共享的下一键锁。
答案 1 :(得分:0)
你工作太辛苦了。我认为做同样的事情:
[.]txt
如果orderId已经在表格中,则INSERT IGNORE INTO voucher
( id, order_id, voucher_code )
VALUES
( #{id}, #{orderId}, #{voucherCode} )
键会投诉,但UNIQUE
会使其静音。
但是......你还有另外一个问题......你的查询(和我的)检查IGNORE
是否有重复。也就是说,id
可以有两件事,但你只检查一件。
通常,人们只会遗漏UNIQUE
:
id
但是,您可能希望在插入发生时获得新的INSERT IGNORE INTO voucher
( order_id, voucher_code )
VALUES
( #{orderId}, #{voucherCode} )
?所以......
id
另一方面,如果你真的想要使用两个唯一键,请考虑这个混乱的陈述:
INSERT INTO voucher
( order_id, voucher_code )
VALUES
( #{orderId}, #{voucherCode} )
ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id);
SELECT LAST_INSERT_ID();
那将
REPLACE INTO voucher
( id, order_id, voucher_code )
VALUES
( #{id}, #{orderId}, #{voucherCode} )
符合 唯一键(id或order_id)的任何行DELETE
值。 (由于您指定了INSERT
,id
无效。)现在,让我们更进一步。为什么有两个独特的键?你不能完全摆脱AUTO_INCREMENT
吗?然后将id
设为order_id
?
现在,您已经简化了架构,简化了查询,并且很可能消除了死锁。