MySQL 事务原理:锁机制

文章目录

  • 一、锁类型
    • 1.1 全局锁
    • 1.2 表级锁
      • 1.2.1 表锁
      • 1.2.2 元数据锁
      • 1.2.3 意向锁
      • 1.2.4 自增锁
    • 1.3 行级锁
      • 1.3.1 记录锁
      • 1.3.2 间隙锁
      • 1.3.3 临键锁
      • 1.3.4 插入意向锁
    • 1.4 锁的兼容性
  • 二、锁的CUDP
    • 2.1 查询
    • 2.2 删除、更新
    • 2.3 插入
  • 三、锁的对象
  • 四、并发死锁
    • 4.1 相反加锁顺序导致死锁
    • 4.2 锁冲突导致死锁
    • 4.3 如何避免死锁
    • 4.4 例子

锁机制用于管理对共享资源的并发访问,实现事务的隔离级别 。

一、锁类型

MySQL当中事务采用的是粒度锁:针对表(B+树)、页(B+树叶子节点)、行(B+树叶子节点当中某一段记录行)三种粒度加锁。

因此可分为全局锁、表级锁和行级锁。全局锁是针对数据库加锁,表级锁是针对表或页进行加锁;行级锁是针对表的索引加锁。

1.1 全局锁

全局锁(Global Lock)是一种数据库锁机制,它可以锁定整个数据库系统,阻止其他事务对数据库进行写入或修改操作。当一个事务获取到全局锁时,其他事务将无法执行任何对数据库写入的操作,直到全局锁被释放。

-- 全局锁,整个数据库处于只读状态,其他操作均阻塞
FLUSH TABLES WITH READ LOCK-- 释放全局锁
UNLOCK TABLES

全局锁用于全库逻辑备份。这样在备份数据库期间,不会因为数据或表结构的更新,而出现备份文件的数据与预期的不一样。

但是在备份期间,业务只能读数据,不能更新数据, 造成业务停滞。

1.2 表级锁

表级锁又分为表锁、元数据锁、意向锁、自增锁。

1.2.1 表锁

表锁(Table-level lock)用于锁定整个表,控制对表的并发访问。当一个事务获取到表级锁时,其他事务将被阻塞,无法同时对该表进行写操作或修改操作。

表锁是一种粗粒度的锁,适用于需要对整张表进行操作的场景。

LOCK TABLES 表名 READ|WRITEUNLOCK TABLES

1.2.2 元数据锁

元数据锁(MetaData Lock)用于保护数据库对象的元数据(如表结构、索引信息等)。当一个事务获取到元数据锁后,其他事务将无法修改该元数据,直到锁被释放。元数据锁将阻止并发事务对元数据的并行修改,防止出现不一致或损坏的元数据状态。

1.2.3 意向锁

意向锁(Intention Lock)用于快速判断表里是否有记录加锁。

当一个事务要获取一个表中某些行的排他锁或共享锁时,它需要首先获取该表的意向锁,可以快速判断表里是否有行记录加锁,从而避免全表扫描查询是否有记录被加锁了。

意向锁分为两种类型:

∘ \circ 意向共享锁(Intent Shared Lock,IS):一个事务要获取某表中某些行的共享锁时,需要先获取该表的意向共享锁。意向共享锁不互斥,多个事务可以同时获取。

∘ \circ 意向排他锁(Intent Exclusive Lock,IX):一个事务要获取某表中某些行的排他锁时,需要先获取该表的意向排他锁。意向排他锁与意向共享锁互斥,当一个事务持有意向排他锁时,其他事务无法获取该表的意向排他锁或意向共享锁。目的:为了告诉其他事务,此时这条表被一个事务在访问;作用:排除表级别读写锁 (全面扫描加锁)

例如,当一个事务要获取某表的排他锁时,它可以先检查是否已经有其他事务持有意向共享锁,如果有,则可以知道在表级上可能存在其他事务正在读取,而不必尝试获取排他锁。

1.2.4 自增锁

自增锁(Auto-Increment Lock)实现自增约束,当往表插入数据时会使用auto-inc锁来加锁,语句结束后释放锁,而不是在事务结束时释放(这和行级锁有区别,行级锁是在事务结束才释放锁)。

当一个事务要插入新数据并获取下一个自增值时,它首先会获取一个自增锁。一旦事务获得了自增锁,它就可以安全地执行插入操作,并确保每次插入都会得到一个唯一且连续的自增值。其他事务在等待自增锁被释放之前,无法获取下一个自增值,从而避免了冲突和重复。

但是,自增锁在对大量数据进行插入操作时,阻塞其他事务的插入操作,影响性能。因此, 在 Mysql 5.1.22 版本后仅对 AUTO_INCREMENT字段加上轻量级锁,当字段自增后,立即释放锁,而不需要等待整个插入语句执行完后才释放锁。

1.3 行级锁

行级锁的类型有

  • 记录锁,也就是仅仅把一条(行)记录锁上;
  • 间隙锁,锁定一个范围,但是不包含记录本身;
  • 临键锁:记录锁 + 间隙锁的组合,锁定一个范围,并且锁定记录本身。

1.3.1 记录锁

记录锁(record lock),所以一行记录,可分为:
共享锁
共享锁(Shared Lock,也称为读锁、S锁):多个事务可以同时获取同一行的共享锁,用于读取数据。共享锁之间不互斥,多个事务可以同时持有共享锁并进行读操作,但无法同时持有排他锁或修改锁。

1)在 SERIALIZABLE 隔离级别下,默认帮读操作加共享锁;
2)在 REPEATABLE READ 隔离级别下,需手动加共享锁,可解决幻读问题;
3)在 READ COMMITTED 隔离级别下,没必要加共享锁,采用的是 MVCC;
4)在 READ UNCOMMITTED 隔离级别下,既没有加锁也没有使用MVCC;

排他锁

排他锁(Exclusive Lock,也称为写锁、X锁):只有一个事务可以获取同一行的排他锁,用于修改数据。排他锁与共享锁和其他排他锁互斥,一个事务持有排他锁时,其他事务无法获取共享锁或排他锁。

在4种隔离级别下,都添加了排他锁,事务提交或事务回滚后释放锁。

1.3.2 间隙锁

间隙锁(Gap Lock)用于锁定两个索引键之间的空隙(即不存在的值)。主要是锁范围,但不包含记录本身,是全开区间。RR级别及以上支持。

当一个事务执行范围查询时,可能存在其他事务在查询结果范围内插入新行或修改已有行。为了防止幻读(Phantom Read)的情况发生,间隙锁可以锁定这些空隙,以确保数据的一致性。
(解决了快照读的幻读问题。但对于当前读,仍需要手动加锁 )

1.3.3 临键锁

临键锁(Next-Key Lock),记录锁 与 间隙锁的组合,用于锁范围和记录。包含记录本身是左开右闭区间。RR级别及以上支持,解决了幻读问题。

1.3.4 插入意向锁

插入意向锁(Insert Intention Lock)是一种间隙锁形式的意向锁,在insert 操作的时候产生。在多事务同时写入不同数据至同一索引间隙的时候,并不需要等待其他事务完成,不会发生锁等待。

假设有一个记录索引包含键值 4 和 7,两个不同的事务分别插入5 和 6,每个事务都会产生一个加在 4-7 之间的插入意向锁,获取在插入行上的排它锁,但是不会被互相锁住,因为数据行并不冲突。

1.4 锁的兼容性

1.4.1 兼容性1

SXISIXAI
S兼容冲突兼容冲突冲突
X冲突冲突冲突冲突冲突
IS兼容冲突兼容兼容兼容
IX冲突冲突兼容兼容兼容
AI冲突冲突兼容兼容冲突

由于 innodb 支持的是行级别的锁,意向锁并不会阻塞除了全表扫描以外的任何请求。
1)意向锁之间是互相兼容的。
2)IS 只对排他锁不兼容。
3)当想为某一行添加 S 锁,先自动为所在的页和表添加意向锁IS,再为该行添加 S 锁。
4)当想为某一行添加 X 锁,先自动为所在的页和表添加意向锁IX,再为该行添加 X 锁。
5)当事务试图读或写某一条记录时,会先在表上加上意向锁,然后才在要操作的记录上加上读锁或写锁。这样判断表中是否有记录加锁就很简单了,只要看下表上是否有意向锁就行了。意向锁之间是不会产生冲突的,也不和 AUTO_INC 表锁冲突,它只会阻塞表级读锁或表级写锁,另外,意向锁也不会和行锁冲突,行锁只会和行锁冲突。

1.4.2 兼容性2

Gap 持有Insert Intention 持有Record 持有Next-key 持有
Gap 请求兼容兼容兼容兼容
Insert Intention 请求冲突兼容兼容冲突
Record 请求兼容兼容冲突冲突
Next-key 请求兼容兼容冲突冲突

横向:表示已经持有的锁;纵向:表示正在请求的锁;

一个事务已经获取了插入意向锁,对其他事务是没有任何影响的;

一个事务想要获取插入意向锁,如果有其他事务已经加了 gap lock 或 Next-key lock 则会阻塞;这个是重死锁之源;

二、锁的CUDP

2.1 查询

  • MVCC:undo log 实现历史版本记录
  • S 锁:lock in share mode
  • X 锁:for update
  • 不做任何处理:读未提交隔离级别使用的策略

2.2 删除、更新

自动添加 X 锁

2.3 插入

  • 插入意向锁:特殊的间隙锁,同时会使用 X 锁。
  • 自增锁:特殊表锁实现

三、锁的对象

行级锁是针对表的索引加锁,索引包括聚集索引和辅助索引。

表级锁是针对页或表进行加锁。

重点讨论 InnoDB 在 read committed 和 repeatable read 级别下锁的情况。

假设存在如下的students 表作为实例,其中 id 为主键,no(学号)为辅助唯一索引,name(姓名)和 age(年龄)为二级非唯一索引,score(学分)无索引。

idnonameagescore
15S0001Bob2534
18S0002Alice2477
20S0003Jim245
30S0004Eric2391
37S0005Tom2222
49S0006Tom2583
50S0007Rose2389

1)聚集索引,查询命中: UPDATE students SET score = 100 WHERE id = 15;
命中就进行写操作,也就是加X锁
在这里插入图片描述
2)聚集索引,查询未命中: UPDATE students SET score = 100 WHERE id = 16;
未命中,加gap锁。RC级别没有gap锁,不加。
RR级别加在(15,18)间加gap锁,阻止其他事务在这个区间操作(修改、插入),避免幻读。
在这里插入图片描述
3)辅助唯一索引,查询命中: UPDATE students SET score =100 WHERE no = ‘S0003’;
在这里插入图片描述
4)辅助唯一索引,查询未命中: UPDATE students SET score = 100 WHERE no = ‘S0008’;
在这里插入图片描述
5)辅助非唯一索引,查询命中: UPDATE students SET score = 100 WHERE name = ‘Tom’;

RR级别下,可重复读,因此需要加gap锁,阻止其他事务再插入Tom
在这里插入图片描述
6)辅助非唯一索引,查询未命中: UPDATE students SET score = 100 WHERE name = ‘John’;
RR级别,按字典顺序,在JIm和Rose间插入gap锁
在这里插入图片描述
7)无索引: UPDATE students SET score = 100 WHERE score = 22;
在无索引的情况下,全表查询,按扫描顺序,逐行加锁,效率最低。
在这里插入图片描述

8)聚集索引,范围查询: UPDATE students SET score = 100 WHERE id <= 20;
在这里插入图片描述

9)辅助索引,范围查询: UPDATE students SET score = 100 WHERE age <= 23;
在这里插入图片描述
注意:事务对聚集索引 B+ 树的范围查询是按序的,不会有死锁。但是对于辅助索引 B+ 树的修改却不一定有序,可能会导致死锁。比如事务A加锁顺序1、2,事务B加锁顺序2、1

10)修改索引值: UPDATE students SET name = ‘John’ WHERE id = 15;
在这里插入图片描述

四、并发死锁

死锁:两个或两个以上的事务在执行过程中,因争夺锁资源而造成的一种互相等待的现象。
MySQL 中采用 wait-for graph(等待图-采用非递归深度优先的图算法实现)的方式来进行死锁检测。

4.1 相反加锁顺序导致死锁

这种情况下有两种原因造成死锁:
1)不同表的加锁顺序相反

2)相同表不同行加锁顺序相反
相同表不同行加锁顺序相反造成死锁有很多变种,其中容易忽略的是给辅助索引行加锁的时候,同时会给聚集索引行加锁;同时还可能出现在外键索引时,给父表加锁,同时隐含给子表加锁;触发器同样如此,这些都需要视情况分析。

解决办法是:调整加锁顺序;

4.2 锁冲突导致死锁

innodb 在 RR 隔离级别下,最常见的是插入意向锁与 gap 锁冲突造成死锁;主要原理为:一个事务想要获取插入意向锁,如果有其他事务已经加了 gap lock 或 Next-key lock 则会阻塞;

解决办法是:更换语句或者降低隔离级别;

4.3 如何避免死锁

1)尽可能以相同顺序来访问索引记录和表

2)如果能确定幻读和不可重复读对应用影响不大,考虑将隔离级别降低为 RC

3)添加合理的索引,不走索引将会为每一行记录加锁,死锁概率非常大

4)尽量在一个事务中锁定所需要的所有资源,减小死锁概率

5)避免大事务,将大事务分拆成多个小事务;大事务占用资源多,耗时长,冲突概率变高

6)避免同一时间点运行多个对同一表进行读写的概率;

4.4 例子

例1

DROP TABLE IF EXISTS `account_t`;
CREATE TABLE `account_t` (`id` INT(11) NOT NULL,`name` VARCHAR(255) DEFAULT NULL,`money` INT(11) DEFAULT 0,PRIMARY KEY (`id`),KEY `idx_name` (`name`)
)ENGINE = INNODB AUTO_INCREMENT=0 DEFAULT CHARSET = utf8;INSERT INTO `account_t` VALUES (1, 'C', 1000),(2, 'B', 1000),(3, 'A', 1000);SELECT * FROM account_t;-- 相反加锁顺序死锁1
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN
-- 死锁事务1
UPDATE `account_t` SET `money` = `money` - 100 WHERE `id` = 1;
-- 死锁事务2
-- UPDATE `account_t` SET `money` = `money` - 100 WHERE `id` = 2;
-- 死锁事务1
UPDATE `account_t` SET `money` = `money` + 100 WHERE `id` = 2;
-- 死锁事务2
-- UPDATE `account_t` SET `money` = `money` - 100 WHERE `id` = 1;rollback

例2

DROP TABLE IF EXISTS `dl_mark_t`;
CREATE TABLE `dl_mark_t` (`a` INT(11) NOT NULL DEFAULT '0',`b` INT(11) DEFAULT NULL,PRIMARY KEY (`a`),UNIQUE KEY `uk_b` (`b`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;INSERT INTO `dl_mark_t` VALUES (1,1),(5,4),(20,20),(25,12);SELECT * from dl_mark_t;
-- 死锁情况 1
/*事务一在插入时由于跟事务二插入的记录唯一键冲突,所以对 b=10 这个唯一索引加 S 锁(Next-key)并处于锁等待,事务二再插入 b=9 这条记录,需要获取插入意向锁(lock_mode X locks gap before rec insert intention)和事务一持有的 Next-key 锁冲突,从而导致死锁。
*/
BEGIN
-- 死锁事务 2
insert into `dl_mark_t` values(26,10);  -- a=26不存在,插入的时候加了(26,+∞)的gap锁
-- 死锁事务 1
insert into `dl_mark_t` values(30,10);  -- 获取插入意向锁,与上面的gap锁冲突(记录唯一键冲突),对b加gap锁,进入锁等待,等待事务2释放gap锁
-- 死锁事务 2
insert into `dl_mark_t` values(40,9);   -- 获取插入意向锁,与事务1的gap锁冲突,对b加gap锁,进入锁等待,等待事务1释放gap锁-- 死锁情况 2
/*
1. 三个事务依次执行 insert 语句,由于 b是唯一索引,所以后两个事务会出现唯一键冲突。
但此时要注意的是事务一还没有提交,所以并不会立即报错。事务二和事务三为了判断是否出现唯一键冲突,
必须进行一次当前读,加的锁是 Next-Key 锁,所以进入锁等待。要注意的是,就算在 RC 隔离级别下,一样会加 Next-Key 锁,所以说出现 GAP 锁不一定就是 RR 隔离级别;
3. 事务一回滚,此时事务二和事务三成功获取记录上的 S 锁;
4. 事务二和事务三继续执行插入操作,需要依次请求记录上的插入意向锁(插入意向锁和 GAP 锁冲突,所以事务二等待事务三,事务三等待事务二,形成死锁。
*/
BEGIN
-- 死锁事务 1
insert into `dl_mark_t` values(27, 29);
-- 死锁事务 2
insert into `dl_mark_t` values(28, 29);
-- 死锁事务 3
insert into `dl_mark_t` values(29, 29);
-- 死锁事务 1
ROLLBACK;

注:本专栏知识点是通过<零声教育>的系统学习,进行梳理总结写下文章,对c/c++linux课程感兴趣的读者,可以点击链接,详细查看详细的服务器课程链接 。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/66435.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

三维可视化平台有哪些?Sovit3D可视化平台怎么样?

随着社会经济的发展和数字技术的进步&#xff0c;互联网行业发展迅速。为了适应新时代社会发展的需要&#xff0c;大数据在这个社会经济发展过程中随着技术的进步而显得尤为重要。同时&#xff0c;大数据技术的快速发展进程也推动了可视化技术的飞速发展&#xff0c;国内外各类…

老网工必备好物,分享15个网络监控神器

下午好&#xff0c;我的网工朋友。 近年来&#xff0c;随着虚拟、云和边缘网络的增加&#xff0c;网络监控工具已经显得越来越重要。 在当今大多数企业中&#xff0c;监控混合IT环境中的网络流量对于主动网络管理至关重要。 无论是检测行为异常、占用带宽、应对新威胁&#…

springboot使用configtree读取树形文件目录中的配置

文章目录 一、介绍二、演示环境三、项目演示1. 配置文件2. 导入配置3. 检测配置属性 四、应用场景五、源码解析1. ConfigTreeConfigDataLocationResolver2. ConfigTreeConfigDataLoader 六、总结 一、介绍 相信绝大多数使用springboot开发项目的朋友们在添加配置时&#xff0c…

对约瑟夫问题的进一步思考

约瑟夫问题重述&#xff1a; 在计算机编程的算法中&#xff0c;类似问题又称为约瑟夫环 约瑟夫环&#xff1a;N个人围成一圈&#xff0c;从第一个开始报数&#xff0c;第M个将被杀掉&#xff0c;最后剩下一个&#xff0c;其余人都将被杀掉。 例如N6&#xff0c;M5&#xff0…

[Mongodb 5.0]单机启动

安装完mongodb后&#xff0c;会自动生成下面两个目录(mongod.conf中设定的)&#xff0c;用来存放日志和数据 /var/lib/mongo (数据目录) /var/log/mongodb (日志目录) 要启动一个单机版的mongodb&#xff0c;一般有两种方式&#xff1a; 第一种启动方式&#xff1a;直接使用…

Spring 使用注解开发、代理模式、AOP

使用注解开发 在Spring4之后&#xff0c;要使用注解开发&#xff0c;必须要保证AOP的包导入了 项目搭建&#xff1a; 在配置文件中导入约束&#xff0c;增加注解支持 <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.spri…

双端列表 —— Deque 接口概述,使用ArrayDeque实现队列和双端队列数据结构

Deque接口简介 Deque译为双端队列&#xff0c;在双向都能作为队列来使用&#xff0c;同时可用作栈。Deque接口的方法是对称成比例的。 Deque接口继承Queue接口&#xff0c;因此具有Queue&#xff0c;Collection&#xff0c;Iterable的方法属性。 双端队列的工作原理 在常规队…

Springboot集成ip2region离线IP地名映射-修订版

title: Springboot集成ip2region离线IP地名映射 date: 2020-12-16 11:15:34 categories: springboot description: Springboot集成ip2region离线IP地名映射 1. 背景2. 集成 2.1. 步骤2.2. 样例2.3. 响应实例DataBlock2.4. 响应实例RegionAddress 3. 打开浏览器4. 源码地址&…

K8S系列一:概念入门

I. K8S概览 1.1 K8S是什么&#xff1f; K8S是Kubernetes的全称&#xff0c;官方称其是&#xff1a; Kubernetes is an open source system for managing containerized applications across multiple hosts. It provides basic mechanisms for deployment, maintenance, and …

uniapp开发小程序-分包(微信错误码:800051)

在使用uniapp开发小程序时&#xff0c;上传的时候因为文件过大&#xff0c;显示上传失败。 以下是开发过程中遇到的问题及解决方法&#xff1a; 1. 问题一&#xff1a;因为文件过大&#xff0c;显示上传失败 ①尝试过把本地使用的图片压缩到最小&#xff1b; ②把图片转换为网…

【Vue-Router】路由入门

路由&#xff08;Routing&#xff09;是指确定网站或应用程序中特定页面的方式。在Web开发中&#xff0c;路由用于根据URL的不同部分来确定应用程序中应该显示哪个内容。 构建前端项目 npm init vuelatest //或者 npm init vitelatest安装依赖和路由 npm install npm instal…

Python学习 -- 常用函数与实例详解

在Python编程中&#xff0c;数据转换是一项关键任务&#xff0c;它允许我们在不同数据类型之间自由流动&#xff0c;从而提高代码的灵活性和效率。本篇博客将深入探讨常用的数据转换函数&#xff0c;并通过实际案例为你展示如何巧妙地在不同数据类型之间转换。 数据类型转换函…