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

本文涉及的产品
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
简介: 解析 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,订阅一波不再迷路

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


相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。   相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情: https://www.aliyun.com/product/rds/mysql 
目录
相关文章
|
6月前
|
缓存 关系型数据库 MySQL
在MySQL中处理高并发和负载峰值的关键技术与策略
采用上述策略和技术时,每个环节都要进行细致的规划和测试,确保数据库系统既能满足高并发的要求,又要保持足够的灵活性来应对各种突发的流量峰值。实施时,合理评估和测试改动对系统性能的影响,避免单一措施可能引起的连锁反应。持续的系统监控和分析将对维护系统稳定性和进行未来规划提供重要信息。
330 15
|
7月前
|
关系型数据库 MySQL 分布式数据库
Super MySQL|揭秘PolarDB全异步执行架构,高并发场景性能利器
阿里云瑶池旗下的云原生数据库PolarDB MySQL版设计了基于协程的全异步执行架构,实现鉴权、事务提交、锁等待等核心逻辑的异步化执行,这是业界首个真正意义上实现全异步执行架构的MySQL数据库产品,显著提升了PolarDB MySQL的高并发处理能力,其中通用写入性能提升超过70%,长尾延迟降低60%以上。
|
10月前
|
消息中间件 NoSQL 关系型数据库
去哪面试:1Wtps高并发,MySQL 热点行 问题, 怎么解决?
去哪面试:1Wtps高并发,MySQL 热点行 问题, 怎么解决?
去哪面试:1Wtps高并发,MySQL 热点行 问题, 怎么解决?
|
10月前
|
监控 负载均衡 安全
静态IP代理与动态IP代理:提升速度与保障隐私的技术解析
本文探讨了静态IP代理和动态IP代理的特性和应用场景。静态IP代理通过高质量服务提供商、网络设置优化、定期更换IP与负载均衡及性能监控提升网络访问速度;动态IP代理则通过隐藏真实IP、增强安全性、绕过封锁和提供独立IP保障用户隐私。结合实际案例与代码示例,展示了两者在不同场景下的优势,帮助用户根据需求选择合适的代理服务以实现高效、安全的网络访问。
362 1
|
9月前
|
人工智能 缓存 NoSQL
高并发秒杀系统设计:关键技术解析与典型陷阱规避
在电商、在线票务等场景中,高并发秒杀活动对系统性能和稳定性提出极大挑战。海量请求可能导致服务器资源耗尽、数据库锁争用及库存超卖等问题。通过飞算JavaAI生成的Redis + Lua分布式锁代码,可有效解决高并发下的锁问题,提升系统QPS达70%,同时避免缓存击穿与库存超卖。相较传统写法,AI优化代码显著提高性能与响应速度,为高并发系统开发提供高效解决方案。
|
存储 安全 数据安全/隐私保护
深入解析iOS 14隐私保护功能:用户数据安全的新里程碑
随着数字时代的到来,个人隐私保护成为全球关注的焦点。苹果公司在最新的iOS 14系统中引入了一系列创新的隐私保护功能,旨在为用户提供更透明的数据使用信息和更强的控制权。本文将深入探讨iOS 14中的几项关键隐私功能,包括App跟踪透明性、简化的隐私设置以及增强的系统安全性,分析它们如何共同作用以提升用户的隐私保护水平。
749 3
|
存储 安全 Java
JVM锁的膨胀过程与锁内存变化解析
在Java虚拟机(JVM)中,锁机制是确保多线程环境下数据一致性和线程安全的重要手段。随着线程对共享资源的竞争程度不同,JVM中的锁会经历从低级到高级的膨胀过程,以适应不同的并发场景。本文将深入探讨JVM锁的膨胀过程,以及锁在内存中的变化。
202 1
|
存储 安全 算法
网络安全与信息安全:构建数字世界的坚固防线在数字化浪潮席卷全球的今天,网络安全与信息安全已成为维系社会秩序、保障个人隐私与企业机密的关键防线。本文旨在深入探讨网络安全漏洞的成因与影响,解析加密技术如何筑起数据安全的屏障,并强调提升公众安全意识的重要性,共同绘制一幅数字时代安全防护的蓝图。
本文聚焦网络安全与信息安全领域,通过剖析网络安全漏洞的多样形态及其背后成因,揭示其对个人、企业乃至国家安全的潜在威胁。随后,详细阐述了加密技术的原理、分类及应用,展现其在保护数据安全方面的核心作用。最后,强调了提升全民网络安全意识的紧迫性,提出具体策略与建议,旨在构建一个更加安全、可靠的数字环境。
|
安全 Java 开发者
Java并发编程中的锁机制解析
本文深入探讨了Java中用于管理多线程同步的关键工具——锁机制。通过分析synchronized关键字和ReentrantLock类等核心概念,揭示了它们在构建线程安全应用中的重要性。同时,文章还讨论了锁机制的高级特性,如公平性、类锁和对象锁的区别,以及锁的优化技术如锁粗化和锁消除。此外,指出了在高并发环境下锁竞争可能导致的问题,并提出了减少锁持有时间和使用无锁编程等策略来优化性能的建议。最后,强调了理解和正确使用Java锁机制对于开发高效、可靠并发应用程序的重要性。
141 3
|
JSON JavaScript 前端开发
深入解析ESLint配置:从入门到精通的全方位指南,精细调优你的代码质量保障工具
深入解析ESLint配置:从入门到精通的全方位指南,精细调优你的代码质量保障工具
521 0

推荐镜像

更多