20.3.9 关于死锁
上文讲过,MyISAM表锁是deadlock free的,这是因为MyISAM总是一次获得所需的全部锁,要么全部满足,要么等待,因此不会出现死锁。但在InnoDB中,除单个SQL组成的事务外,锁是逐步获得的,这就决定了在InnoDB中发生死锁是可能的。如表20-17所示的就是一个发生死锁的例子。
表20-17 InnoDB存储引擎中的死锁例子
session_1
session_2
mysql> set autocommit = 0;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from table_1 where where id=1 for update;
…
做一些其他处理…
mysql> set autocommit = 0;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from table_2 where id=1 for update;
…
select * from table_2 where id =1 for update;
因session_2已取得排他锁,等待
做一些其他处理…
mysql> select * from table_1 where where id=1 for update;
死锁
在上面的例子中,两个事务都需要获得对方持有的排他锁才能继续完成事务,这种循环锁等待就是典型的死锁。
发生死锁后,InnoDB一般都能自动检测到,并使一个事务释放锁并回退,另一个事务获得锁,继续完成事务。但在涉及外部锁,或涉及表锁的情况下,InnoDB并不能完全自动检测到死锁,这需要通过设置锁等待超时参数innodb_lock_wait_timeout来解决。需要说明的是,这个参数并不是只用来解决死锁问题,在并发访问比较高的情况下,如果大量事务因无法立即获得所需的锁而挂起,会占用大量计算机资源,造成严重性能问题,甚至拖跨数据库。我们通过设置合适的锁等待超时阈值,可以避免这种情况发生。
通常来说,死锁都是应用设计的问题,通过调整业务流程、数据库对象设计、事务大小,以及访问数据库的SQL语句,绝大部分死锁都可以避免。下面就通过实例来介绍几种避免死锁的常用方法。
(1)在应用中,如果不同的程序会并发存取多个表,应尽量约定以相同的顺序来访问表,这样可以大大降低产生死锁的机会。在下面的例子中,由于两个session访问两个表的顺序不同,发生死锁的机会就非常高!但如果以相同的顺序来访问,死锁就可以避免。
表20-18 InnoDB存储引擎中表顺序造成的死锁例子
session_1
session_2
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> select first_name,last_name from actor where actor_id = 1 for update;
+————+———–+
| first_name | last_name |
+————+———–+
| PENELOPE| GUINESS|
+————+———–+
1 row in set (0.00 sec)
mysql> insert into country (country_id,country) values(110,’Test’);
Query OK, 1 row affected (0.00 sec)
mysql>insert into country (country_id,country) values(110,’Test’);
等待
mysql> select first_name,last_name from actor where actor_id = 1 for update;
+————+———–+
| first_name | last_name |
+————+———–+
| PENELOPE| GUINESS|
+————+———–+
1 row in set (0.00 sec)
mysql>insert into country (country_id,country) values(110,’Test’);
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
(2)在程序以批量方式处理数据的时候,如果事先对数据排序,保证每个线程按固定的顺序来处理记录,也可以大大降低出现死锁的可能。
表20-19 InnoDB存储引擎中表数据操作顺序不一致造成的死锁例子
session_1
session_2
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> select first_name,last_name from actor where actor_id = 1 for update;
+————+———–+
| first_name | last_name |
+————+———–+
| PENELOPE| GUINESS|
+————+———–+
1 row in set (0.00 sec)
mysql> select first_name,last_name from actor where actor_id = 3 for update;
+————+———–+
| first_name | last_name |
+————+———–+
| ED| CHASE|
+————+———–+
1 row in set (0.00 sec)
mysql> select first_name,last_name from actor where actor_id = 3 for update;
等待
mysql> select first_name,last_name from actor where actor_id = 1 for update;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
mysql> select first_name,last_name from actor where actor_id = 3 for update;
+————+———–+
| first_name | last_name |
+————+———–+
| ED| CHASE|
+————+———–+
1 row in set (4.71 sec)