文章目录
- 前言
- 一、行锁的介绍
- 二、行锁的使用
- 三、使用行锁所带来的问题
- 四、死锁和死锁检测
前言
上篇文章已经学习了MySQL的全局锁和表锁,今天这篇文章我们对行锁进行以下学习
一、行锁的介绍
行锁就是针对数据表中行记录的锁,比如事务A更新了一行,而这时候事务B也要更新同一行,则必须等事务A的操作完成后才能进行更新。并且MySQL的行锁是在各个引擎自己实现的,并不是所有的引擎都支持行锁的,比如MyISAM引擎就不支持行锁。
二、行锁的使用
接下来以一个具体的例子,来展现下行锁,别看先它暴露的问题。
如下对同一张表的操作,开启如下两个事务:
这两个事务执行的操作结果是,事务B的update语句会被阻塞,直到事务A执行commit之后,事务B才能继续执行,这是因为在InnoDB事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放(两阶段协议锁)。
了解到了行锁使用和释放的节点,接下来我们分析下这样做,使用行锁会引出什么问题
三、使用行锁所带来的问题
接下来我们也是用个具体的例子来展示这个问题。
我们用电影购票这是系统,来展示行锁带来的问题
电影购票的具体操作:
- 首先从购票顾客账户余额中扣除电影票价
- 给相对于影院的账户余额增加这张电影票价
- 记录一条交易日志
从上面执行流程中,我们可以看出这一个需要两次update操作,一次insert操作,并且这三次操作必须在一个事务里。
设想一下,实际购票网站中是会有很多人进行购买,那么就会有很多人购票业务开启,都会对影院账户余额这条记录进行update操作,根据行锁的释放都是在事务提交的时候才释放的,那么就不可避免的造成事务的等待,线程的阻塞
为了解决这个问题,我们看这个购票操作,造成事务等待的原因,就是因为在update影院账户余额造成锁阻塞。为了解决这个问题,我们可以把引起事务阻塞等待的操作放到事务的最后执行,即把对影院余额的更新操作放到事务最后执行的sql语句,这样最大先程度的减少了事务之间的锁等待,提升了并发度
了解了这些之后,接下来我们再才看下使用锁普遍遇到的问题,死锁。接下来我们分析下,MySQL使用行锁,遇到死锁是怎么办的
四、死锁和死锁检测
对操作数据库的表出现死锁现象,接下来我们也是以一个例子进行学习。
如下,对同一表中的相同行开启操作,造成死锁的现象:
事务A在等待事务B释放id=2的行锁,而事务B在等待事务A释放id=1的行锁。 事务A和事务B在互相等待对方的资源释放,就是进入了死锁状态。
MySQL中对于死锁,有两种策略,接下来分别介绍下:
- 直接进入等待,直到超时。这个超时时间可以通过参数innodb_lock_wait_timeout进行设置
意思就是在InnoDB中,innodb_lock_wait_timeout的默认值是50s,当出现死锁以后,第一个被锁住的线程要过50s才会超时退出,然后其他线程才有可能继续执行。默认执行时间如此之长,放到业务中是不现实的,如果把这个时间设置的比较小的,比如1s。又会造成别的问题,比如,如果是真的死锁,可以很快解开没有问题,但是如果不是死锁,而是简单的锁等待呢,设置时间太短的话,会出现很多误伤。所以一般情况下,会采用下面的这种策略:
- 发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数innodb_deadlock_detect设置为on,表示开启这个逻辑。
主动死锁检测默认本身就是on。主动死锁检测虽然能在发生死锁的时候快速发现并进行处理,但它也是有负担的。
具体执行流程如下:每当一个事务被锁的时候,就要看看它所依赖的线程有没有被别人锁住,如此循环,最后判断是否出现了循环等待,也就是死锁。
知道了这执行流程,一眼边可以看出这一个死锁检测的性能消耗是非常大的,比如有多个并发线程同时更新某一行,那么就要进行大量的死锁检测,消耗大量的CPU资源,就会让我们看到CPU的利用率很高。
那么对于,这更新加锁,然后进行死锁检测导致耗费的大量CPU资源,这种更新导致性能的问题怎么解决呢
- 如果确定业务一定不会出现死锁,则可以临时把死锁检测关掉;
- 控制并发度也可以,比如可以更改MySQL的操作,对于相同行的更新,可以在进入引擎之前排队,这样在InnoDB内部就不会有大量的死锁检测工作了。像我这样的菜鸡新书,没有能力更改MySQL的操作。这是我们也可以对表、业务进行优化。如我们可以对更新频发的那个行数据,进行拆分成多个行,分摊这个行的压力