S 锁与 X 锁的爱恨情仇《死磕MySQL系列 四》

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: S 锁与 X 锁的爱恨情仇《死磕MySQL系列 四》

前言

下边两幅图还熟悉吧!就是第三期文章中的前言,但上一期文章并未提及死锁,只是引出了全局锁、表锁的概念。本期文章将继续聊聊锁的内容。


Lock wait timeout exceeded; try restarting transaction

image.png



Deadlock found when trying to get lock; try restarting transaction


image.png


一、行锁

行锁的锁粒度最小,发送锁冲突的概率最低,并发度也最高。


问题:MySQL的所有存储引擎都支持行锁吗?


不是的,MySQL中只有Innodb存储引擎才支持行锁,其它的并不支持,MyIsam存储引擎也只支持表锁。


所以Myisam存储引擎只能使用表锁来解决并发,表锁开销小,加锁快,锁定粒度大,发生锁冲突的概率最高,并发度最低。


问题:锁粒度指的是什么?


这种名词不能只记名字,需要知道其代表的含义。锁粒度指的是加锁的范围。


上期文章讲的全局锁锁的是整库、表锁锁定的全表、行锁指的是锁定某一行或某个范围的数据。


问题:如何加行锁?


Innodb存储引擎在执行update、delete、insert语句时会隐式加排它锁,而对于select不会加任何锁。


同样也可以手动加锁。


共享锁:select * from tableName where id = 100 lock in share more


排它锁:select * from tableName where id = 100 for update


共享锁、排它锁也被称之为读锁、写锁。读锁与读锁之间不互斥,读锁与写锁、写锁与写锁之间是互斥的。


问题:为什么要加锁?


MySQL事务的四大特性分别是原子性、隔离性、一致性、持久性,当你了解完事务的四大特性之后就发现都是为了保证数据一致性为最终目的的。


常说一句话有人地方就有江湖,放在MySQL中是有锁的地方就有事务。


所以说加锁就是为了保证当事务结束后,数据库的完整性约束不被破坏,从而确保数据一致性。


二、两阶段锁

问题:两阶段锁是什么?


说实话,这个名字属实很唬人,猛然间你有没有想到另一个名词两阶段提交。这里回忆一下,两阶段提交是确保redo log跟binlog同时提交成功,若有一方提交失败则回滚。


在Innodb存储引擎中,行锁是在需要的时候加上的,但并不是不需要了就直接释放的,而是要等到事务结束才释放。


案例:解释两阶段锁


image.png


上图中MySQL1客户端开启事务并执行了两条update语句,紧接着MySQL2开启另一个事务执行update语句,那么此时MySQL的更新语句会执行成功吗?


答案肯定是不能的。


这个结论取决于MySQL1事务在执行完两条 update 语句后,持有哪些锁,以及在什么时候释放。你可以验证一下:MySQL2事务 update 语句会被阻塞,直到MySQL1事务 执行 commit 之后,才能继续执行。


万事有因必有果,有头必有尾,锁是开启事务后添加的也需提交事务后解除。


现在你理解了两阶段锁,那么试想一下对你在写代码有什么帮助吗?


三、理解死锁


image.png

这幅图是咔咔在2019年画的,当时用这种方式来解释死锁对于一部分伙伴来说属实有点绕。


错误的理解:之前在一个博文中看到对死锁是这样解释的


现实中这样的案例比比皆是,家里有两个小孩,给老大冲了一杯奶,这时老二过来也想喝。但奶嘴只有一个,此时老二只能处于等待状态,让老大先喝完。这个就是死锁。




不要把锁等待跟死锁一同对待,锁等待是,一个事务中的语句添加了共享锁,另一个事务开启了排它锁。此时就需要等待共享锁的释放,这个过程是锁等待。而死锁是两个事务互相等待对方。


四、优化你的代码尽量防止死锁

知道两阶段锁后,在以后的代码实现中要把最可能造成锁冲突也就是死锁的语句放到最后边。


问题:如何理解放到最后边这句话?


这样一个业务场景。


每到中午吃饭时间都是好几个人一起出去,吃饭得付钱吧!复现一下这个流程。


1.你给商家付了10块钱,这笔钱从你的余额中扣。


2.给商家的账户添加10元。


3.记录一条交易日志。


在这个过程中可得知进行了两次update操作,一次insert操作。使用为了保证交易的原子性数据的一致性此时必须得把三个操作放到一个事务。


在这三个操作中最容器造成锁冲突的就是第2步给商家的账户添加钱。


所以在编码过程中需要把第2步放到最后一步执行,保证在同样结果下锁住的时间最短。这样可以在编码的程度上尽量保证事务之间锁等待,提高事务并发度。


五、解释死锁的两种方案

第一种方式


MySQL已经给咱们提供好了,使用参数innodb_lock_wait_timeout来设置超时时间。若等待时间超过设置的值则返回超时错误。


在MySQL8.0版本中此值默认为50s,意味着当出现死锁以后,被锁住的线程需要50s才会自动退出,然后其它线程才会继续执行。这个等待时间一般是无法接受的。


但设置时间太短会造成很多锁等待的语句直接返回超时,造成严重误伤。


重要的话再说一遍:“不要把锁等待跟死锁一同对待,锁等待是,一个事务中的语句添加了共享锁,另一个事务开启了排它锁。此时就需要等待共享锁的释放,这个过程是锁等待。而死锁是两个事务互相等待对方。”


第二种方式


另一个种方式,同样MySQL也给提供了一个参数innodb_deadlock_detect,默认值为on,意思是当发现死锁后,MySQL主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。


检测死锁的流程是当一个事务被堵住时,就要看它所在的线程是否被别的线程锁住,如若没有则继续找下一个线程进行检测,最后判断是否出现了循环等待,也就是死锁。


过程示例:新来的线程F,被锁了后就要检查锁住F的线程(假设为D)是否被锁,如果没有被锁,则没有死锁,如果被锁了,还要查看锁住线程D的是谁,如果是F,那么肯定死锁了,如果不是F(假设为B),那么就要继续判断锁住线程B的是谁,一直走知道发现线程没有被锁(无死锁)或者被F锁住(死锁)才会终止


问题:平时在开发中使用那种方案呢?


存在必合理,一般情况还是采用第二种方式,这种方式在有死锁时是能够快速进行处理的。


作为开发者肯定听过一句这样的话要么用空间换时间,要么用时间换空间。两者只可兼一种。


这种方式虽可以非常迅速的处理死锁问题,同样也会带来额外的负担。


思考:带来了那些额外的负担?


假设你负责的业务都需要更新同一行数据。


此时按照第二种方式,当发现死锁后,主动回滚死锁链条的某一个事务,那么,每一个进来被堵住的线程,都要判断是不是由于自己的加入导致死锁,这个时间复杂度是O(n)的操作。


假设有1000个线程都在更新同一行,操作的数据量是100W,检测出来死锁消耗资源还不怕,若最终检测结果没有死锁,这个期间消耗的CPU资源是非常高的。


就如何解决这种问题再进行谈论一下。


六、如何解决热点数据的更新

为什么要聊这个问题


使用了第二种方案来解决死锁,热点数据死锁检测会非常消耗CPU(每一个进来被堵的线程都会检测是不是由于自己的加入导致的死锁,有可能是锁等待,但还是需要做判断,所以非常消耗CPU),所以针对这个问题进行简单讨论一下。


咔咔在其它资料中看到有三种方案。


1.关闭死锁检测

2.控制并发度

3.修改MySQL源码对于更新同一行数据,在进入引擎之前排队。这样就不会出现大量的死锁检测


方案一:关闭死锁检测不考虑


这种方式会出现大量的超时,降低了用户体验,一般情况死锁不会对业务产生严重错误,毕竟出现死锁,数据大不了回滚即可。


方案二:控制并发度


可以把商家账户分散多个,所有的账户之和为账户余额。


例如分了10个子账户,那么出现更新同一行数据的概率就降低了10倍,这种方式在业务处理时需要简单处理一下。防止账户余额为0时用户发起退款的逻辑处理。


这种方式还是很建议大家使用的,从设计上降低死锁发生。


方案三:修改MySQL源码


大多数公司连DBA都没有,何谈存在可以修改MySQL源码的人,这种对于企业的成本是非常大的,而且也没那个必要。


修改MySQL源码想要实现的功能是当更新同一行数据时,在进入存储引擎之前排队。


这种方案用队列完全可以解决,所以并不需要从根上解决这个问题。


七、总结

image.png


本期从行锁出发引出了两阶段锁,明白了事务提交后才会释放锁。


死锁的产生,如何从代码的角度来减少死锁的产生。


MySQL也给提供了两种方案来解决死锁问题,对于这两种方案咔咔也给了不同的观点。根据自己的情况来使用。


在这期文章中并没有演示死锁案例,在后边的文章中咔咔会给大家列举几种典型的死锁案例。


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
1月前
|
SQL 关系型数据库 MySQL
MySQL 锁
MySQL里常见的几种锁
52 3
|
25天前
|
存储 关系型数据库 MySQL
优化 MySQL 的锁机制以提高并发性能
【10月更文挑战第16天】优化 MySQL 锁机制需要综合考虑多个因素,根据具体的应用场景和需求进行针对性的调整。通过不断地优化和改进,可以提高数据库的并发性能,提升系统的整体效率。
45 1
|
1月前
|
关系型数据库 MySQL Java
MySQL数据锁:Record Lock,Gap Lock 和 Next-Key Lock
本文基于 MySQL 8.0.30 版本及 InnoDB 引擎,深入解析三种行锁机制:记录锁(Record Lock)、间隙锁(Gap Lock)和临键锁(Next-key Lock)。记录锁锁定索引记录,确保事务唯一修改;间隙锁锁定索引间的间隙,防止新记录插入;临键锁结合两者,锁定范围并记录自身,有效避免幻读现象。通过具体示例展示了不同锁的作用机制及其在并发控制中的应用。
106 2
|
1月前
|
存储 关系型数据库 MySQL
MySQL数据库锁:共享锁和独占锁
本文详细介绍了`InnoDB`存储引擎中的两种行级别锁:共享锁(S锁)与排他锁(X锁)。通过具体示例展示了这两种锁的工作机制及其在`InnoDB`与`MyISAM`引擎中的表现差异。文章还提供了锁的兼容性矩阵,帮助读者更好地理解锁之间的互斥关系。最后总结了两种锁的特点及适用场景。适合希望深入了解`MySQL`并发控制机制的读者阅读。
39 1
|
25天前
|
存储 关系型数据库 MySQL
MySQL锁,锁的到底是什么?
【10月更文挑战第16天】MySQL 锁锁定的是与数据和资源相关的对象,其目的是为了保证数据的一致性、避免冲突,并在并发环境下合理协调事务或操作的执行。理解锁的对象和意义对于优化数据库性能、处理并发问题至关重要。
54 0
|
2月前
|
监控 关系型数据库 MySQL
MySQL锁机制与解决死锁问题
MySQL锁机制与解决死锁问题
267 5
|
28天前
|
关系型数据库 MySQL 数据库
mysql锁详解
通过理解并合理运用MySQL中的锁机制,开发者可以有效管理数据库并发访问,平衡性能与数据一致性需求。更多关于MySQL锁的深入探讨和最佳实践,请参考专业的数据库管理资源[[深入MySQL锁机制详解
32 0
|
2月前
|
关系型数据库 MySQL 数据库
Mysql的锁
本文介绍了MySQL中表级锁和行级锁的区别,其中MyISAM仅支持表级锁,而InnoDB支持表级锁和行级锁,默认为行级锁。表级锁锁定整个表,实现简单,资源消耗少,但并发度低;行级锁仅锁定相关记录,减少冲突,提高并发度,但加锁开销大。此外,还介绍了共享锁和排他锁的概念及意向锁的作用。
|
2月前
|
存储 SQL 关系型数据库
MySQL 的锁机制,那么多的锁,该怎么区分?
MySQL 的锁机制,那么多的锁,该怎么区分?
38 0
|
3月前
|
关系型数据库 MySQL 数据库
MySQL MVCC和间隙锁有什么区别?
【8月更文挑战第24天】MySQL MVCC和间隙锁有什么区别?
88 0