select ... for update
语句查询订单是否存在,如果不存在才插入订单记录。死锁的发生
CREATE TABLE `t_order` (
`id` int NOT NULL AUTO_INCREMENT,
`order_no` int DEFAULT NULL,
`create_date` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_order` (`order_no`) USING BTREE
) ENGINE=InnoDB ;
t_order
表里现在已经有了 6 条记录:select ... for update
语句,目的为了防止事务执行的过程中,有其他事务插入了记录,而出现幻读的问题。select ... for update
语句,而使用了单纯的 select 语句,如果是两个订单号一样的请求同时进来,就会出现两个重复的订单,有可能出现幻读,如下图:为什么会产生死锁?
Record Loc,记录锁,锁的是记录本身; Gap Lock,间隙锁,锁的就是两个值之间的空隙,以防止其他事务在这个空隙间插入新的数据,从而避免幻读现象。
begin;
//对读取的记录加共享锁
select ... lock in share mode;
commit; //锁释放
begin;
//对读取的记录加排他锁
select ... for update;
commit; //锁释放
(2, +∞]
范围的记录,然后期间如果有其他事务在这个锁住的范围插入数据就会被阻塞。select id from t_order where order_no = 1008 for update;
(1006, +∞)
。那么,当事务 B 往间隙锁里插入 id = 1008 的记录就会被锁住。insert into t_order (order_no, create_date) values (1008, now());
select ... for update
语句并不会相互影响。select ... for update
语句后都持有范围为(1006,+∞)
的间隙锁,而接下来的插入操作为了获取到插入意向锁,都在等待对方事务的间隙锁释放,于是就造成了循环等待,导致死锁。如何避免死锁?
-
设置事务等待锁的超时时间。当一个事务的等待时间超过该值后,就对这个事务进行回滚,于是锁就释放了,另一个事务就可以继续执行了。在 InnoDB 中,参数 innodb_lock_wait_timeout
是用来设置超时时间的,默认值时 50 秒。当发生超时后,就出现下面这个提示:
-
开启主动死锁检测。主动死锁检测在发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect
设置为 on,表示开启这个逻辑,默认就开启。当检测到死锁后,就会出现下面这个提示: