一、 什么是死锁
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等的进程称为死锁进程.
二、 死锁产生的四个必要条件
•互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放
•请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放
•不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放
•环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
三、死锁的影响
当产生某表死锁的一开始,所有涉及这张表的操作都将受到阻塞。假设这张表在业务逻辑上是读写频繁的,那就会使很多操作在那里排队等待,而排队等待会占用数据库连接,当该达到该数据库连接数的最大承载数之后,就会使所有数据库操作均无法再继续下去,致使数据库各项指标异常,导致整个环境崩溃。在生产环境中出现这种问题,那是相当致命的,当发现数据库指标异常时因快速处理!
四、如何发现死锁
1.查询数据库进程
主要看State字段,如果出现大量 waiting for ..lock 即可判定死锁:
1 |
SHOW FULL PROCESSLIST; |
注意:需要拥有root组权限(supper),否则只能看到当前用户的进程,无法查询所有
2.查看当前的事务
1 |
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX; |
INNODB_TRX 表包含信息关于每个事务(排除只读事务)当前执行的在InnoDB,包含是否事务是等待一个锁, 当事务启动后, SQL语句事务是正在执行
INNODB_TRX Columns 相关列信息:
- a) trx_id:innodb存储引擎内部事务唯一的事务id。
- b) trx_state:当前事务的状态。
- c) trx_started:事务开始的时间。
- d) trx_requested_lock_id:等待事务的锁id,如trx_state的状态为LOCK WAIT,那么该值代表当前事务之前占用锁资源的id,如果trx_state不是LOCK WAIT的话,这个值为null。
- e) trx_wait_started:事务等待开始的时间。
- f) trx_weight:事务的权重,反映了一个事务修改和锁住的行数。在innodb的存储引擎中,当发生死锁需要回滚时,innodb存储引擎会选择该值最小的事务进行回滚。
- g) trx_mysql_thread_id:正在运行的mysql中的线程id,show full processlist显示的记录中的thread_id。
- h) trx_query:事务运行的sql语句,在实际中发现,有时会显示为null值,当为null的时候,就是t2事务中等待锁超时直接报错(ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction)后,trx_query就显示为null值
比如事务t2正在运行trx_query: update test.t1 set b=’t2′ where a=1的sql语句,t1先执行,所以是trx_state: RUNNING先申请的资源一直在运行,而t2后run的所以是trx_state: LOCK WAIT一直在等待t1执行完后释放资源。 但是并不能仔细判断锁的一些详细情况,我们需要再去看当前锁定的事务表数据。
3.查看当前锁定的事务
1 |
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS; |
INNODB_LOCKS 表包含信息关于每个锁一个InnoDB 事务已经请求,但是没有获得锁,每个lock一个事务持有是堵塞另外一个事务
INNODB_LOCKS Columns 相关列信息:
- a) lock_id:锁的id以及被锁住的空间id编号、页数量、行数量
- b) lock_trx_id:锁的事务id。
- c) lock_mode:锁的模式。
- d) lock_type:锁的类型,表锁还是行锁
- e) lock_table:要加锁的表。
- f) lock_index:锁的索引。
- g) lock_space:innodb存储引擎表空间的id号码
- h) lock_page:被锁住的页的数量,如果是表锁,则为null值。
- i) lock_rec:被锁住的行的数量,如果表锁,则为null值。
- j) lock_data:被锁住的行的主键值,如果表锁,则为null值。
如以下查询 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
mysql> select * from INNODB_LOCKSG 1. row ** lock_id: 3015646:797:3:2 lock_trx_id: 3015646 lock_mode: X lock_type: RECORD lock_table: test.t1 lock_index: PRIMARY lock_space: 797 lock_page: 3 lock_rec: 2 lock_data: 1 2. row ** lock_id: 3015645:797:3:2 lock_trx_id: 3015645 lock_mode: X lock_type: RECORD lock_table: test.t1 lock_index: PRIMARY lock_space: 797 lock_page: 3 lock_rec: 2 lock_data: 1 2 rows in set (0.00 sec) |
这里我们可以看到当前的锁信息了,2个事务都锁定了,看相同的数据lock_space: 797、lock_page: 3、lock_rec: 2可以得出事务t1和事务t2访问了相同的innodb数据块,再通过lock_data字段信息lock_data: 1,看到锁定的数据行都是主键为1的数据记录,可见2个事务t1和t2都申请了相同的资源,因此会被锁住,事务在等待。
通过lock_mode: X值也可以看出事务t1和t2申请的都是排它锁。
PS:当执行范围查询更新的时候,这个lock_data的值并非是完全准确。当我们运行一个范围更新时,lock_data只返回最先找到的第一行的主键值id;另外如果当前资源被锁住了,与此同时由于锁住的页因为InnoDB存储引擎缓冲池的容量,而导致替换缓冲池页面,再去查看INNODB_LOCKS表时,这个lock_data会显示未NULL值,意味着InnoDB存储引擎不会从磁盘进行再一次查找。
4.查看当前等锁的事务
1 |
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS; |
INNODB_LOCK_WAITS 表包含了blocked的事务的锁等待的状态。当事务量比较少,我们可以直观的查看,当事务量非常大,锁等待也时常发生的情况下,这个时候可以通过INNODB_LOCK_WAITS表来更加直观的反映出当前的锁等待情况:
INNODB_LOCK_WAITSColumns 相关列信息:
-
- a) requesting_trx_id:申请锁资源的事务id。
- b) requested_lock_id:申请的锁的id。
- c) blocking_trx_id:阻塞的事务id。
- d) blocking_lock_id:阻塞的锁的id。
如以下查询:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
mysql> select * from INNODB_LOCK_WAITSG 1. row ** requesting_trx_id: 3015646 requested_lock_id: 3015646:797:3:2 blocking_trx_id: 3015645 blocking_lock_id: 3015645:797:3:2 1 row in set (0.00 sec) mysql> |
这里我们可以看到事务t1(3015646)申请了锁资源,而事务t2(3015645)则阻塞了事务t1的申请。
如何处理死锁
杀死进程
通过以上方法一可以查询对应死锁的数据库进程,可以直接杀掉
1 |
kill 进程ID |
如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。
虽然不能完全避免死锁,但可以使死锁的数量减至最少。将死锁减至最少可以增加事务的吞吐量并减少系统开销,因为只有很少的事务回滚,而回滚会取消事务执行的所有工作。由于死锁时回滚而由应用程序重新提交。
下列方法有助于最大限度地降低死锁:
(1)按同一顺序访问对象。
(2)避免事务中的用户交互。
(3)保持事务简短并在一个批处理中。
(4)使用低隔离级别。
(5)使用绑定连接。