MyISAM表锁是deadlock free的,这是因为MyISAM总是一次获得所需的全部锁,要么全部满足,要么等待,因此不会出现死锁。但在InnoDB中,除单个SQL组成的事务外(因为我们的绝大部分应用都是单个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 for update; ... 做一些其他处理... mysql> set autocommit = 0; Query OK, 0 rows affected (0.00 sec) mysql> select * from table_2 where for update; ...
select * from table_2 where id =1 for update; 因session_2已取得排他锁,等待 做一些其他处理...
mysql> select * from table_1 where where for update; 死锁

在上面的例子中,两个事务都需要获得对方持有的排他锁才能继续完成事务,这种循环锁等待就是典型的死锁。

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

通常来说,死锁都是应用设计的问题,通过调整业务流程、数据库对象设计、事务大小,以及访问数据库的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)

转自:《深入浅出MySQL–数据库开发、优化与管理维护》