MySQL 锁机制全面解析

目录

  • 1. MySQL的锁类型
    • 1.1 全局锁
    • 1.2 表锁
    • 1.3 行锁
    • 1.4 共享锁(读锁)
    • 1.5 排它锁(写锁)
    • 1.6 死锁
  • 2 乐观锁和悲观锁
    • 2.1 乐观锁
    • 2.2 悲观锁
  • 3 意向锁
  • 4 间隙锁
  • 5 临键锁
  • 6. 事务隔离级别对锁的影响
    • 6.1 读未提交(Read Uncommitted)
    • 6.2 读已提交(Read Committed)
    • 6.3 可重复读(Repeatable Read)
    • 6.4 串行化(Serializable)
    • 6.5 为什么mvcc无法防止幻读

1. MySQL的锁类型

MySQL按锁的粒度分主要分为全局锁、表锁和行锁。锁类型分为共享锁和排它锁。

1.1 全局锁

全局锁是一种锁定机制,它可以对整个数据库或特定的资源进行锁定。

全局锁的作用是确保在特定的操作期间,防止其他并发操作对受保护的资源进行修改。这有助于维护数据的一致性完整性。用于全库备份、大规模数据迁移。

列举:

开启全局锁:FLUSH TABLES WITH READ LOCK(这是一种全局读锁,用于在执行一些全局操作时阻止其他写入操作。)
开启数据备份:mysqldump -uroot -p 数据库名 > /home/backup.sql
解除全局锁:unlock tables

1.2 表锁

MyISAM 存储引擎默认表锁、InnoDB 存储引擎默认行锁

在MySQL中,对MyISAM表的读操作,会自动加上读表锁,对MyISAM表的写操作,会自动加上写表锁。

InnoDB引擎在必要情况下会使用表锁,但主要是使用行锁来实现多版本并发控制(MVCC) ,它能提供更好的并发性能和更少的锁冲突。

innoDB存储引擎下行锁升级为表锁场景:

  • 查询涉及到索引范围扫描
 例如: SELECT * FROM orders WHERE order_id BETWEEN 100 AND 200虽然带有 BETWEEN 表达式的 SELECT 查询通常会使用行级锁,但是数量较大时会升级为表锁,因为InnoDB 认为多个行锁会占用过多资源。
  • 一整张表进行删除
DELETE FROM orders
  • 整张表进行更新
UPDATE orders SET status = 'processed'
  • 为整张表添加或者删除索引时,会对整张表加锁
ALTER TABLE Orders ADD INDEX (Column); -- 加锁整张表完成添加索引操作
  • 某些情况下,MySQL优化器会选择全表扫描,此时对全表加锁。例如,如果在 WHERE 或 JOIN 的 ON
    子句中使用了列的函数表达式,那么 InnoDB 存储引擎不能使用行锁实现高效的索引扫描,会退化为表级锁定。

  • 使用 LOCK TABLES 命令显式加表锁。

LOCK TABLES Orders WRITE; -- 显式锁定整张表
INSERT INTO Orders(ID, Total) VALUES (1, 100);
UNLOCK TABLES;

1.3 行锁

InnoDB存储引擎加锁 默认是行锁,例如 对某行数据添加共享锁、排它锁,都称为行锁

  • SELECT ... FOR UPDATE:这种查询会对选定的行添加一个排他锁(×锁),这意味着其他事务不能修改这些行,也不能对这些行添加共享锁。
  • SELECT ... LOCK IN SHARE MODE:这种查询会对选定的行添加一个共享锁(S锁) ,这意味着其他事务不能修改这些行,但可以对这些行添加共享锁。
  • INSERT:插入操作会对新添加的行添加一个排他锁(X锁)。
  • UPDATE:更新操作会对被更新的行添加一个排他锁(×锁) 。
  • DELETE:删除操作会对被删除的行添加一个排他锁(×锁) 。

注:同一个事务中,被加上行锁的数据可以被访问

.锁升级:如果一个事务试图锁定的行过多,InnoDB可能会将锁从行级升级为表级,这就可能导致更多的锁冲突。

1.4 共享锁(读锁)

共享锁优点是允许一个资源被多个事务读取,但不能被任何事务写入。

START TRANSACTION;
SELECT * FROM table_name WHERE id = 10 LOCK IN SHARE MODE;

这个查询会在’table_name’的’id=10’这一行上放置一个共享锁。起效后,任意数量的其他事务可以对此行设置共享锁并读取,但无法获取排它锁进行修改,直到你结束事务为止。

1.5 排它锁(写锁)

排它锁允许事务独占某个资源。其他事务无法进行读取和写入。

START TRANSACTION;
SELECT * FROM table_name WHERE id = 20 FOR UPDATE;

这个查询会在’table_name’的’id=20’这一行上放置一个排他锁。起效后,同一时间只有这个事务可以读取和修改这一行,所有其他尝试获取这行的锁(共享锁或排他锁)的操作都会被阻塞,直到你结束事务为止。

注:同一事务 中不同程度的锁可被升级,例如,先给某一行设置共享锁,然后在需要对此行进行修改时升级为排他锁。但是,一旦一个锁被设置,就无法在相同事务中降级。(在事务结束前,无法将排他锁降级为共享锁)。

1.6 死锁

在 MySQL 数据库中,死锁是指两个或多个事务相互等待对方所持有的资源,从而导致它们无法继续执行的情况。当发生死锁时,这些事务将会无限期地相互等待,除非通过干预来解决死锁。

下面是一些可能导致死锁的情况:

  • 事务顺序交叉:当多个事务同时访问数据时,如果它们的操作序列交叉且相互依赖,可能会导致死锁。例如,事务 A 锁定资源 X,并等待锁定资源Y,而事务 B 正好相反,这将导致两个事务相互等待对方的锁而发生死锁。
  • 索引顺序不一致:当多个事务以不同的顺序访问相同的资源时,可能会引发死锁。例如,事务 A 首先锁定资源 X,再锁定资源 Y;而事务 B先锁定资源 Y,再锁定资源 X。这种情况下,如果事务 A 和事务 B 同时执行,就有可能发生死锁。
  • 长时间事务:如果一个事务持有锁的时间过长,其他事务可能会因为等待所需资源被阻塞,从而导致死锁。长时间运行的事务可能会导致锁等待链条的增长,增加死锁的风险。
  • 锁粒度过高:当锁的粒度过高时,可能会增加发生死锁的可能性。例如,如果对整个表进行锁定而不是仅锁定需要的行或记录,就会增加死锁的概率。
  • 并行执行:当多个并发事务同时执行时,由于竞争相同资源而可能导致死锁。如果没有正确处理并发访问,例如使用适当的锁定机制和事务隔离级别,就会增加死锁的风险。

为了处理或避免死锁,可以采取以下几种方法:

  • 设置适当的事务隔离级别,确保事务之间的隔离性。
  • 尽量缩小事务持有锁的时间,减少锁冲突的可能性。
  • 使用合适的索引,以避免全表扫描或不必要的锁定。
  • 对事务执行顺序进行优化,以减少死锁的可能性。
  • 监控和检测死锁,并及时处理解决。

2 乐观锁和悲观锁

2.1 乐观锁

乐观地认为并发访问不会造成数据冲突,只在更新时检查是否有冲突。乐观锁和CAS的关系可以用“乐观锁是一种思想,CAS是一种具体的实现”来理解。

当使用CAS操作修改数据时,如果版本号不匹配或者其他线程已经修改了要操作的数据,CAS会返回失败。这时候,程序可以再次尝试CAS操作,也就是进行自旋重试,直到CAS操作成功。

因此,CAS操作已经内置了自旋重试的机制,避免了使用额外的自旋锁。

适用场景:适用于并发较低(高并发场景每次修改了去对比,还不如让加锁阻塞排队执行)、读多写少的场景,相信数据多数情况下不会发生冲突,只在更新时进行检查,以减少对共享资源的争用。

java中常见悲观锁实现:可以使用java.util.concurrent.atomic包中的原子类,比如AtomicInteger、AtomicLong等,来实现CAS操作。

mysql实现乐观锁:版本号、时间戳

2.2 悲观锁

悲观地认为并发访问会造成数据冲突,因此在访问共享资源之前就会进行加锁,确保同一时刻只有一个线程能够访问。

适用场景:适用于高并发写多的场景,通过加锁保护共享资源,确保并发访问时不会造成数据不一致性。

java中常见悲观锁实现:synchronized 关键字、ReentrantLock(可重入锁)

mysql中实现悲观锁SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODE

3 意向锁

意向锁是表锁,用于表明某个事务有意锁定某个表中的某些行或区间。主要用于协调行锁和表锁的关系,以优化InnoDB的加锁策略。意向锁的主要目的是为了提高并发性能并减少锁冲突。

作用:当有事务A有行锁时, MySQL会自动为该表添加意向锁,事务B如果想申请整个表的写锁,那么不需要遍历每一行判断是否存在行锁,而直接判断是否存在意向锁,增强性能。

意向共享锁:给行数据添加共享锁(读锁)时,会自动给表添加意向共享锁。例: SELECT ... LOCK IN SHARE MODE
意向排它锁:给行数据添加排它锁(写锁)时,会自动给表添加意向排它锁。例: SELECT ... FOR UPDATE

两个事务间锁的兼容性:

事务A/事务B意向共享锁(IS)意向排他锁(IX)
共享锁(S)兼容互斥
排它锁(X)互斥互斥

4 间隙锁

间隙锁是MySQL InnoDB存储引擎RR隔离级别引入的一种解决幻读的锁机制。它锁定的不是具体的行记录,而是两个索引之间一个左开右开的区间 ,这样可以防止新的记录插入到该间隙,确保数据的一致性和事务的隔离性。间隙锁之间不互斥,但是间隙锁和插入语句互斥

间隙锁的存在,主要是为了解决幻读问题。因此间隙锁只存在于可重复读(RR)隔离级别下。

产生间隙锁方式:普通索引锁定、多列唯一索引、唯一索引锁定多行记录。

表结构如下,其中id是主键索引,age是普通索引
在这里插入图片描述
age对应的间隙锁就是(-,20),(20,22),(22,27),(27,28),(28,+)

举例 1:

// 事务 A,则会主动给(20,22)这个区间加间隙锁,(20,22]临键锁,由于是普通索引会上升到主键索引。
START TRANSACTION;
SELECT * FROM `user` where  age =23 for update;

可以用:SELECT * FROM performance_schema.data_locks; 查看加锁情况,但不会显示间隙锁
在这里插入图片描述

rr级别下间隙锁引起的死锁:1 降低隔离级别到rc,2 分布式锁

5 临键锁

临键锁是由间隙锁记录锁(行锁)组成的一种特殊锁,是一个左开右闭的索引区间。也是为了解决幻读问题,只存在于非唯一索引

6. 事务隔离级别对锁的影响

MySQL InnoDB 支持四种异步隔离级别:READ UNCOMMITTED(读未提交)、READ COMMITTED(读已提交)、REPEATABLE READ(可重复读)和 SERIALIZABLE(串行化)。不同的隔离级别用不同的方法处理并发事务,以满足您的特定需求。

  • READ UNCOMMITTED:在此隔离级别下,事务可以读取未提交的数据。
  • READ COMMITTED:此隔离级别下,只能读取已经提交的数据。
  • REPEATABLE READ:在此隔离级别下,同一事务中的多次读取可以看到同一份数据。这是 MySQL 默认的隔离级别。
  • SERIALIZABLE:这是最高的事务隔离级别,它要求所有事务顺序执行。

如果需要了解mysql事务和隔离级别,可以参考:MySQL事务的四个特征(ACID)以及隔离级别

6.1 读未提交(Read Uncommitted)

该隔离级别允许事务读取未提交的数据,但在修改数据时仍然会加锁,防止其他事务同时对同一条数据进行修改,从而避免"脏写"。事务可以读取未提交的数据(即,脏读)。select 不加锁,update delete insert 会加行锁(排它锁)

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
SELECT * FROM Orders WHERE CustomerID = 1; -- 可以读取到未提交的数据

6.2 读已提交(Read Committed)

只能读取已经提交的数据读操作不会使用锁,写操作则会加上行锁,并且只有在事务提交后才释放。select 不加锁,update delete insert 会加行锁(排它锁)

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
UPDATE Orders SET Total = Total + 100 WHERE CustomerID = 1; -- 对匹配的行加上排他锁

6.3 可重复读(Repeatable Read)

这是 MySQL 默认的隔离级别。在此隔离级别中,同一事务中的多次读取可以看到同一份数据。读操作(不使用 FOR UPDATE 或 LOCK IN SHARE MODE 句法)不会使用锁,写操作会加上行锁,并在事务结束后释放。select 不加锁,update delete insert 会加行锁(排它锁)

SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
INSERT INTO Orders(CustomerID, Total) VALUES (1, 100); -- 加上排他锁,其他事务无法修改这一行,直到本事务结束

那么可重复读是如何解决多次读取的是同一份数据呢?,这是因为可重复读的实现主要依赖于 MySQL 的多版本并发控制(MVCC)。当一个事务开始时,数据库的当前状态会被记录下来,随后在这个事务中的所有读操作,看到的都是这个存储的版本,而不是最新的数据。
比如:

SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT * FROM Orders WHERE CustomerID = 1;
-- 假设在此时,另一个事务修改了 CustomerID 为 1 的这条数据的值并且提交了
SELECT * FROM Orders WHERE CustomerID = 1; -- 在同一事务中,尽管另一个事务已经修改了数据,但你仍然能看到第一次查询的结果。因为你看到的是事务开始时的那个版本的数据。
COMMIT;

6.4 串行化(Serializable)

在串行化(Serializable)隔离级别下,事务会在读取的时候也设置锁, 并且会按顺序排队,类似于队列,即并发多个当前行的查询会排序一个一个执行。select 加行锁(共享锁),update delete insert 会加行锁(排它锁)

串行化级别可以解决幻读的问题。“幻读” 是指一个事务在读取了几行数据后,另一个并发事务插入了一些符合其查询条件的新行,如果前一个事务再次读取,会发现一些“从未存在”的新行,这些新行被称为"幻读"。

串行化通过锁住整张表,阻止其他事务插入新的行,从而防止了幻读的发生。

在串行化中,读数据会加上共享锁,增删改会加上排它锁。至于是否从行锁上升到表锁在于是否需要全表查询。同时除了锁定,串行化还遵循一个重要的规则——所有的操作都必须按照某种确定的顺序执行,即事务必须按照它们的提交顺序来串行执行,以确保数据的一致性和事务的隔离性。在这种级别下,即使是并发的事务,也会被数据库系统排队,按照提交顺序一个接一个地处理。这就是“串行化”一词的来源。

SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
DELETE FROM Orders WHERE CustomerID = 1; -- 对匹配的行加上排他锁,并阻塞其他所有事务SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
SELECT COUNT(*) FROM Orders WHERE Total > 100; --假设返回10
-- 另一个事务插入了一个Total > 100的新订单并且提交
SELECT COUNT(*) FROM Orders WHERE Total > 100; --串行化下,此次查询会阻塞,等待插入新订单的事务完成。事务完成后,新的查询结果会返回新插入的行,即返回11
COMMIT;

6.5 为什么mvcc无法防止幻读

“幻读"是指在一个事务内读取某范围的记录时,另一个事务在此范围内插入了新的记录,当前事务在此范围内再次读取时,会发现一些之前不存在的记录,这就是"幻读”。

即使使用了 MVCC(多版本并发控制)以在同一事务内保持数据的一致性,通过多版本并发控制(MVCC)可以创建查询时点的数据快照,使得在一个事务内多次读取数据时始终能够得到一致的结果。"幻读"问题仍然可能发生,因为 MVCC 主要解决的是"非重复读"的问题,也就是防止一个事务读取到在此期间其他事务提交的数据。

然而,"幻读"是由其它事务提交了新的数据引起的。仅仅依靠 MVCC 是无法解决"幻读"问题的,因为在事务开始后,新提交的数据仍然可能影响到同一事务后续的查询结果。

例如:

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;-- 初始读取
SELECT * FROM Orders WHERE Total > 1000;-- 此时,另一个事务插入了一个 Total > 1000 的记录并提交-- 再次读取
SELECT * FROM Orders WHERE Total > 1000;  -- 这时会发现一个新的记录,这就是幻读COMMIT;

为了解决幻读问题,需要升级到串行化(SERIALIZABLE)隔离级别。该隔离级别会在读取的数据范围上放置共享锁,防止其他事务插入新的行,从而解决了幻读问题。但这种方式的代价是并发性能的下降。

InnoDB的行锁是针对索引加的锁,不是针对记录加的锁。并且该索引不能失效,否则都会从行锁升级为表锁(也就是where后的条件)

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

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

相关文章

3ds Max2024安装包(亲测可用)

目录 一、软件简介 二、软件下载 一、软件简介 3ds Max是一款基于PC系统的强大3D建模、渲染和制作软件,广泛应用于游戏开发、影视后期制作、建筑设计、工业设计等多个领域。其拥有丰富的建模工具,可轻松创建逼真的三维场景和模型;同时&#…

OpenMesh 极小曲面(局部迭代法)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 我们的目标是想得到一个曲率处处为0的曲面,具体操作如下所述: 二、实现代码 #define _USE_MATH_DEFINES #include

Linux 操作系统gdb、makefile

今天是对前面两天的补充和完善。 1、gdb 1.1 gdb 作用 调试程序 1.2 调试bug的步骤 测试:发现问题 固化:让bug重现 定位:找到bug的位置 修改:修改bug 验证 1.3 gdb调试工具的使用 1->想要使用gdb调试工具,在编…

【网站项目】自习室预约系统

🙊作者简介:拥有多年开发工作经验,分享技术代码帮助学生学习,独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。🌹赠送计算机毕业设计600个选题excel文件,帮助大学选题。赠送开题报告模板&#xff…

汽车充电桩充电效率的四大决定因素

随着电动汽车的快速普及,交流充电桩作为电动汽车的充电基础设施,其充电效率受到了广泛的关注。接下来,我们将深入探讨交流充电桩的充电效率,包括充电效率的定义、影响因素以及提升方法。 充电效率的定义 交流充电桩的充电效率指的…

【微信小程序从入门到精通(项目实战)】——微电影小程序

👨‍💻个人主页:开发者-曼亿点 👨‍💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍💻 本文由 曼亿点 原创 👨‍💻 收录于专栏&#xff1a…

2024华中杯C题完整解题思路及代码

C 题 基于光纤传感器的平面曲线重建算法建模 光纤传感技术是伴随着光纤及光通信技术发展起来的一种新型传感器技 术。它是以光波为传感信号、光纤为传输载体来感知外界环境中的信号,其 基本原理是当外界环境参数发生变化时,会引起光纤传感器中光波参量&…

Vue前端框架如何调用天聚数行API?

Vue是一个业内优秀的前端解决方案,采用了最新流行的MVVM框架模式,深受web前端开发者的喜爱,在相关开发者社区也一直都是热门交流话题。那么,如何通过Vue调用天行数据的API接口呢,下面就具体给大家示例。 vue采用的是数…

数字零售力航母-看微软如何重塑媒体

数字零售力航母-看微软如何重塑媒体 - 从2024全美广播协会展会看微软如何整合营销媒体AI技术和AI平台公司 2024年,微软公司联合英伟达总司,赞助全美广播协会展会。本次展会微软通过搭建一个由全面的合作伙伴生态系统支持的可信和安全的平台,…

TCP和UDP协议的区别

1、定义 TCP协议的全称是Transmission Control Protocol(传输控制协议),是一种面向连接的点对点的传输层协议。 UDP协议的全称是User Datagram Protocal(用户数据报协议),为应用程序提供一种无需建立连接…

手机从0基础到精通拍摄,拍摄手法0基础教学,快速入门

课程下载:https://download.csdn.net/download/m0_66047725/89064797 更多资源下载:关注我。 课程内容: 1 为什么学拍摄剪辑.mp4 2 如何选择手机.mp4 3 补光灯的作用.mp4 !三角支架的作用.mp4 5 麦克风的作用.mp4 6 帧率清晰度参数.m…

Hive进阶

目录 一、MapReduce的计算过程 二、Yarn的资源调度 yarn的资源调度策略 三、Hive的语法树 四、Hive配置优化 五、数据开发 六、数据仓库 七、数据仓库开发流程 八、数仓分层 九、ETL和ELT 一、MapReduce的计算过程 分布式计算框架 需要编写代码执行,执行时…

Edge的使用心得与深度探索:优化浏览体验的技巧与建议

随着互联网的快速发展,浏览器已经成为我们日常生活中不可或缺的工具之一。在众多浏览器中,微软Edge凭借其稳定性、安全性和功能丰富性备受用户青睐。本文将深入探讨Edge浏览器的各种功能与技巧,帮助用户优化其浏览体验。 文章目录 Edge翻译插…

iOS 全平台矢量动画库:体积小巧、功能丰富 | 开源日报 No.227

airbnb/lottie-ios Stars: 24k License: NOASSERTION lottie-ios 是一个用于在 iOS 平台上本地渲染 After Effects 矢量动画的库。 该项目主要功能、关键特性、核心优势包括: 跨平台支持:可在 iOS, macOS, tvOS, visionOS, Android 和 Web 上使用实时渲…

ASP.NET医院手麻信息系统源码 .NET6.0+VUE

目录 麻醉记录单 复苏记录单 麻醉文书 手术麻醉信息 1、 体征监控记录 2、 麻醉用药信息 3、 手术事件登记 4、 手术状态变更 5、 麻醉医师交接 6、 其他辅助操作 手麻信息系统是以服务围术期临床业务工作的开展为核心,通过与床边监护设备以及医院H…

OpenCV从入门到精通实战(五)——dnn加载深度学习模型

从指定路径读取图像文件、利用OpenCV进行图像处理,以及使用Caffe框架进行深度学习预测的过程。 下面是程序的主要步骤和对应的实现代码总结: 1. 导入必要的工具包和模型 程序开始先导入需要的库os、numpy、cv2,同时导入utils_paths模块&…

Objective-C网络数据捕获:使用MWFeedParser库下载Stack Overflow示例

概述 Objective-C开发中,网络数据捕获是一项常见而关键的任务,特别是在处理像RSS源这样的实时网络数据流时。MWFeedParser库作为一个优秀的解析工具,提供了简洁而强大的解决方案。本文将深入介绍如何利用MWFeedParser库,以高效、…

【学习笔记二十】EWM TU运输单元业务概述及后台配置

一、EWM TU运输单元业务流程概述 TU是指车辆和运输单元在货场中,移动车辆或运输单元。 车辆是特定运输方式的专用化工具,车辆可以包含一个或多个运输单元,并代表运输车辆的实际实体。 运输单元是用于运输货物的车辆的最小可装载单位,运输单元可以是车辆的固定部分。 …

高仿小米商城用户端

高仿小米商城用户端(分为商城前端(tongyimall-vue)和商城后端(tongyimall-api)两部分),是Vue SpringBoot的前后端分离项目,用户端包括首页门户、商品分类、首页轮播、商品展示、商品推荐、购物车、地址管理、下订单、扫码支付等功能模块。 …

AndroidStudio 导出aar包,并使用

打包 1、确认当前选项是否勾选,如未勾选请先勾选。 2、勾选完成后重启Android Studio。 3、重启完成后,选中要打包的module 4、打包完成 使用 1.在项目中新建libs,放入aar文件。 2.修改配置 添加如下代码 flatDir {dirs("libs")}3.修改app…