| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 大数据 -> MySQL 8:行锁 -> 正文阅读 |
|
[大数据]MySQL 8:行锁 |
行锁行锁是针对数据库表中行记录的锁,是锁一行或者多行。MySQL 的行锁是基于索引加载的,所以行锁是要加在索引响应的行上。加行锁的目的是为了减少锁冲突,提升业务的并发度。 示例 1 :比如事务 A 更新了一行数据,而此时事务 B 也要更新同一行数据,则必须等待事务 A 操作完成之后才能进行更新操作。 示例 2 :数据库表中有一个主键索引和一个普通索引,SQL 语句基于索引查询,命中两条记录。此时行锁就锁定两条记录,当其他事务访问数据库同一张表时,被锁定的记录不能被访问,其他的记录都可以访问到。 行锁优点
表锁和行锁比较:表锁:开销小,加锁快;不会出现死锁;锁定力度大,发生锁冲突概率高,并发度低。 行锁:开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高。 两阶段锁我们先来看一个例子:在下面的操作序列中,事务 B 的 update 语句执行时会是什么现象呢?假设字段 id 是表 t 的主键。
实际上事务 B 的 update 语句会被阻塞,直到事务 A 执行 commit 之后,事务 B 才能继续执行。 我们来分析下:
知道了这个设定,对我们使用事务有什么帮助呢? 那就是,如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。 来举个栗子: 假设你负责实现一个直播间在线交易业务,顾客 A 要在电商直播间 B 购买一件衣服。我们简化一点,这个业务需要涉及到以下操作:
也就是说,要完成这个交易,我们需要 update 两条记录,并 insert 一条记录。当然,为了保证交易的原子性,我们要把这三个操作放在一个事务中。那么,你会怎样安排这三个语句在事务中的顺序呢? 试想如果同时有另外一个顾客 C 要在电商直播间 B 买衣服,那么这两个事务冲突的部分就是语句 2 了。因为它们要更新同一个直播间商家的余额,需要修改同一行数据。 根据两阶段锁协议,不论我们怎样安排语句顺序,所有的操作需要的行锁都是在事务提交的时候才释放的。所以如果把语句 2 的顺序放在最后,那么电商直播间 B 的商家账户余额这一行锁的时间就最少,这样就大幅度减少了事务之间的等待,提升了并发度。 如果这个电商直播间 B 做促销活动,以特别低价清仓处理一年内所有的衣服,而且这个活动只做一天。于是在活动时间开始的时候,你的 MySQL 就挂了。你登上服务器一看,CPU 消耗接近 100%,但整个数据库每秒就执行不到 100 个事务。这是什么原因呢? 这里,我就要说到死锁和死锁检测了。 死锁和死锁检测死锁:当并发系统中不同线程出现资源循环依赖,涉及的线程都在等别的线程释放资源时,就会导致这几个线程处于无限等待的状态,这种情况称为死锁。 我们来看个栗子:
事务 A 在等待事务 B 释放行 id=2 的行锁,而事务 B 在等待事务 A 释放行 id=1 的行锁,于是就进入一种事务 A 和事务 B 在互相等待对方资源的状态,此时也即使进入了死锁的状态。 当出现死锁以后,有两种策略:
在 InnoDB 中 innodb_lock_wait_timeout 的默认值是 50s,也就是如果采用第一个策略,当出现死锁以后,第一个被锁住的线程要过 50s 之后才会超时退出,然后其他线程才有可能继续执行下去,对于在线的业务系统来说,这个等待时间着实有点长,客户是无法接受的。 但是,我们又不可能直接把这个时间设置成一个很小的值,比如 1s。这样当出现死锁的时候,确实很快就可以解开,但如果不是死锁,而是简单的锁等待呢?所以,超时时间设置太短的话,会出现很多误伤。 所以,正常情况下我们还是要采用第二种策略,即:主动死锁检测,而且 innodb_deadlock_detect 的默认值本身就是 on。主动死锁检测在发生死锁的时候,是能够快速发现并进行处理的,但是它也是有额外负担的。 你可以想象一下这个过程:每当一个事务被锁的时候,就要看看它所依赖的线程有没有被别人锁住,如此循环,最后判断是否出现了循环等待,也就是死锁。那如果是我们上面说到的所有事务都要更新同一行的场景呢? 每个新来的被堵住的线程,都要判断会不会由于自己的加入导致了死锁,这是一个时间复杂度是 O(n) 的操作。假设有 1000 个并发线程要同时更新同一行,那么死锁检测操作就是 100 万这个量级的。虽然最终检测的结果是没有死锁,但是这期间要消耗大量的 CPU 资源。因此,你就会看到 CPU 利用率很高,但是每秒却执行不了几个事务。 线上踩坑我们线上就遇到了这种情况,做了分表处理,自增 id 要去查数据,然后更新这个自增 id,就造成所有的并发线程都要更新同一行数据让 id 去增值,于是就有很多超时等待的线程。 消耗了大量的 CPU 资源,但是每秒执行的事务却很少。 根据上面的分析,我们来讨论一下,怎么解决由这种热点行更新导致的性能问题呢?问题的症结在于,死锁检测要耗费大量的 CPU 资源。
行锁的衍生锁行锁还衍生了其他几种算法锁,分别是 记录锁、间隙锁、临键锁,下面我们依次来看看这三种锁。 记录锁(Record Lock)记录锁,锁的是表中的某一条数据。 触发条件:必须是精准命中索引并且索引是唯一索引,比如主键 id。 间隙锁(Gap Lock)间隙锁又称为区间锁,每次锁定都是锁定一个区间,也是属于行锁的一种。锁定的是记录与记录之间的空隙,间隙锁只阻塞插入操作,是 Innodb 为了解决幻读问题时引入的锁机制,所以只有在 Read Repeatable 、Serializable 隔离级别才有。 触发条件:也是命中索引,当我们查询数据用范围查询而不是相等查询时,查询条件命中索引,即便是没有查到符合条件的记录,此时也会将查询条件中的范围进行锁定,即使是范围中不存在的数据也会被锁定。 临键锁(Next-Key Lock)Record Lock + Gap Lock,是记录锁与间隙锁的并集,锁定一个范围并且锁定记录本身,是前开后闭区间,是 MySQL 加锁的基本单位。 查找过程中访问到的对象才会加锁。 注意的点索引上的等值查询,给唯一索引加锁的时候,Next-Key Lock 退化为记录锁。 索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。 唯一索引上的范围查询会访问到不满足条件的第一个值为止,MySQL 8.0.22之后已修复。 示例一张表 t, id (主键)、k (普通索引)、d 字段 插入数据 (0,0,0),(5,5,5),(10,10,10),(15,15,15)
|
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/24 4:09:11- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |