解析 MySQL 锁机制:共享锁、排它锁、间隙锁、意向锁等,保障数据安全与高并发的秘密武器

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云解析 DNS,旗舰版 1个月
简介: 解析 MySQL 锁机制:共享锁、排它锁、间隙锁、意向锁等,保障数据安全与高并发的秘密武器

前言

MySQL 锁机制比较显而易见,其最显著的特点是不同的存储引擎支持不同的锁机制

MySQL InnoDB 锁机制官方文档

比如在 MyISAM、Memory 存储引擎采用的是表级锁(table- level locking)InnoDB 存储引擎既支持行级锁(row-level locking)也支持表级锁,但默认情况下是采用行级锁

  • 表锁:开销小、加锁快,不会发生死锁,锁定的粒度大,发生锁冲突的概率最高,并发度最低
  • 行锁:开销大、加锁慢,会发生思索,锁定的粒度最小,发生锁冲突的概率最小,并发度最高

从锁的角度来看,表锁更适合以查询为主,只有少量按索引条件更新数据的应用,如 PC Web 后台应用;行级锁则更适合有大量按索引条件并发更新少量的数据,同时也有并发查询的应用,如在线事务处理(OLTP)系统、小程序 C 端、App C 端系统等

并发事务问题

并发事务处理能大大增加数据库资源的利用率,提高数据库系统的事务吞吐量,从而可以支持更多用户的并发操作,但与此同时,会发生脏读、不可重复读、幻读问题

解决这种问题一般有两种可选的方案,如下:

  1. 读操作 MVCC,写操作进行加锁 > 事务利用 MVCC 进行读取称之为一致性读或快照读、无锁读,一致性读并不会对表中任何记录作加锁操作,其他事务可以自由对表中记录作改动

采用 MVCC 方式,读、写操作彼此不冲突,性能更高,采用加锁的方式,读、写操作彼此需要排队执行,从而影响性能;但是在某些情况下,还是需要采用加锁的方式去执行,比如:要通过间隙锁来解决不可重复读隔离级别的幻读问题

  1. 读、写操作进行加锁 > 适用场景:业务场景不允许读取记录的旧版本,每次都必须读取记录的最新版本

比如在银行存款事务中,需要先把账户余额读取出来,然后再将其加上本次存款的金额,最后再写入到数据库中。再将账户余额读取出来的这个过程,不想让其他的事务再访问此余额,直到本次存款事务执行完成,其他事务才可以访问账户的余额。

此场景意味着读取数据时也要进行加锁操作,读、写操作并行执行,也要像写写操作那样依此排队执行

关于 MySQL MVCC 机制更多内容,可以阅读此文章:基于 MySQL 事务、隔离级别及 MVCC 机制详细剖析

锁分类

共享锁:Shared Locks,简称 S 锁,属于行锁

排它锁:Exclusive Locks,简称 X 锁,属于行锁

意向锁:Intension Locks,意向共享锁+意向排它锁的组合

意向共享锁:Intension Shared Locks,简称 IS 锁,属于表锁

意向排它锁:Intension Exclusive Locks,简称 IX 锁,属于表锁

自增锁:AUTO-INC Locks,在处理自增长列时的锁定行为

临键锁:Next-Key Locks,记录锁+间隙锁的组合

记录锁:Record Locks,仅仅把一条记录上锁

间隙锁:Gap Locks,对索引前后的间隙上锁,不对索引本身上锁,简称 Gap 锁

锁定读

锁定读(LockingReads)也称为当前读 LBCC(基于锁的并发控制 > Lock-Based Concurrency Control)读取的是最新版本,对读取的记录加锁,阻塞其他事务同时改动相同的记录,避免数据安全问题

共享锁:lock in share mode、排它锁:for update、update、delete

通过以下 SQL 语句,进行共享锁、排它锁的案例演示:

CREATE TABLE student (
  id INT ( 10 ) PRIMARY KEY auto_increment,
  `name` VARCHAR ( 20 )
) ENGINE = INNODB;
INSERT INTO student VALUES 
( 1, 'vnjohn' ),
( 2, 'zhangsan' ),
( 3, 'lisi' ),
( 4, 'wangwu' );

共享锁

多个事务对同一条数据可以共享一把锁,但只能读不能写

会话 vnjohn-transaction1:

begin;
select * from student where id=1 lock in share mode;

会话 vnjohn-transaction2:

begin;
# 读取数据没有问题
select * from student where id=1; 
# 注意:无法修改会卡死,
# 当会话 1 commit 提交事务之后,会立刻修改成功
update student set name ='vnjohn' where id=1;

当会话 1 执行查询 + 了共享锁,会话 2 对该条记录进行更新操作,会阻塞住,直到会话 1 事务提交,才会立刻修改成功,假如会话 1 出现慢 SQL 数据一直查询不出来,那么就会出现错误: ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

总之,共享锁,读、读操作不互斥,读、写操作互斥

排它锁

排它锁不能与其他锁并存,若一个事务获取一个数据行的排它锁,其他事务就不能再次获取该行的其他锁,只有获取了该数据行的排它锁所在事务才能对数据进行读取、写入操作

update、delete 语句默认就是排它锁

会话 vnjohn-transaction1:

begin;
select * from student where id=1 for update

会话 vnjohn-transaction2:

begin;
select * from student where id=1 for update;
select * from student where id=1 lock in share mode;

当会话 1 执行查询 + 了排它锁,会话 2 对该条记录加排它锁、共享锁操作,都会被阻塞住,直到会话 1 事务提交,才会加锁成功

意向排它、意向共享锁

意向锁是一种粒度更粗的锁,用于协调并发事务对表和表中行的锁定。它们并不直接锁定行,而是指示事务在某个层次上有意向获取特定类型的锁。意向锁的引入可以减少冲突,提高并发性能

意向排它锁:表示事务有意向在某个表或表分区上获取排它锁(Exclusive Lock)一个事务在获取某个表的排它锁之前,必须先获取该表的意向排它锁

意向共享锁:表示事务有意向在某个表或表分区上获取共享锁(Shared Lock)多个事务可以同时持有同一个表的意向共享锁,但在获取某个表的排它锁之前,必须先释放该表的意向共享锁

意向锁的引入有助于优化锁定算法,避免了不必要的冲突,提高了并发性能。在事务操作过程中,当需要获取某个表的排它锁或共享锁时,先检查是否存在对应的意向锁,以减少对其他事务的干扰

意向锁是隐式获取和释放的,并不需要显式的锁定语句来处理,它们是由 InnoDB 存储引擎自动管理的

自增锁

Auto-Increment:自增长列的特殊锁机制,通过 innodb_autoinc_lock_mode 参数配置自增长列的锁定模式,它决定了在插入数据时,如何对自增长序列进行锁定

innodb_autoinc_lock_mode 该参数有几个可选值,如下:

  1. 0(Traditional): 表示使用传统的自增锁定方式,在插入数据时,会对整个表进行排它锁定,以防止并发插入导致自增值的冲突

插入语句在执行前不可以确定具体要插入多少条记录(无法预计即将插入记录的数量),比方说使用 INSERT … SELECT、REPLACE … SELECT 或者 LOAD DATA 这种插入语句,一般是使用 AUTO-INC 锁为 AUTO_INCREMENT 修饰的列生成对应的值

  1. 1(Consecutive):表示使用连续模式的自增锁定,在插入数据时,只会对自增长索引的最后一个插入行进行排它锁定,而不是整个表
  2. 2(Interleaved):表示使用交错模式的自增锁定,在插入数据时,会对自增长索引的最后一个插入行进行共享锁定,而不是排它锁定。这允许多个事务并发地插入数据,提高并发性能

使用交错模式时,会导致自增值的顺序会被打乱,虽然提高了事务的并发性,但自增列的值顺序可能会被打乱,因为插入行的锁定顺序可能不是它们实际插入时的顺序

在主从复制场景下时,当 binlog_format 配置为 statement 以语句的方式存储,会造成 slave 同步 master 节点数据回放时产生错乱

在 innodb_autoinc_lock_mode 参数中,传统模式(Traditional)使用排它锁(Exclusive Lock)对整个表进行锁定;连续模式(Consecutive)只对自增长索引的最后一个插入行进行排它锁定;交错模式(Interleaved)则使用共享锁(Shared Lock)对自增长索引的最后一个插入行进行锁定

一般该参数默认值为 1:连续模式,既保证了自增值的顺序性,在插入性能上面又高于 0 传统模式

show variables like 'innodb_autoinc_lock_mode' ;

记录锁

官方类型名称:LOCK_REC_NOT_GAP,记录锁,只对一条记录进行上锁,比方说:

select * from student where id=1 for update;
select * from student where id=1 lock in share mode;

隐私锁定:delete from student where id=1、update student set name = ‘’ where id=1

记录锁也是有 S、X 锁之分的,当一个事务获取了一条记录的 S 锁后,其他事务仍然可以继续获取该记录的 S 锁,但不可以获取该记录的 X 锁;当一个事务获取一条记录的 X 锁后,其他事务既不可以获取该记录的 S 锁,也不可以获取该记录的 X 锁

间隙锁

Gap Lock 为了防止其他事务在一个范围内插入新的记录而引入的一种锁机制。通过生成间隙锁,可以确保其他事务无法在已有记录之间插入新记录,从而维护数据的一致性和完整性

生成间隙锁对于维护数据的一致性、避免幻读等问题非常重要

会话 vnjohn-transaction1:

begin;
select * from student where id between 4 and 6 for update;

会话 vnjohn-transaction2:

begin;
insert into student values(5,'wangwu');

当会话 1 执行查询使用了索引间隙锁 > 主键索引:4~6,会话 2 插入一条主键为 5 的数据,会被阻塞住,直到会话 1 事务提交,会话 2 才会插入成功

InnoDB 行锁模式及加锁方法

InnoDB 行锁通过给索引上的索引项加锁来实现的,Oracle 是通过在数据块中相应数据行加锁来实现的,而 MySQL 则不同,只有通过索引条件检索数据,InnoDB 才使用行级别锁,否则,InnoDB 会使用表级别锁

在不通过索引条件查询时,InnoDB 使用的是表锁而不是行锁,用以下建表语句举例:

# 建立一张无索引的表
create table tab_no_index(
  id int,
  name varchar(10)
) engine=innodb;
# 插入表数据
insert into tab_no_index values
(1,'1'),
(2,'2'),
(3,'3'),
(4,'4');

会话 vnjohn-transaction1 会话 vnjohn-transaction2
begin;
select * from tab_no_index where id=1;
begin;
select * from tab_no_index where id=2;
select * from tab_no_index where id=1 for update;
select * from tab_no_index where id=2 for update;

当会话 1 只给其中一行数据加了排它锁,但是会话 2 在请求其他行的排它锁时,会出现锁等待;原因是在没有索引的情况下,InnoDB 只能使用表锁

更新、删除操作无须手动加锁,默认会给数据+上排它锁,一样会出现锁等待。

通过带索引条件查询时,InnoDB 使用的是行锁,用以下建表语句举例:

create table tab_with_index(
  id int,
  name varchar(10)
) engine=innodb;
# 建立 id 列索引
alter table tab_with_index add index id(id);
# 插入数据
insert into tab_with_index values
(1,'1'),
(2,'2'),
(3,'3'),
(4,'4');

会话 vnjohn-transaction1 会话 vnjohn-transaction2
begin;
select * from tab_with_index where id=1;
begin;
select * from tab_with_index where id=2;
select * from tab_with_index where id=1 for update;
select * from tab_with_index where id=2 for update;

当会话 1 只给其中一行数据加了排它锁,但是会话 2 在请求其他行的排它锁时,不会出现锁等待;原因是在有索引的情况下,InnoDB 会使用行锁

行锁是针对索引加锁,而不是针对记录加锁,仍然以 tab_with_index 表为例,插入一条 id 列相同的数据

insert into tab_with_index  values(1,'4');

会话 vnjohn-transaction1 会话 vnjohn-transaction2
begin;
select * from tab_with_index where id = 1;
begin;
select * from tab_with_index where id = 1 and name=‘1’ for update;
select * from tab_with_index where id = 1 and name=‘4’ for update;

会话1、会话2 虽然访问是不同行记录,但是使用了相同的索引键,是会出现锁冲突的,所以会话 2 会出现等待锁的情况

死锁

以 student 表为例,演示两个会话之间,在需要互相获取对方的资源情况下,产生的死锁

会话 vnjohn-transaction1 会话 vnjohn-transaction2
begin;
select * from student where id = 1 for update;
begin;
select * from student where id = 2 for update;
select * from student where id = 2 for update;
select * from student where id = 1 for update;

当执行到第一步 SQL select * from student where id = 2 for update; 时,会看到会话 1 一直会阻塞住,当会话2 执行 SQL select * from student where id = 1 for update; 时,MySQL 检测到了死锁,立即结束了会话2 中事务的执行,此时,会话1 发现原本阻塞的语句立马执行完成了

通过: show engine innodb status\G 命令可以看到死锁的详细情况,一般情况下看不到是哪个事务对那些记录加了什么锁,需要调整系统变量:innodb_status_output_locks(MySQL 5.6.16 引入)缺省值是 OFF

show variables like 'innodb_status_output_locks';
set global innodb_status_output_locks = ON;

开启以后,再次执行上述语句流程后,执行查看死锁详细情况的命令,效果如上图所示,

MySQL 检测到了死锁的发生,最终争抢锁之下,MySQL 自动回滚了会话2 所在的事务

总结

该篇博文讲解了 MySQL 中各种会发生的锁,包括显式、隐式的锁,共享锁、排它锁、意向锁、自增锁、间隙锁,说明了解决并发事务的问题的两种方案,以及通过间隙锁如何解决可重复读隔离级别下出现幻读的问题,阐述了 InnoDB 存储引擎行锁模式及加锁方法,最后,通过实际的小案例演示了死锁的发生以及如何通过 MySQL 自带的命令查看死锁解决的一个过程。

MySQL 专栏高质量博文如下:

MySQL 内置的监控工具介绍及使用篇

构建优化之城:MySQL 数据建模、数据类型优化与索引常识全面解析

MySQL 数据结构优化与索引细节解析:打造高效数据库的优化秘笈

MySQL 数据访问与查询优化:提升性能的实战策略和解耦优化技巧

深度解析 MySQL 事务、隔离级别和 MVCC 机制:构建高效并发的数据交响乐

MySQL 日志体系解析:保障数据一致性与恢复的三位英雄:Redo Log、Undo Log、Bin Log

如果觉得博文不错,关注我 vnjohn,后续会有更多实战、源码、架构干货分享!

推荐专栏:Spring、MySQL,订阅一波不再迷路

大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
22天前
|
网络协议 Java 应用服务中间件
深入浅出Tomcat网络通信的高并发处理机制
【10月更文挑战第3天】本文详细解析了Tomcat在处理高并发网络请求时的机制,重点关注了其三种不同的IO模型:NioEndPoint、Nio2EndPoint 和 AprEndPoint。NioEndPoint 采用多路复用模型,通过 Acceptor 接收连接、Poller 监听事件及 Executor 处理请求;Nio2EndPoint 则使用 AIO 异步模型,通过回调函数处理连接和数据就绪事件;AprEndPoint 通过 JNI 调用本地库实现高性能,但已在 Tomcat 10 中弃用
深入浅出Tomcat网络通信的高并发处理机制
|
22天前
|
设计模式 缓存 Java
Java高并发处理机制
Java高并发处理机制
19 1
|
26天前
|
存储 安全 Java
JVM锁的膨胀过程与锁内存变化解析
在Java虚拟机(JVM)中,锁机制是确保多线程环境下数据一致性和线程安全的重要手段。随着线程对共享资源的竞争程度不同,JVM中的锁会经历从低级到高级的膨胀过程,以适应不同的并发场景。本文将深入探讨JVM锁的膨胀过程,以及锁在内存中的变化。
25 1
|
2月前
|
存储 安全 算法
网络安全与信息安全:构建数字世界的坚固防线在数字化浪潮席卷全球的今天,网络安全与信息安全已成为维系社会秩序、保障个人隐私与企业机密的关键防线。本文旨在深入探讨网络安全漏洞的成因与影响,解析加密技术如何筑起数据安全的屏障,并强调提升公众安全意识的重要性,共同绘制一幅数字时代安全防护的蓝图。
本文聚焦网络安全与信息安全领域,通过剖析网络安全漏洞的多样形态及其背后成因,揭示其对个人、企业乃至国家安全的潜在威胁。随后,详细阐述了加密技术的原理、分类及应用,展现其在保护数据安全方面的核心作用。最后,强调了提升全民网络安全意识的紧迫性,提出具体策略与建议,旨在构建一个更加安全、可靠的数字环境。
|
2月前
|
安全 Java 开发者
Java并发编程中的锁机制解析
本文深入探讨了Java中用于管理多线程同步的关键工具——锁机制。通过分析synchronized关键字和ReentrantLock类等核心概念,揭示了它们在构建线程安全应用中的重要性。同时,文章还讨论了锁机制的高级特性,如公平性、类锁和对象锁的区别,以及锁的优化技术如锁粗化和锁消除。此外,指出了在高并发环境下锁竞争可能导致的问题,并提出了减少锁持有时间和使用无锁编程等策略来优化性能的建议。最后,强调了理解和正确使用Java锁机制对于开发高效、可靠并发应用程序的重要性。
26 3
|
27天前
|
Java C语言 Python
解析Python中的全局解释器锁(GIL):影响、工作原理及解决方案
解析Python中的全局解释器锁(GIL):影响、工作原理及解决方案
31 0
|
3月前
|
存储 SQL 关系型数据库
深入解析MySQL事务机制和锁机制
深入解析MySQL事务机制和锁机制
|
4月前
|
SQL 关系型数据库 MySQL
(八)MySQL锁机制:高并发场景下该如何保证数据读写的安全性?
锁!这个词汇在编程中出现的次数尤为频繁,几乎主流的编程语言都会具备完善的锁机制,在数据库中也并不例外,为什么呢?这里牵扯到一个关键词:高并发,由于现在的计算机领域几乎都是多核机器,因此再编写单线程的应用自然无法将机器性能发挥到最大,想要让程序的并发性越高,多线程技术自然就呼之欲出,多线程技术一方面能充分压榨CPU资源,另一方面也能提升程序的并发支持性。
341 3
|
3月前
|
安全 Nacos 数据库
【技术安全大揭秘】Nacos暴露公网后被非法访问?!6大安全加固秘籍,手把手教你如何保护数据库免遭恶意篡改,打造坚不可摧的微服务注册与配置中心!从限制公网访问到启用访问控制,全方位解析如何构建安全防护体系,让您从此告别数据安全风险!
【8月更文挑战第15天】Nacos是一款广受好评的微服务注册与配置中心,但其公网暴露可能引发数据库被非法访问甚至篡改的安全隐患。本文剖析此问题并提供解决方案,包括限制公网访问、启用HTTPS、加强数据库安全、配置访问控制及监控等,帮助开发者确保服务安全稳定运行。
252 0
|
5月前
|
Arthas 监控 Java
深入解析与解决高并发下的线程池死锁问题
在高并发的互联网应用中,遇到线程池死锁问题导致响应延迟和超时。问题源于库存服务的悲观锁策略和线程池配置不当。通过以下方式解决:1) 采用乐观锁(如Spring Data JPA的@Version注解)替换悲观锁,减少线程等待;2) 动态调整线程池参数,如核心线程数、最大线程数和拒绝策略,以适应业务负载变化;3) 实施超时和重试机制,减少资源占用。这些改进提高了系统稳定性和用户体验。
188 2

推荐镜像

更多