MySQL数据库锁:共享锁和独占锁

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: 本文详细介绍了`InnoDB`存储引擎中的两种行级别锁:共享锁(S锁)与排他锁(X锁)。通过具体示例展示了这两种锁的工作机制及其在`InnoDB`与`MyISAM`引擎中的表现差异。文章还提供了锁的兼容性矩阵,帮助读者更好地理解锁之间的互斥关系。最后总结了两种锁的特点及适用场景。适合希望深入了解`MySQL`并发控制机制的读者阅读。

你好,我是猿java。

InnoDB存储引擎中,行级别锁有两种类型:共享锁(S锁)排他锁(X锁),理解这 2种锁的工作机制及其交互关系对于掌握MySQL的并发控制和锁机制非常重要,因此,今天就来一起聊聊MySQL共享锁排他锁

申明:本文基于 MySQL 8.0.30 版本,InnoDB引擎

共享锁

什么是共享锁?

共享锁(shared lock,S锁),也叫读锁。它是指当对象被锁定时,允许多个事务同时读取该资源,也允许其它事务从该对象上再次获取共享锁,但不能对该对象进行写操作。

加锁方式

共享锁一般通过下面 2种方式进行加锁:

# 方式1
select ... lock in share mode;

# 方式2
select ... for share;

如果事务T1 持有某对象的共享(S)锁,则事务T2 需要再次获取该对象的锁时,会出现下面两种情况:

  • 如果T2 获取该对象的共享(S)锁,则可以立即获取锁;
  • 如果T2 获取该对象的排他(X)锁,则无法获取锁;

举例说明

为了更好地理解上述两种情况,这里分别以下面的执行顺序流对InnoDB存储引擎和MyISAM存储引擎进行验证:

InnoDB存储引擎

创建一张用户user表,表结构如下:

CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `age` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

给行加共享锁

这里给user表中id=3行加共享锁为例,执行顺序流如下表:

加锁线程 sessionA 线程B sessionB 线程C sessionC
#开启事务
begin;
#给id=3的行加共享锁
select * from user
where id = 3 lock in share mode;
#获取id=3行的共享锁成功
#select操作执行成功
select * from user where id=3;
#获取id=3行的共享锁成功
#select操作执行成功
select * from user where id=3;
#获取id=3行的排它锁失败
#delete操作被堵塞
delete from user where id = 3;
#获取id=4行的排它锁成功
#delete操作执行成功
delete from user where id = 4;
#提交事务
#id=3的行上共享锁被释放
commit;
#获取id=3行的排它锁成功
#被堵塞的delete操作执行成功
delete from user where id = 3;

示例执行结果图如下:
share-lock-row.png

通过上述的示例执行结果可以看出:当事务A(sessionA)对userid=3这行添加共享锁后,事务B(sessionB)和事务C(sessionC)都可以获取user表的共享锁,也就是select操作能成功执行,但是事务B(SessionB)获取userid=3的写锁失败,即delete where id=3操作被阻塞,而事务C(sessionC)获取userid=4的写锁成功,即delete where id=4操作成功;

给表加共享锁

这里通过lock in share mode方式给user整张表添加共享锁,执行顺序流如下表:

加锁线程 sessionA 线程B sessionB
#开启事务
begin;
#对user整张表加共享锁
select * from user lock in share mode;
#成功获取user表的共享锁,select操作成功执行
select * from user;
#获取user表的排他锁失败,操作被堵塞
delete from user where id = 1;
#提交事务
#user表的共享锁被释放
commit;
#获取user表上排他锁成功,delete操作执行成功
delete from user where id = 1;

示例执行结果图如下:
share-lock.png

通过上述的示例执行结果可以看出:当事务A(sessionA)对user整张表添加共享锁后,事务B(sessionB)可以获取user表的共享锁,也就是select操作能成功执行,但是事务B(SessionB)获取user表的写锁失败,即delete操作被阻塞。

所以,尽管共享锁(S锁)InnoDB存储引擎的行级别锁,但是一旦它作用到整张表时,其实是对表中所有的行加共享锁

MyISAM引擎

创建一张用户person表,表结构如下:

CREATE TABLE `person` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(25) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_name` (`name`) USING BTREE
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_

给行加共享锁

这里给person表的id=3行加共享锁为例,执行顺序流如下表:

加锁线程 sessionA 线程B sessionB
#开启事务
begin;
#给id=3的行加共享锁
select * from person where id = 3 lock in share mode;
#获取id=3行的共享锁成功
#select操作成功
select * from person where id=3;
#获取id=3行的排它锁成功
#update操作成功
update person set name='name3xx' user where id = 3;
select * from person where id=3;
#提交事务
#id=3行上共享锁被释放
commit;

示例执行结果图如下:
myisam-share-lock.png

通过上述的示例执行结果可以看出:当事务A(sessionA)对personid=3这行添加共享锁后,事务B(sessionB)既能获取person表的共享锁,也能获取personid=3的写锁,即selectupdate where id=3都操作成功;

因此,在MyISAM引擎中其实不存在共享锁。

总结

通过上述示例及其运行结果可以看出:

  • 共享锁是InnoDB存储引擎的行级锁,在MyISAM存储引擎中不存在;
  • 共享锁是尽管是行级别锁,但是当锁加在整个表时(表中所有的行,一种特殊的行),排他锁也会在表级别生效;

排它锁

什么是排他锁?

排它锁(exclusive lock,X锁),也叫写锁或者独占锁,主要是防止其它事务和当前加锁事务锁定同一对象,同一对象主要有两层含义:

  • 当排他锁加在上,则其它事务无法对该表进行insert,update,delete,alter,drop等更新操作;
  • 当排他锁加在上,则其它事务无法对该行进行insert,update,delete,alter,drop等更新操作;

加锁方式

排他锁加锁的方式一般有 2种:显式加锁和隐式加锁,如下:

-- 显式加锁
select ... for update;

-- 隐式加锁,是 MySQL内部自动加锁

为了更好的说明排他锁,这里以下面的执行顺序流来进行验证,用户user表的结构还是和上面的一样:

举例说明

为了更好地理解上述两种情况,这里分别以下面的执行顺序流对InnoDB存储引擎和MyISAM存储引擎进行验证:

InnoDB存储引擎

给行加排他锁

这里通过for update显式给user表中id=6行加排他锁为例,执行顺序流如下表:

加锁线程 sessionA 线程B sessionB 线程C sessionC
#开启事务
begin;
#给id=6的行加排他锁
select * from user
where id = 6 for update;
#获取id=6的共享锁成功
select * from user where id=6;
#获取id=6的共享锁成功
select * from user where id=6;
#获取id=6的排它锁失败
delete from user where id = 6;
#获取id=7的排它锁成功
delete from user where id = 7;
#提交事务
#user表id=6的行上排他锁被释放
commit;
#获取id=6的排它锁成功
#被堵塞的delete操作执行成功
delete from user where id = 6;

示例执行结果图如下:
excusive-lock-2.png

通过上述的示例执行结果可以看出:当事务A(sessionA)对userid=6这行添加共享锁后,事务B(sessionB)和事务C(sessionC)都可以获取user表的共享锁,也就是select操作能成功执行,但是事务B(SessionB)获取userid=6的写锁失败,即delete where id=6操作被阻塞,而事务C(sessionC)获取userid=7的写锁成功,即delete where id=7操作成功;

给表加排他锁

这里通过for update显式方式给user整张表添加排他锁,执行顺序流如下表:

加锁线程 sessionA 线程B sessionB
#开启事务 begin;
#对user整张表加排他锁
select * from user for update;
#获取user表上的共享锁成功,select执行成功
select * from user;
#获取user表上的排他锁失败,操作被堵塞
delete from user where id=3;
#提交事务
#user表上的排他被释放
commit;
#获取user表上的排他锁成功,操作执行成功
delete from user where id = 3;

示例执行结果图如下:
excusive-lock-1.png

通过上述的示例执行结果可以看出:当事务A(sessionA)对user整张表加排他锁后,事务B(sessionB)可以获取user表的共享锁,也就是select操作能成功执行,但是事务B(SessionB)获取user表的排他锁失败,即delete操作被阻塞;

所以,尽管排他锁(X锁)InnoDB存储引擎的行级别锁,但是一旦它作用到整张表时,其实是对表中所有的行加排他锁

MySQL 隐式加排他锁

这里通过MySQL隐式给userid=6行添加排他锁,执行顺序流如下表

加锁线程 sessionA 线程B sessionB
#开启事务 begin;
#MySQL隐式给id=6行加排他锁
update user set name = 'name6' where id =6;
#获取id=6的共享锁失败,select执行被阻塞
select * from user where id = 6 lock in share mode;
#提交事务
#id=6的排他被释放
commit;
#获取id=6表上的共享锁成功,select执行成功;

示例执行结果图如下:
excusive-lock-3.png

通过上述的示例执行结果可以看出:当事务A(sessionA)执行update where id=6时,MySQL会隐式加排他锁,事务B(sessionB)在lock in share mode模式下获取userid=6的共享锁失败,也就是select操作能成功被阻塞;

MyISAM引擎

MySQL 隐式加排他锁

这里通过MySQL隐式给personid=4行添加排他锁,执行顺序流如下表:

加锁线程 sessionA 线程B sessionB
#开启事务 begin;
#MySQL不会隐式给id=4行加排他锁
update person set name = 'name4' where id =4;
#获取id=4的共享锁成功,select执行成功
select * from user where id=4 lock in share mode;
#获取id=4表上的共享锁成功,select执行成功;
#提交事务 id=4的排他被释放
commit;

示例执行结果图如下:
myisam-excusive-lock.png

通过上述的示例执行结果可以看出:当事务A(sessionA)执行update where id=6时,MySQL不会隐式加排他锁,事务B(sessionB)既能获取id=4的共享锁busuanzi_count,也能获取id=4的排他锁;

因此,在MyISAM引擎中其实不存在排他锁。

总结

通过上述 3个示例及其运行结果可以看出:排他锁有表级别共享锁行级别共享锁自动锁机制 3种
表级别共享锁:锁定整个表,排他锁也会在表级别生效;
行级别共享锁:锁定特定行,排他锁也会在行级别生效;
自动锁机制:根据操作是表级别还行级别自动加对应的锁;

共享锁和排他锁的兼容性矩阵

为了更好地理解共享锁和排他锁的互斥关系,可以参考以下兼容性矩阵:

无锁 共享锁 排他锁
无锁 允许 允许 允许
共享锁 允许 允许 阻塞
排他锁 允许 阻塞 阻塞

从上述矩阵可以看出:

  • 无锁状态下可以获取任何类型的锁
  • 共享锁状态下可以继续获取共享锁,但不能获取排他锁
  • 排他锁状态下不能获取任何其他锁

总结

  • 共享锁(S锁)排他锁(X锁)InnoDB存储引擎中的 2种行级别锁,MyISAM存储引擎不存在。
  • 尽管共享锁(S 锁)排他锁(X 锁)是行级锁,但是当他们加到表级别时,对表所有行都生效,这样看上去等同表级锁
  • 共享锁(S 锁)允许多个事务同时读取数据,但不允许修改数据。多个事务可以同时持有共享锁
  • 排他锁(X 锁)允许一个事务修改数据。只有一个事务可以持有排他锁,并且在它释放锁之前,其他事务不能获得任何类型的锁

参考

InnoDB Locking官方资料

学习交流

如果你觉得文章有帮助,请帮忙转发给更多的好友,或关注:猿java,持续输出硬核文章。

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
2月前
|
SQL 关系型数据库 MySQL
MySQL 锁
MySQL里常见的几种锁
57 3
|
2月前
|
SQL 关系型数据库 MySQL
案例剖析,MySQL共享锁引发的死锁问题!
案例剖析,MySQL共享锁引发的死锁问题!
|
26天前
|
SQL 存储 关系型数据库
数据库的行级锁与表锁?
表锁:存储引擎在SQL数据读写请求前对涉及的表加锁,分共享读锁和独占写锁,读锁阻塞写,写锁阻塞读写,易发锁冲突,并发性低。行级锁:InnoDB支持,通过索引加锁,提高并发性,但可能引起死锁,需注意索引使用,适用于避免不可重复读场景。
58 21
|
2月前
|
存储 关系型数据库 MySQL
优化 MySQL 的锁机制以提高并发性能
【10月更文挑战第16天】优化 MySQL 锁机制需要综合考虑多个因素,根据具体的应用场景和需求进行针对性的调整。通过不断地优化和改进,可以提高数据库的并发性能,提升系统的整体效率。
147 1
|
2月前
|
关系型数据库 MySQL Java
MySQL数据锁:Record Lock,Gap Lock 和 Next-Key Lock
本文基于 MySQL 8.0.30 版本及 InnoDB 引擎,深入解析三种行锁机制:记录锁(Record Lock)、间隙锁(Gap Lock)和临键锁(Next-key Lock)。记录锁锁定索引记录,确保事务唯一修改;间隙锁锁定索引间的间隙,防止新记录插入;临键锁结合两者,锁定范围并记录自身,有效避免幻读现象。通过具体示例展示了不同锁的作用机制及其在并发控制中的应用。
291 2
|
3月前
|
监控 关系型数据库 MySQL
MySQL锁机制与解决死锁问题
MySQL锁机制与解决死锁问题
360 5
|
2月前
|
存储 关系型数据库 MySQL
MySQL锁,锁的到底是什么?
【10月更文挑战第16天】MySQL 锁锁定的是与数据和资源相关的对象,其目的是为了保证数据的一致性、避免冲突,并在并发环境下合理协调事务或操作的执行。理解锁的对象和意义对于优化数据库性能、处理并发问题至关重要。
102 0
|
2月前
|
关系型数据库 MySQL 数据库
mysql锁详解
通过理解并合理运用MySQL中的锁机制,开发者可以有效管理数据库并发访问,平衡性能与数据一致性需求。更多关于MySQL锁的深入探讨和最佳实践,请参考专业的数据库管理资源[[深入MySQL锁机制详解
63 0
|
3月前
|
存储 SQL 关系型数据库
MySQL 的锁机制,那么多的锁,该怎么区分?
MySQL 的锁机制,那么多的锁,该怎么区分?
51 0
|
7天前
|
存储 Oracle 关系型数据库
数据库传奇:MySQL创世之父的两千金My、Maria
《数据库传奇:MySQL创世之父的两千金My、Maria》介绍了MySQL的发展历程及其分支MariaDB。MySQL由Michael Widenius等人于1994年创建,现归Oracle所有,广泛应用于阿里巴巴、腾讯等企业。2009年,Widenius因担心Oracle收购影响MySQL的开源性,创建了MariaDB,提供额外功能和改进。维基百科、Google等已逐步替换为MariaDB,以确保更好的性能和社区支持。掌握MariaDB作为备用方案,对未来发展至关重要。
27 3