Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

find mysql innodb deadlock reason on INSERT INTO SELECT

I have a java application, and it occurs dead lock exception on the sql below:

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})

The order_id is unique key. And I am sure that it will be deadlocked when the sql is executed on concurrency. However, I dont have permission enough to execute show engine innodb status, so I fail to get information about the deadlock exception.

I try to reproduce the problem in a lab environment. table test is below:

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

and I execute two sqls in two different sessions:

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'); 

deadlock log is below:

------------------------
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)

I supposed that the subquery select info from test where info=‘12345’ may hold SLock, and insert into … select want XLock. But I fail to find the offical document to support my point.

So my question is below:
1. is my reproduce design right?
2. is my supposal(the subquery select info from test where info=‘12345’ may hold SLock) right? Any offical document can support my supposal?

like image 523
FatGhosta Avatar asked Oct 20 '25 04:10

FatGhosta


1 Answers

When you run INSERT ... SELECT, by default MySQL is locking all rows in SELECT. If you change isolation level to READ-COMMITTED, then rows in SELECT is not locked. In your case this should solve deadlock problem.

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

PS:I suggest to read about isolation levels and understand differences between them.

http://dev.mysql.com/doc/refman/5.7/en/innodb-locks-set.html

INSERT INTO T SELECT ... FROM S WHERE ... sets an exclusive index record lock (without a gap lock) on each row inserted into T. If the transaction isolation level is READ COMMITTED, or innodb_locks_unsafe_for_binlog is enabled and the transaction isolation level is not SERIALIZABLE, InnoDB does the search on S as a consistent read (no locks). Otherwise, InnoDB sets shared next-key locks on rows from S. InnoDB has to set locks in the latter case: In roll-forward recovery from a backup, every SQL statement must be executed in exactly the same way it was done originally.

CREATE TABLE ... SELECT ... performs the SELECT with shared next-key locks or as a consistent read, as for INSERT ... SELECT.

When a SELECT is used in the constructs REPLACE INTO t SELECT ... FROM s WHERE ... or UPDATE t ... WHERE col IN (SELECT ... FROM s ...), InnoDB sets shared next-key locks on rows from table s.

like image 122
Pavel Katiushyn Avatar answered Oct 21 '25 17:10

Pavel Katiushyn