Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Mysql学习笔记【锁】

Mysql学习笔记【锁】

原创
作者头像
Porco1Rosso
修改于 2020-07-02 03:25:53
修改于 2020-07-02 03:25:53
6390
举报
本文为极客时间《Mysql实战45讲》的学习笔记,并结合《高性能Mysql》,梳理了索引相关的知识点,总结了一些常见问题,并记录了一些比较实用的方法。
MYSQL锁知识点.png
MYSQL锁知识点.png

锁的类型

全局锁

  • 全局锁就是对整个数据库实例加锁
  • 命令是 Flush tables with read lock (FTWRL)
  • 使用这个命令,其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句
  • 全局锁的典型使用场景是,做全库逻辑备份。

既然要全库只读,为什么不使用 set global readonly=true 的方式呢?

  1. 有些情况下,readonly 的值会被用来做其他逻辑,比如用来判断一个库是主库还是备库。
  2. 在异常处理机制上有差异。FTERL在服务出现异常后,会自动释放全局锁。而readonly不会,将导致服务长期处在不能写入的状态。
  3. readonly 这个设置对于super 权限是无效的。

表锁

表锁大致可以分成两种:表锁,元数据锁(MDL锁)

表锁
  1. 表锁的语法是 lock tables … read/write,解锁unlock tables。客户端断开的时候自动释放。
  2. lock tables 语法除了会限制别的线程的读写外,也限定了本线程接下来的操作。
MDL 锁
  1. MDL 不需要显式使用,在访问一个表的时候会被自动加上,已保证读写的正确性
  2. 当对一个表做增删改查操作的时候,加 MDL 读锁;当要对表做结构变更操作的时候,加 MDL 写锁
  3. MDL 锁,在语句执行开始时申请,但是语句结束后并不会马上释放,而会等到整个事务提交后再释

如何安全地给小表加字段?首先,干掉长事务,避免不必要的锁等待。其次,设置等待时间,反复重试。undefined查询MDL锁可以使用:

代码语言:txt
AI代码解释
复制
show processlist // 需要设置performance_schema on 大约会有10%性能损失

行锁

(各个引擎对于行锁的实现方式不一样)

与表锁的一些比较:

  • 表锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低
  • 行锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高 查询行锁可以使用:
代码语言:txt
AI代码解释
复制
select * from t sys.innodb_lock_waits where locked_table = 'db'.'table'
两阶段锁

在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。

因此,对于一个事务中语句执行顺序有一个大致的原则:如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放

Record Locks 行记录锁
代码语言:txt
AI代码解释
复制
UPDATE users SET name = "saurfang" WHERE id = 3;

最基本的锁,锁住ID为1的这一行数据。锁会加在索引上,如果没有主键索引,那么会加在row_id上。如果查询的是二级索引,会回到主键索引上,并加锁。

当查询没有索引时,会走全表,把查到的每一行都加锁,在RC(读提交)下,加锁的语句执行完成后,就会直接释放掉不符合要求的行锁。因此,如果一条更新语句没有走索引,会花费极大的开销。

Gap Locks 间隙锁

我们之前提到过一个幻读的问题,在RR(可重复读)的隔离级别下,解决方法就是间隙锁。

间隙锁,锁住的是两个行之间的数据,不允许其他人向中间写入一个数据。比如在2-4 之间加上间隙锁,那么其他人在写入 3的时候就不会成功。

以上面的sql 为例子,MySQL会给id=3 这行的前后索引之间的间隙都加上锁。当多个事务同时持有这一行间隙锁的时候是不会出现冲突的,因为跟间隙锁存在冲突关系的,是“往这个间隙中插入一个记录”这个操作。 (这里的写入指的是insert,更新操作是不会被锁住的)。

实际上,目前遇到的多数业务中,对于数据一致性的要求不是非常非常的高,出现幻读也不是非常严重的问题,可以把隔离级别降到RC(读提交)这样可以提高并发性。间隙锁虽然彼此不冲突,本身也是花费一些开销,而且会和写入操作发生冲突,影响并发。另外,所谓“间隙”,其实根本就是由“这个间隙右边的那个记录”定义的。

Next-Key Locks

可以认为是记录锁和间隙锁的组合。是一个前开后闭区间。比如上边的sql,Next-Key Locks加的锁就是(2,3]。意思就是,加了(2,3)的间隙锁,又加了3 的行锁

无论主键索引还是二级索引,都会加上间隙锁。Next-Key Locks 因为包含行锁,会出现冲突。

Insert Intention Locks 插入意向锁

只有在insert的时候会使用,和间隙锁冲突,但是彼此不冲突。比如两个写入的事务都有(1,5)的意向锁,一个写入2,一个写入4,不会发生冲突。如果(1,5)之间有间隙锁,那么他们都会个间隙锁发生冲突。

读写锁与意向锁

锁的标志
  • LOCK_IS:读意向锁;
  • LOCK_IX:写意向锁;
  • LOCK_S:读锁;
  • LOCK_X:写锁;
  • LOCK_AUTO_INC:自增锁;
读写锁
  1. 读锁,又称共享锁(Share locks,简称 S 锁),加了读锁的记录,所有的事务都可以读取,但是不能修改,并且可同时有多个事务对记录加读锁
  2. 写锁,又称排他锁(Exclusive locks,简称 X 锁),或独占锁,对记录加了排他锁之后,只有拥有该锁的事务可以读取和修改,其他事务都不可以读取和修改,并且同一时间只能有一个事务加写锁。
  3. 以上都基于当前读。在快照读下,无论加不加锁,都可以直接读。
读写意向锁

表锁和行锁是互相冲突的。如果一个行锁只锁住了一行数据,这时要申请一下表锁,那么会遍历表,看看是否存在行锁,开销很大。为了解决这个问题,会先在表上加上意向锁,然后再执行行锁操作。这样就可以避免上述问题。

意向锁之间是不会产生冲突的,也不和 AUTO_INC 表锁冲突,它只会阻塞表级读锁或表级写锁,另外,意向锁也不会和行锁冲突,行锁只会和行锁冲突。

1431433403.png
1431433403.png
  • 意向锁之间互不冲突;
  • S 锁只和 S/IS 锁兼容,和其他锁都冲突;
  • X 锁和其他所有锁都冲突;
  • AI 锁只和意向锁兼容;

加锁的规则

根据极客时间的《Mysql实战45讲》中的说明,对于加锁的基本规则大致为5个,包含了两个“原则”、两个“优化”和一个“bug”:

  1. 原则 1:加锁的基本单位是 next-key lock。希望你还记得,next-key lock 是前开后闭区间
  2. 原则 2:查找过程中访问到的对象才会加锁
  3. 优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁
  4. 优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁
  5. 一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止

几个经典的例子

代码语言:txt
AI代码解释
复制
CREATE TABLE `user` (
  `id` int NOT NULL,
  `name` int DEFAULT NULL,
  `age` int DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `age` (`age`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

BEGIN;
INSERT INTO `user` VALUES (1, 0, 0);
INSERT INTO `user` VALUES (2, 1, 5);
INSERT INTO `user` VALUES (3, 2, 10);
INSERT INTO `user` VALUES (5, 3, 15);
INSERT INTO `user` VALUES (10, 4, 20);
INSERT INTO `user` VALUES (15, 5, 25);
COMMIT;

假设我们数据库里的数据是这样的。我们对sql依次分析:

第一类:主键等值查询与普通索引等值查询

代码语言:txt
AI代码解释
复制
update user set age = 15 where id = 5;

首先加上(3,5]的next-key lock(原则1);然后找到了id=5 的这一行,next-key lock退化为行锁(优化1)。此时其他事务可以写入一个ID=4的数据。

代码语言:txt
AI代码解释
复制
update user set age = 15 where id = 4; #主键索引

首先,ID=4 不存在,向后查到ID=5加上(3,5]的next-key lock(原则1);然后next-key lock退化为间隙锁(优化2)锁住了(3,5)。此时其他事务无法写入一个ID=4的数据。

代码语言:txt
AI代码解释
复制
select ID from user where age = 10 lock in share mode #普通索引 覆盖索引

这个语句走了普通索引,只查询id,所以会走覆盖索引,不用回表。此时会查到age (5,10],然后退化为age=10 的行锁(优化1),继续往后查到(10,15],然后退化为间隙锁(10,15)(优化2)。

注意! 根据原则2,此时主键索引不会加锁。因此,通过主键更新name 不会被锁住,原因是当前索引上没有name这个字段,但写入一行数据会被锁住。

lock in share mode 只锁覆盖索引。for update ,系统会认为你接下来要更新数据,因此会顺便给主键索引上满足条件的行加上行锁。


第二类:范围查询

代码语言:txt
AI代码解释
复制
select * from user where id>=10 and id<11 for update; #主键范围查询

首先,给ID=10 加行锁(同上边);然后,继续向后查到ID=15,加(10,15]的next-key lock。此时实际加锁范围是10,15

代码语言:txt
AI代码解释
复制
select * from user where age >=10 and age<11 for update; #普通索引范围查询

首先,给普通索引(5,10]加锁,此时age 索引不是唯一索引,不能走优化1;然后,继续向后查到ID=15,加(10,15]的next-key lock。此时实际加锁范围是(5,15]

代码语言:txt
AI代码解释
复制
select * from user where id >5 and id <= 10 for update; #主键范围查询

按理说,给(5,10]加上锁就行了,实际上,会继续向后查,给(10,15]加上锁。这就是上面说的BUG

加锁的顺序

我们前面说加锁指的是 next-key lock。实际的加锁顺序分成两步,第一步加间隙锁,第二步加行锁。我们之前说,间隙锁彼此不冲突,一个间隙可以很多个事务持有间隙锁,但是行锁只有一个事务持有,其他就处在等待状态了。

在执行过程中,通过树搜索的方式定位记录的时候,用的是“等值查询”的方法。

当查询很多行数据时,锁是一个一个加上去的,并不是一起加的。 在实际工作中,可以遵循以下规则:

  1. 由于锁是一个个加的,要避免死锁,对同一组资源,要按照尽量相同的顺序访问;
  2. for update 这条语句占有的资源更多,回滚成本更大,所以 InnoDB 选择了回滚成本更小的 lock in share mode 语句回滚。

死锁与死锁检测

死锁

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

举一个简单的死锁例子:

代码语言:txt
AI代码解释
复制
#TRANSACTION 1
begin;
update user set age = age + 1 where id = 1;
update user set age = age + 1 where id = 2;

#TRANSACTION 2
begin;
update user set age = age + 1 where id = 2;
update user set age = age + 1 where id = 1;

此时,事务1 在等事务2 放开 ID= 2 的行锁,事务2在等事务1 放开ID=1 的行锁,出现了死锁。

死锁检测

处理死锁的两个策略:

  1. 直接进入等待,直到超时。这个超时时间可以通过参数 innodb_lock_wait_timeout 来设置
  2. 发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑

在 InnoDB 中,innodb_lock_wait_timeout 的默认值是 50s,意味着出现死锁后,第一个被锁住的线程要过 50s 才会超时退出,其他线程才有可能继续执行。这个开销有点大。死锁检测,就是出现事务被锁,就检查下他所依赖的线程有没有被其他锁住。

死锁检测有一定的性能损耗,如果并发很大的话,会导致CPU负载很高,但是并发量却上不去。

show engine innodb status 可以用来排查死锁信息

引申:怎么解决由这种热点行更新导致的性能问题

  1. 如果业务上不会出现死锁的情况发生,那就把死锁检测关闭了。
  2. 控制并发度,把同时需要进行死锁检测的并发降到合理的范围。难度较大
  3. 分而治之,将一行数据拆分为多行数据。将要加锁的行数进行分散,降低加锁的冲突。

insert时的情况

Insert 语句如果出现唯一键冲突,会在冲突的唯一值上加共享的 next-key lock(S 锁)。因此,碰到由于唯一键约束导致报错后,要尽快提交或回滚事务,避免加锁时间过长

乐观锁与悲观锁

乐观锁与悲观锁,可以认为是一种基于业务需要的特殊的锁。其中,乐观锁需要依赖业务逻辑来实现,悲观锁则直接使用select……for update 来实现。

其他

  • MDL作用是防止DDL和DML并发的冲突。
  • 快照读不需要加锁,自然也就没有死锁检测。
  • 并不是每次死锁检测都都要扫所有事务。A=B C=D,E=D 此时只会判断CDE 不会关注AB。
  • 如果查询直接走了覆盖索引,没有走到主键索引上,那么就直接在覆盖索引上加锁(原则2)。
  • 在删除数据的时候尽量加 limit,可以有效降低加锁的范围。既安全,又高效。
  • 空表有间隙锁码?有,空表锁 (-supernum,supernum]

Mysql 官方文档

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
万字硬核实战分析MySQL死锁
本文先完整介绍MySQL的各种锁类型及加锁机制,之后通过一个案例带大家了解如何分析排查死锁问题。最后,再介绍几种预防死锁的方法。以下是示例表的表结构
会玩code
2022/04/24
9600
万字硬核实战分析MySQL死锁
MySQL锁、加锁机制(超详细)—— 锁分类、全局锁、共享锁、排他锁;表锁、元数据锁、意向锁;行锁、间隙锁、临键锁;乐观锁、悲观锁
客户端发往MySQL的一条条SQL语句,实际上都可以理解成一个个单独的事务(一条sql语句默认就是一个事务)。而事务是基于数据库连接的,每个数据库连接在MySQL中,又会用一条工作线程来维护,也意味着一个事务的执行,本质上就是一条工作线程在执行,当出现多个事务同时执行时,这种情况则被称之为并发事务,所谓的并发事务也就是指多条线程并发执行。
寻求出路的程序媛
2024/06/24
20.3K5
MySQL锁、加锁机制(超详细)—— 锁分类、全局锁、共享锁、排他锁;表锁、元数据锁、意向锁;行锁、间隙锁、临键锁;乐观锁、悲观锁
两万字详解!InnoDB锁专题!
大家好,我是捡田螺的小男孩。本文将跟大家聊聊InnoDB的锁。本文比较长,包括一条SQL是如何加锁的,一些加锁规则、如何分析和解决死锁问题等内容,建议耐心读完,肯定对大家有帮助的。
捡田螺的小男孩
2022/05/23
1K0
两万字详解!InnoDB锁专题!
【MySQL】一文带你搞懂MySQL中的各种锁
锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资( CPU 、
陶然同学
2023/10/14
2K0
【MySQL】一文带你搞懂MySQL中的各种锁
mysql各种锁,一篇文章讲明白
当数据库中有多个操作需要修改同一数据时,不可避免的会产生数据的脏读。这时就需要数据库具有良好的并发控制能力,这一切在 MySQL 中都是由服务器和存储引擎来实现的。解决并发问题最有效的方案是引入了锁的机制,锁在功能上分为共享锁 (shared lock) 和排它锁 (exclusive lock) 即通常说的读锁和写锁; 锁的粒度上分行锁和表锁,表级锁MySQL 里面表级别的锁有两种:一种是表锁,一种是元数据锁(meta data lock,MDL)
jinjunzhu
2022/09/23
2.1K0
mysql各种锁,一篇文章讲明白
第15章_锁
🧑个人简介:大家好,我是 shark-Gao,一个想要与大家共同进步的男人😉😉
程序员Leo
2023/08/02
2200
第15章_锁
【MySQL】加了什么锁,导致死锁的?
最近在看 小林coding 的文章,看到一篇《字节面试:加了什么锁,导致死锁的?》,自己也跟着做了做,题目如下图:
sidiot
2023/08/31
3390
【MySQL】加了什么锁,导致死锁的?
10人面试9个答错?鹅厂T12详解MySQL加锁机制
👉腾小云导读 鹅厂有一道关于「数据库锁」的面试题。我们发现其实很多 DBA (数据库管理员,Database administrator)包括工作好几年的 DBA 都答得不太好。这说明 MySQL 锁的机制其实还是比较复杂,值得深入研究。本文对3条简单的查询语句加锁情况进行分析,以期帮助各位开发者彻底搞清楚加锁细节。欢迎阅读~ 👉看目录,点收藏 1 MySQL 有哪些锁?    1.1 全局锁    1.2 表锁    1.3 行锁 2 锁的兼容情况 3 锁信息查看方式 4 测试环境搭建    4.1 建立
腾讯云开发者
2023/05/29
5971
10人面试9个答错?鹅厂T12详解MySQL加锁机制
MySQL 锁
锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU、RAM、I/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。
用户9615083
2022/12/25
1.3K0
MySQL 锁
MySQL高级9-锁
  锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除了传统的计算资源(CPU、RAM、i/O)的挣用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性,有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素,从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。
Se7eN_HOU
2023/09/06
2570
MySQL高级9-锁
【MySQL-25】万字总结<锁>——(全局锁&行级锁&表级锁)【共享锁,排他锁】【间隙锁,临键锁】【表锁,元数据锁,意向锁】
默认情况下,InnODB在 REPEATABLE READ事务隔离级别运行,InnoDB使用 临键锁 进行搜索和索引扫描,以防止幻读。(本次演示)
YY的秘密代码小屋
2024/09/09
2130
【MySQL-25】万字总结<锁>——(全局锁&行级锁&表级锁)【共享锁,排他锁】【间隙锁,临键锁】【表锁,元数据锁,意向锁】
简单了解 MySQL 中相关的锁
数据也是一样,在并发的场景下,如果不对数据加锁,会直接破坏数据的一致性,并且如果你的业务涉及到钱,那后果就更严重了。
冬夜先生
2021/10/08
3510
数据库锁的12连问,抗住!
同理,对于MySQL数据库来说的话,一般的对象都是一个事务一个事务来说的。所以,如果一个事务内,一个SQL正在更新某条记录,我们肯定不想它被别的事务影响到嘛?因此,数据库设计大叔,给该行数据加上锁(行锁)。
捡田螺的小男孩
2023/02/22
7760
数据库锁的12连问,抗住!
MySQL锁详解
全局锁就是对整个数据库实例加锁。MySQL提供了一个加全局读锁的方法,命令是Flush tables with read lock。当需要让整个库处于只读状态的时候,可以使用这个命令,之后其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句
全栈程序员站长
2022/08/12
7341
MySQL锁详解
MySQL的锁机制_线程安全与锁机制
数据库使用锁是为了支持对共享资源的并发访问,同时保证数据的完整性和一致性。其中,MySQL在Server层和InnoDB引擎设计了多种类型的锁机制,用于实现不同场景下的并发控制,下面我们分析一下这些锁的定义和使用场景。
全栈程序员站长
2022/11/11
6920
MySQL的锁机制_线程安全与锁机制
MySQL的死锁系列- 锁的类型以及加锁原理
疫情期间在家工作时,同事使用了 insert into on duplicate key update 语句进行插入去重,但是在测试过程中发现了死锁现象:
程序员历小冰
2020/04/01
1.1K0
MySQL的死锁系列- 锁的类型以及加锁原理
MySQL的并发守护者:MVCC和锁机制的细节解析
开始之前推荐一篇实用的文章:《KeepAlive 组件深度解析:缓存与性能优化之道》,作者:【Front_Yue】。
Lion 莱恩呀
2024/12/02
1960
MySQL的并发守护者:MVCC和锁机制的细节解析
MySQL InnoDB 加锁机制
InnoDB有两种不同的SELECT,即普通SELECT 和 锁定读SELECT. 锁定读SELECT 又有两种,即SELECT ... FOR SHARE 和 SELECT ... FOR UPDATE; 锁定读SELECT 之外的则是 普通SELECT
code-x
2022/06/21
3.1K1
MySQL死锁浅析
MySQL中存在着许多的锁,按照锁的作用范围可以分为全局锁、表级锁和行级锁,每种锁级别下又可划分更细粒度的锁。文章不会涉及锁的具体实现细节,主要介绍的是碰到锁时的现象和其背后原理。由于日常开发阶段主要打交道的是行级锁,所以你可以重点关注行级锁的特性!
政采云前端团队
2023/12/14
5490
MySQL死锁浅析
MySQL锁都分不清,怎么面试进大厂?
作者简介:肖泽凡,腾讯TEG研发管理部小小后台攻城狮一枚,负责腾讯敏捷产品研发平台TAPD的基础功能的开发和维护,热爱技术,喜欢分享,文章首次发表于SegmentFault,博客名“X先生”,欢迎与我交流~ 锁是为了解决并发环境下资源竞争的手段,其中乐观并发控制,悲观并发控制和多版本并发控制是数据库并发控制主要采用的技术手段,而MySQL中的锁就是其中的悲观并发控制。 MySQL中的锁有很多种类,我们可以按照下面方式来进行分类。 一、按读写 从数据库的读写的角度来分,数据库的锁可以分为分为以下几种:
腾讯云数据库 TencentDB
2020/09/15
1.1K0
相关推荐
万字硬核实战分析MySQL死锁
更多 >
LV.1
自由工作者
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档