当前位置:首页 > mysql > 正文内容

mysql中行锁、两阶段锁协议、死锁以及死锁检测

phpmianshi4年前 (2017-05-09)mysql309

行锁

MySQL的行锁都是在引擎层实现的,但是 MyISAM 不支持行锁,意味着并发控制只能使用表锁,同一张表任何时刻只能被一个更新在执行,影响到业务并发度。InnoDB 是支持行锁的,这也是 MyISAM 被 InnoDB 替换的重要原因之一。

行锁就是针对数据库中表的行记录的锁,这很好理解,比如事务 A 更新了一行,而这时候,事务 B 也要更新一行,则必须等事务 A 的操作完成后才能更新。

两阶段锁 (Two-Phase Locking――2PL)

在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,需要等事务结束时才释放,这就是两阶段锁协议,分为加锁阶段和解锁阶段,所有的 lock 操作都在 unlock 操作之后。

两段锁协议规定所有的事务应遵守的规则:
  ① 在对任何数据进行读、写操作之前,首先要申请并获得对该数据的封锁。
  ② 在释放一个封锁之后,事务不再申请和获得其它任何封锁。
  即事务的执行分为两个阶段:
  第一阶段是获得封锁的阶段,称为扩展阶段。
  第二阶段是释放封锁的阶段,称为收缩阶段。


遵循两段锁协议的事务有可能发生死锁。
 

  此时事务T1 、T2同时处于扩展阶段,两个事务都坚持请求加锁对方已经占有的数据,导致死锁。
  为此,又有了一次封锁法。一次封锁法要求事务必须一次性将所有要使用的数据全部加锁,否则就不能继续执行。因此,一次封锁法遵守两段锁协议,但两段锁并不要求事务必须一次性将所有要使用的数据全部加锁,这一点与一次性封锁不同,这就是遵守两段锁协议仍可能发生死锁的原因所在。


死锁

如下图所示,事务 A 在等待事务 B 释放 id = 2 的行锁,而事务 B 在等待 事务 A 释放 id = 1 的行锁,事务 A 和事务 B 在互相等待对方的资源释放,就是进入了死锁状态。

图片.png

在并发系统中,不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会导致这几个线程进入无限等待的状态,成为死锁。

当进入死锁状态时,有下列 2 种策略:

  1. 通过 innodb_lock_wait_timeout 来设置超时时间,InnoDB 中默认值是 50s,第一个被锁住的事务 A 等待超过 50s 才会超时退出,其他事务才能得以执行,对于在线服务来说,这个等待时间往往是无法接受的。如果设置太短 1s,可能有的事务只是简单的锁等待,就被退出了,会出现很多误伤。

  2. 通过设置 innodb_deadlock_detect = on,发起死锁检测,发现死锁之后主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。比如回滚事务 A,让事务 B 继续执行。

死锁检测

正常情况下使用第 2 种策略的,但是快速发现死锁并进行处理,也是有额外负担的。你可以想象一下这个发现死锁的过程:每当一个事务被锁的时候,就要看看他依赖的线程有没有被别人锁住,判断是否出现了循环等待,也就是死锁。

假设有 1000 个并发线程,都要同时更新同一行,第 1 个线程来的时候检测数是 0;第 2 个线程来的时候,需要检测【线程1】有没有被别人锁住;第 3 个线程来的时候,需要检测【线程1,线程2】有没有被其他线程锁住,以此类推,第 n 个线程来的时候,检测数是 n - 1,所以总的检测数是 0 + 1 + 2 + 3 + 。。。+ (n - 1) = n(n -1)/2,所以时间复杂度应该是 O(n²)。

也就是 1000 个并发线程同时操作同一行,那么死锁检测操作就是 100 万这个量级的,虽然最终检测的结果是没有死锁,但是这期间要消耗大量的 CPU 资源,就会看到 CPU 利用率很高,但是每秒却执行不了几个事务。

那么怎么处理这种热点行更新导致的性能问题呢

  1. 1、关掉死锁检测,等待超时

  2. 如果你能确定这个业务一定不会出现死锁,可以临时把死锁关掉,这种操作带有一定风险,因为业务设计的时候一般不会把死锁当成一个严重错误,毕竟出现死锁了,就回滚,然后通过业务重试一般就没有问题了,这是业务无损的,而关掉死锁检测意味着可能出现大量超时,这是业务有损的。

  3. 2、控制并发度

  4. 比如同一行最多只有 10 个线程在更新,这样死锁检测的成本很低,一个直接的想法就是在客户端做并发控制。可是如果客户端有 600 个,即使每个客户端控制到只有 5 个线程,汇总到数据库服务端以后,峰值并发数也有可能达到 3000。因此这个并发控制要在服务端,比如引入中间件来实现,在进入引擎之前排队。

  5. 3、热点key拆分

  6. 将一行改成逻辑上的多行来处理,比如影院的账户余额等于 10 行记录的值总和,这样每次给影院账户加金额的时候,随机选取其中一条记录来加,冲突概率变为原来的1/10,减少锁的等待个数,也就减少了死锁检测的 CPU 消耗。这个方案看上去是无损的,但是需要根据业务逻辑做详细设计。如果账户余额减少,比如退票,这个时候就要考虑当一部分行记录变为 0 的时候,代码要有特殊处理。



版权声明:本文由PHP面试资料网发布,如需转载请注明出处。
分享给朋友:

相关文章

mysql中Query语句对系统性能的影响

背景我们重点分析实现同样功能的不同 SQL语句在性能方面会产生较大的差异的根本原因,并通过一个较为典型的示例来对我们的分析做出相应的验证。为什么返回完全相同结果集的不同SQL语句,在执行性能方面存在差...

InnoDB体系结构及工作原理

InnoDB体系结构及工作原理

概念InnoDB主要包括了内存池、后台线程以及存储文件。INNODB的三大特性:插入缓存,两次写,自适应hash内存池又是由多个内存块组成的,主要包括Buffer Pool、redo log缓冲等,解...

mysql中eq_range_index_dive_limit参数学习

概念官方文档如下描述:This variable indicates the number of equality ranges in an equality comparison conditi...

mysql中performance_schema(二) 理论篇

背景mysql performance_schema中总共包含52个表,主要分为几类:Setup表,Instance表,Wait Event表,Stage Event表Statement Ev...

mysql中index_merge索引合并优化

前言深入理解 index merge 是使用索引进行优化的重要基础之一。理解了 index merge 技术,我们才知道应该如何在表上建立索引。MySQL在分析执行计划时发现走单个索引的过滤效果都不是...

mysql中distinct的实现与优化

概念DISTINCT 实际上和 GROUP BY的操作非常相似,只不过是在 GROUP BY 之后的每组中只取出一条记录而已。所以,DISTINCT 的实现和 GROUP BY 的实现也基本差不多,没...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。