浅谈MySQL死锁

死锁是并发系统绕不开的问题,不管是Java并发编程、MySQL并发处理client请求,还是操作系统,都是如此。本篇文章主要记录一下MySQL死锁的原因、检测与预防解决。

MySQL死锁实例

首先在MySQL里,锁可以分为S锁(share共享锁)和X锁(Exclusive排它锁)。这两种锁可以直接理解为读锁(共享锁)和写锁(排它锁)。

加了读锁的记录,不管是本事务还是其他事务都只能读;加了写锁的记录,本事务可以写,其他记录不管是读还是写这条记录都会阻塞,只有等本事务commit了以后,其他想读或写这条记录的事务才能继续执行。简而言之,读锁和读锁不互斥;写锁无论是和读锁,还是和写锁都互斥。

关于MySQL在读提交(Read Commit)隔离级别以及可重复读(Repeatable Read)隔离级别下的行锁加锁规则,可以参考这篇博文,讲的还是挺清楚的。

下面进入正题,讲讲死锁。首先贴一个官方文档里的小例子,很简短也很直截了当的演示了死锁:

CREATE TABLE t (id INT) ENGINE = InnoDB;  # 创建一个表,只有一个int字段id
INSERT INTO t (id) VALUES (1);  # 插入一条记录

# session A
START TRANSACTION;
SELECT * FROM t WHERE id=1 LOCK IN SHARE MODE;  # 给id=1的记录加了读锁

# 此时另一个用户在执行session B:
START TRANSACTION;
DELETE FROM t WHERE id=1;  # 删除id=1的记录需要加上写锁,拿到写锁以后和session A的读锁不相容,阻塞,等待A释放读锁

# 接下来session A继续执行:
DELETE FROM t WHERE id=1;  # 删除id=1的记录需要加上写锁,然而这一行的写锁在session B那里,阻塞

执行完之后MySQL的执行结果是:

ERROR 1213 (40001): Deadlock found when trying to get lock;
try restarting transaction

至此,死锁就产生了,A等待B释放写锁才能继续进行,B等待A释放读锁,两边互相等待,如果没有外力作用,将永远无法继续进行下去。

MySQL死锁原因

  • 并发修改:也就是上面例子里所展示的,两个事务先加读锁,后来先后升级为写锁,会导致死锁。
  • 不同的事务争夺同一批资源,先后申请的顺序颠倒就会产生死锁。比如session1先对记录A加写锁,session2对记录B加写锁;接着session1想对记录B加写锁,而session2想对记录A加写锁,就出现了session1 持有资源A等待资源B,session2 持有资源B等待资源A的情况,如果他们加锁的顺序都是先A再B,就不会发生死锁。
  • 间隙锁:当两个事务都在表里执行当前读(可重复读隔离级别下),在表里加入了间隙锁,接着双方又向对方锁上的间隙里插入数据时,会产生死锁。

死锁检测

  1. 查看死锁日志的SQL命令:show engine innodb status。可以看到每个事务目前持有的锁信息,以及等待的锁信息。
  2. InnoDB有一个参数:innodb_deadlock_detect,将其设置为on(默认就是on),可以自动检测死锁。当系统发生死锁了以后,会选择一个事务进行回滚,释放这个事务所占用的锁,解开这些事务之间形成的首尾依赖的关系。
  3. 还有一个参数:innodb_lock_wait_timeout,表示对锁的超时等待,默认50s,超过这个时间就不等了。带来的问题是,这个时间太短了有可能误杀(明明没发生死锁),太长了浪费时间,性能差。

避免死锁

根据上面讲的死锁原因,可以归纳一下几点:

  1. 在编写应用程序的时候,尽量保证每个线程以相同的(固定的)顺序访问记录、表、索引。
  2. 如果幻读和不可重复读不是不可接受的,考虑将隔离级别降为不可重复读,这样就不会加间隙锁了,降低了发生死锁的可能性。
  3. 设计合理的索引,避免mySQL不走索引、全表扫描。因为对于当前读,MySQL会给所有扫描到的记录加上排它锁,如果走索引的话还好,如果走了全表扫描,那相当于给给全表加了锁,大大降低并发度、使得锁之间的竞争更激烈,也更有可能发生死锁。
  4. 避免长事务,将长事务切分成若干个短事务,长事务长期持有锁,加大了发生死锁的概率。在事务中,对竞争激烈的资源的加锁尽量靠后,因为事务提交的时候才能释放锁,越靠后持有的时间越短。
  5. 设置innodb_lock_wait_timeout和innodb_deadlock_detect参数,及时解决死锁。
  6. 避免同一时间多个脚本对同一个表进行大量读写,最好错开时间。
  7. 尽量一次性给所有需要的资源加锁(类似MyIsam那样),不要陆续加锁。
  8. 对于非常容易发生死锁的业务部分,可以加表锁。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值