Mysql锁专题四、Mysql死锁问题

对该博客的理解 http://www.cnblogs.com/sessionbest/articles/8689082.html

1)死锁例子
MyISAM是deadlock free的。因为MyISAM总是一次获得所需的全部锁(包括读和写,并且后续不能再申请其他锁),要么全部满足,要么等待,因此不会出现死锁。但是在InnoDB中,除单个SQL语句组成的事务外,锁是逐步获得的,这就决定了在InnoDB中是有可能发生死锁的。

表20-17 InnoDB存储引擎中的死锁例子
在这里插入图片描述
上述的操作中,线程A先获得表1记录1的排他锁;线程B获得表2记录1的排他锁。然后线程A又去尝试获得表2记录1的排他锁,因为该锁已被占用,故陷入等待;此时线程B又去尝试获取表1记录1的排他锁,这样线程A和B都各自持有一个排他锁,而且又都想要获取对方持有的那个排他锁,因此也就发生死锁(循环等待)。

发生死锁后,InnoDB一般都能自动检测到,并使一个事务释放锁并回退,另一个事务获得锁,继续完成事务。但在涉及外部锁,或涉及表锁的情况下,InnoDB并不能完全自动检测到死锁,这需要通过设置锁等待超时参数 innodb_lock_wait_timeout来解决。需要说明的是,这个参数并不是只用来解决死锁问题,在并发访问比较高的情况下,如果大量事务因无法立即获得所需的锁而挂起,会占用大量计算机资源,造成严重性能问题,甚至拖跨数据库。我们通过设置合适的锁等待超时阈值,可以避免这种情况发生。

2)避免死锁的方法
通常来说,死锁都是应用设计的问题,通过调整业务流程、数据库对象设计、事务大小,以及访问数据库的SQL语句,绝大部分死锁都可以避免。下面就通过实例来介绍几种避免死锁的常用方法。
(1)在应用中,如果不同的程序会并发存取多个表,应尽量约定以相同的顺序来访问表(比如两个账户相互转账的情况,都约定拿A账户,再拿B账户的锁,就不容易死锁。如果一个先拿A再拿B,另一个先拿B再拿A,就容易死锁),这样可以大大降低产生死锁的机会。在下面的例子中,由于两个session访问两个表的顺序不同,发生死锁的机会就非常高!但如果以相同的顺序来访问,死锁就可以避免。
(2)在程序以批量方式处理数据的时候,如果事先对数据排序,保证每个线程按固定的顺序来处理记录,也可以大大降低出现死锁的可能。
(3)在事务中,如果要更新记录,应该直接申请足够级别的锁,即排他锁,而不应先申请共享锁,更新时再申请排他锁,因为当用户申请排他锁时,其他事务可能又已经获得了相同记录的共享锁,从而造成锁冲突,甚至死锁。具体演示可参见20.3.3小节中的例子。
(4)前面讲过,在REPEATABLE-READ隔离级别下,如果两个线程同时对相同条件记录用SELECT…FOR UPDATE加排他锁,在没有符合该条件记录情况下,两个线程都会加锁成功。程序发现记录尚不存在,就试图插入一条新记录,如果两个线程都这么做,就会出现死锁。这种情况下,将隔离级别改成READ COMMITTED,就可避免问题

3)死锁的排查
尽管通过上面介绍的设计和SQL优化等措施,可以大大减少死锁,但死锁很难完全避免。因此,在程序设计中总是捕获并处理死锁异常是一个很好的编程习惯。
如果出现死锁,可以用SHOW ENGINE INNODB STATUS命令来确定最后一个死锁产生的原因。返回结果中包括死锁相关事务的详细信息,如引发死锁的SQL语句,事务已经获得的锁,正在等待什么锁,以及被回滚的事务等。据此可以分析死锁产生的原因和改进措施。下面是一段样例:
在这里插入图片描述
这个可以看到两个事物产生死锁的语句。实际上,上面体现的是表20-7 InnoDB存储引擎的共享锁例子,先占用共享锁,再用排他锁很容易产生死锁。

猜你喜欢

转载自blog.csdn.net/xiaohesdu/article/details/87971938