|
深入浅出MySQL——数据库开发、优化与管理维护
20.3.9 关于死锁
http://book.iyunv.com 2008-03-27 23:07 唐汉明 翟振兴 兰丽华 关宝军 申宝柱 人民邮电出版社 我要评论(0)
- 摘要:《深入浅出MySQL——数据库开发、优化与管理维护》从数据库的基础、开发、优化、管理4方面对MySQL进行了详细的介绍,其中每一部分都独立成篇,每一篇又包括多个章节。本书面向实用,内容覆盖广泛,讲解由浅入深,适合于各个层次的读者。本文介绍了InnoDB锁。
- 标签:MySQL 数据库 InnoDB
- Oracle帮您准确洞察各个物流环节
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)
|
|
|
|