死锁是指两个或两个以上的事务在执行过程中,因争夺锁资源而造成的一种互相等待的现象。
1 数据库层面解决死锁的两种方式
1、解决死锁的问题最简单的方式是不要有等待,将任何的等待都转化为回滚,并且事务重新开始。
这种没有死锁问题的产生。在线上环境中,可能导致并发性能的下降,甚至任何一个事务都不能进行。而这锁带来的问题远比死锁问题更为严重,而这锁带来的问题原题远比死锁问题更为严重,因为这很难被发现并且浪费资源。
2、解决死锁的问题最简单的一种方法时超时
,即当两个事务互相等待是,当一个等待时超过设置的某一阈值是,其中一个事务进行回滚,另一个等待的事务就能继续进行。用innodb_lock_wait_timeout
用来设置超时的时间。
超时机制虽然简单,仅通过超时后对事务进行回滚的方式来处理,或者说其根据FIFO的顺序选择回滚对象。但若超时的事务所占权重比较大,如事务操作更新很多行(比如某程序猿用死循环来执行一些事务),占用了较多的undo log,这是采用FIFO 的方式,就显得不合适了,因为回滚这个事务的时间相对另一个事务所占用的时间可能会更多。
在mysql 5.7.x 和 mysql 5.6.x 对死锁采用的方式:
mysql 5.6.x 是用锁等待(超时)的方式来解决, 没有自动解决死锁的问题:
mysql 5.7.x 默认开启了死锁保护机制:
2 死锁演示
如果程序是串行的,那么不可能发生死锁。死锁只存在于并发的情况,而数据库本身就是一个并发运行的程序,因此可能会发生死锁。
死锁示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
a :创建表
create table temp(id int primary key ,name varchar(10));
insert into temp values(1, 'a' ),(2, 'b' ),(3, 'c' );
此时表里只有3条数据
执行步骤根据数据顺序来:
1. 事务1:
start transaction;
update temp set name= 'aa' where id=1;
2. 事务2:
start transaction;
update temp set name= 'bb' where id=2;
3. 事务1:update temp set name= 'aaa' where id=2;
这时候3的步骤会有锁等待, 立马执行4,就会马上产生死锁
4. 事务2: update temp set name= 'bbb' where id=1;
|
3 避免死锁发生的方法
在事务性数据库中,死锁是个经典的问题,但只要发生的频率不高则死锁问题不需要太过担心
死锁应该非常少发生,若经常发生,则系统是不可用。
查看死锁的方法有两种:
通过show engine innodb status
命令可以查看最后一个死锁的情况
通过innodb_print_all_deadlocks
参数配置可以将所有死锁的信息都打印到MySQL的错误日志中
减少死锁发生的方法:
1、尽可能的保持事务小型化,减少事务执行的时间可以减少发生影响的概率
2、及时执行commit或者rollback,来尽快的释放锁
3、当要访问多个表数据或者要访问相同表的不同行集合时,尽可能的保证每次访问的顺序是相同的。比如可以将多个语句封装在存储过程中,通过调用同一个存储过程的方法可以减少死锁的发生
4、增加合适的索引以便语句执行所扫描的数据范围足够小
5、尽可能的少使用锁,比如如果可以承担幻读的情况,则直接使用select语句,而不要使用select…for update语句
6、如果没有其他更好的选择,则可以通过施加表级锁将事务执行串行化,最大限度的限制死锁发生