Mysql-死锁

死锁

死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致的恶性循环现象。

MyISAM总是一次获得所需的全部锁,要么全满足,要么等待,因此不会出现死锁。但在Innodb中,除单个SQL组成的事务外,锁是逐步获得的,这就决定了Innodb中发生死锁是可能的。

死锁出现典型场景

两个事务都需要获得对方持有的排他锁才能继续完成事务。 where id = 1 for update。一个事务先A表,加锁,再B表在加锁,另个事务先B表加锁,在A表加锁 发生死锁后

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

事务中死锁代码

// 事务1
START TRANSACTION;
UPDATE TABLE DemoA SET NAEME = xxx WHERE ID = 1;
UPDATE TABLE DemoB SET NAEME = xxx WHERE ID = 2;
COMMIT;
// 事务2
START TRANSACTION;
UPDATE TABLE DemoA SET MOUT = xxx WHERE NAME = 2;
UPDATE TABLE DemoB SET MOUT = xxx WHERE NAME = 1;
COMMIT;

 

· 表DemoA、DemoB的ID是主键、NAME是普通索引

· 假如恰好两个事务同时执行,以ID1、2的记录去更新名称,ID是主键索引(特殊索引),此时会先锁定主键索引,然后去更新名称,名称在以上表中处于普通索引,同时也会锁定NAME的普通索引(因为要保持主键索引的叶子节点数据跟普通索引的节点名称数据保持一致)。事务2在做更新时,是根据NAME普通索引去更新MOUT字段。事务2会先锁定NAME普通索引,由于要确定更新的行数,同时会索引NAME普通索引对应的主键索引(普通索引叶子节点存放的是主键ID,然后根据ID回表查询更新)。由于两个事务此时都会请求锁定对方占用的资源,但此时两个事务都无法释放资源,由此陷入死锁

· 为了避免死锁,事务2可以优化成“SELECT ID FROM DemoB WHERE NAME = 2”;即先查询出来对应的ID主键,然后通过主键去更新相应的记录即可(这样的好处在于:只锁定了主键ID索引,而避免NAME普通索引同时锁定)。更新操作时,如果更新非主键索引的记录,建议先通过条件查找对应的主键ID,然后再根据主键ID去更新记录,避免死锁

下面几个实例介绍几种避免死锁的常用方法:

  1. 不同程序会并发存取多个表,应尽量约定以相同的顺序访问,这样子可以大大降低产生死锁的机会。事务1:查询A表加锁,插入B表,事务2:插入B表,查询A表加锁。
  2. 程序以批量处理数据的时候,事先对数据排序,保证每个线程按固定的顺序来处理记录,也可以大大降低出现死锁。事务1:查询A表 where id = 1,where id = 3,事务2:查询A表 where id = 3 ,where id = 1。
  3. 在事务中,如果要更新记录,应该直接申请足够级别的锁,即排他锁。不应先申请共享锁,更新时在申请排他锁。
  4. 在REPEATABLE-READ隔离级别下,如果两个线程同时对相同条件记录用了排他锁,在没有复合该条件记录的情况下,两个线程都会加锁成功,程序发现记录尚不存在,就试图插入一条记录,如果两个线程都这么做,就会出现死锁,这种情况下,将隔离级别改成READ COMMITED就可避免问题(设置为READ COMMIT是,两个线程都会执行排他锁,判断记录是否凑整,如果没有插入,此时只有一个线程插入成功,另一个线程出现锁等待,当第一个线程插入成功后,第二个线程会出现主键重复错误,这个错误虽然是线程错误,但是却获得一个排他锁,如果这个时候第三个线程去申请排他锁,则会出现死锁,因为第二个线程未解锁,此时需要在程序捕获第二个线程插入的主键冲突的错误,回滚事务并释放锁,这样子第三个线程则可以成功申请排他锁)

1、3的情况是因为,防止事务1在处理程序的过程中,已经在处理第n的程序的时候,才发现正要处理的数据已经加锁,冲突,导致后续的程序无法处理,两个事务都卡顿了,如果按照顺序,这个时候,在刚处理的时候已经发现锁以被占用,则不需要花更多的时间去处理可能需要回滚的程序,可以提前回滚事务等。 事务1已经处理到了,where id = 3,这个时候事务2刚开始就使用where id =3 加锁,因为锁是行锁索引实现的,所以这个时候就冲突了。出入数据也同理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章