desc track_lock;
+
| Field | Type | Null | Key | Default | Extra |
+
| id | varchar(100) | NO | PRI | NULL | |
| status | int(2) | NO | | NULL | |
| create_date | timestamp | YES | | NULL | |
+
验证场景1:三个事务同时插入相同主键的记录。事务1正常提交,事务2,3插入失败,没有死锁问题。
时间线 | 事务1 | 事务2 | 事务3 |
---|
1 | begin; | begin; | begin; |
2 | insert into track_lock (id, status) values ('1','1'); Query OK, 1 row affected (0.01 sec) | | |
3 | | insert into track_lock (id, status) values ('1','1'); 等待 | insert into track_lock (id, status) values ('1','1');等待 |
4 | commit; Query OK, 0 rows affected (0.01 sec) | | |
5 | | ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY' | ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY' |
6 | | commit; | commit; |
验证场景2:三个事务同时插入相同主键的记录。事务1插入后回滚,事务2,3一个插入成功,一个死锁错误。
时间线 | 事务1 | 事务2 | 事务3 |
---|
1 | begin; | begin; | begin; |
2 | insert into track_lock (id, status) values ('1','1'); Query OK, 1 row affected (0.01 sec) | | |
3 | | insert into track_lock (id, status) values ('1','1'); 等待 | insert into track_lock (id, status) values ('1','1');等待 |
4 | rollback; Query OK, 0 rows affected (0.02 sec) | | |
5 | | ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction | Query OK, 1 row affected (8.39 sec) |
6 | | commit; | commit; |
高并发insert会产生死锁的可能,主键索引下插入相同的记录,事务1插入记录尚未提交,事务2,3同时也插入操作,在事务1回滚事务时候会造成事务2,3有一个插入成功,有一个死锁错误。
业务系统设计时候,尽量避免高并发插入相同记录业务,可在数据库之前做过滤。如Java锁,分布式锁。
insert锁机制
这个为什么出现共享锁呢,网上找到这个大牛的文章跟这里的问题是一样的记一次神奇的Mysql死锁排查。但insert操作具体加锁步骤说明,我看后还是不理解。又找到一篇文章Insert into 加锁机制
官方文档对于insert 加锁的描述:
INSERT sets an exclusive lock on the inserted row. This lock is an index-record lock, not a next-key lock (that is, there is no gap lock) and does not prevent other sessions from inserting into the gap before the inserted row.Prior to inserting the row, a type of gap lock called an insertion intention gap lock is set. This lock signals the intent to insert in such a way that multiple transactions inserting into the same index gap need not wait for each other if they are not inserting at the same position within the gap.If a duplicate-key error occurs, a shared lock on the duplicate index record is set. This use of a shared lock can result in deadlock should there be multiple sessions trying to insert the same row if another session already has an exclusive lock.
大体的意思是:insert会对插入成功的行加上排它锁,这个排它锁是个记录锁,而非next-key锁(当然更不是gap锁了),不会阻止其他并发的事务往这条记录之前插入记录。在插入之前,会先在插入记录所在的间隙加上一个插入意向gap锁(简称I锁吧),并发的事务可以对同一个gap加I锁。如果insert 的事务出现了duplicate-key error ,事务会对duplicate index record加共享锁。这个共享锁在并发的情况下是会产生死锁的,比如有两个并发的insert都对要对同一条记录加共享锁,而此时这条记录又被其他事务加上了排它锁,排它锁的事务提交或者回滚后,两个并发的insert操作是会发生死锁的。
还有这边文章说的Insert语句的加锁流程 :
隐式锁主要用在插入场景中。在Insert语句执行过程中,必须检查两种情况,一种是如果记录之间加有间隙锁,为了避免幻读,此时是不能插入记录的,另一中情况如果Insert的记录和已有记录存在唯一键冲突,此时也不能插入记录。除此之外,insert语句的锁都是隐式锁,但跟踪代码发现,insert时并没有调用lock_rec_add_to_queue函数进行加锁, 其实所谓隐式锁就是在Insert过程中不加锁。
只有在特殊情况下,才会将隐式锁转换为显示锁。这个转换动作并不是加隐式锁的线程自发去做的,而是其他存在行数据冲突的线程去做的。例如事务1插入记录且未提交,此时事务2尝试对该记录加锁,那么事务2必须先判断记录上保存的事务id是否活跃,如果活跃则帮助事务1建立一个锁对象,而事务2自身进入等待事务1的状态
以及INSERT 加锁流程
首先对插入的间隙加插入意向锁(Insert Intension Locks)
总结下 用具体3个事务插入相同记录 在主键字段,模拟死锁情况。
事务1,2,3找到相应的gap添加插入意向间隙锁I。并发的事务可以对同一个gap加I锁。为了防止幻读,如果记录之间加有 GAP 锁或 Next-Key 锁,此时不能 INSERT。
事务1插入成功,尚未提交事务。
事务2插入相同记录,出现了duplicate-key error ,事务2会给事务1对duplicate index record加排他锁X。事务2等待共享锁S
事务3插入相同记录,发现有X锁,等待共享锁S
如果事务1提交,则释放X锁。
6. 事务2,事务3同时获得S锁,重新进行唯一性约束检查。
2. 事务2,事务3同时获得S锁,然后发现唯一冲突结束。没有死锁。
如果事务1回滚,则释放X锁。
事务2,事务3同时获得S锁,重新进行唯一性约束检查。
事务2,事务3发现可以插入记录,先去获取X锁。
事务2,事务3各自持有S锁,又同时获取X锁,导致死锁。
事务2,事务3有一个报死锁错误后释放锁,另外一个插入成功。
高并发insert会产生死锁的可能,主键索引下插入相同的记录,事务1插入记录尚未提交,事务2,3同时也插入操作,在事务1回滚事务后,事务2,3有一个死锁错误而释放S锁,从而另外一个事务则可以获取锁成功执行。