盘点MySQL中的各种锁

简介: 盘点MySQL中的各种锁

前言


当多个线程并发访问某个数据的时候,尤其是针对一些敏感的数据(比如订单、金额等),我们就需要保证这个数据在任何时刻最多只有一个线程在访问,保证数据的完整性和一致性。在开发过程中加锁是为了保证数据的一致性,这个思想在数据库领域中同样很重要。MySQL中也存在各种各样的锁,本文对此做了一个盘点总结。


按属性区分


MySQL根据加锁的属性分为共享锁和排他锁,主要是为了最大程度保证的高并发和安全性。


共享锁


共享锁也成为读锁,针对同一份数据,多个事务的读操作可以同时进行而不会互相影响,相互不阻塞的。

  • 通过下面命令加共享锁
SELECT...LOCK IN SHARE MODE
#或
SELECT...FOR SHARE;#(8.0新增语法)


排他锁


排他锁也叫写锁,当一个事务对一份数据执行写入,即加上排他锁后,其他事务对同一份数据进行读写操作会阻塞,直到前一个事务提交。

  • 通过下面的命令加排他锁
SELECT ... FOR UPDATE;
  • DELETEUPDATEINSERT等操作也相当于加排他锁

共享锁和排他锁是否会发生阻塞如下图所示:

共享锁 排他锁
共享锁 不阻塞 阻塞
排他锁 阻塞 阻塞


按锁粒度区分


为了尽可能提高数据库的并发度,每次锁定的数据范围越小越好理论上每次只锁定当前操作的数据的方案会得到最大的并发度,但是管理锁是很耗资源的事情(涉及获取、检查、释放锁等动作)。因此数据库系统需要在高并发响应和系统性能两方面进行平衡,这样就产生了“锁粒度”的概念。


行级锁


行级锁是锁住行,粒度小,性能较高,但是行级锁只在存储引擎层实现,行级锁分为3种,记录锁、间隙锁和临键锁。

假如下面的一个表test_lock:

id a b
0 0 0
4 4 4
8 8 8
16 16 16
32 32 32
  • id是主键索引
  • a是普通索引
  • b是普通列
  1. 记录锁(Record Locks)

记录锁是仅仅锁住一条记录,锁的粒度最小。

什么时候会加记录锁?

当用唯一索引进行等职查询时,且查询的记录是存在的时候,会加记录锁。

会话1 会话2 会话3
begin;select * from test_lock where id = 16 for update;
update test_lock set a = 100 where id = 16;(阻塞)
insert into test_lock value(9, 9, 9);(正常)
  • 会话1对id=16记录加了行锁
  • 会话2阻塞,无法对这条记录进行修改操作
  • 会话3正常插入
  1. 间隙锁(Gap Locks)

大家还记得并发事务中会出现"幻读"的问题吗?就是事务期间,其他事务添加一条数据,再次读取突然多出一条记录。为了解决这样的问题,我们是不是可以对一段区间的数据加锁,加上锁以后,其他事务添加数据时必须阻塞。像这样的锁就叫做间隙锁,即锁定一个区间,左开右开。

什么情况会加间隙锁

  • 在用唯一索引进行等值查询时,当查询记录不存在时,会加间隙锁。
select * from test_lock where id = 10 for update;

id=10位于8到16区间,由于10这条记录不存在,所以加的间隙锁,锁定(8, 16)的区间。

  • 唯一锁引使用范围查询的时候,会加间歇锁。
select * from test_lock where id <10 and id> 8 for update;

id <10 and id> 8是一个范围查询,会锁定范围(8,16)

  • 普通索引等值查询时,如果记录存在,会额外添加一个间隙锁。
select * from test_lock where a = 8 for update;

由于a=8记录存在,会对范围(4,8]添加临键锁,这个后面会提到,同时额外向下遍历到第一个不符合条件的值才能停止,因此间隙锁的范围是(8,16)

  • 通索引等值查询时,如果记录不存在,会加一个间隙锁。
select * from test_lock where a = 10 for update;

此种情况锁定的范围为(8,16)

  1. 临键锁(Next-Key Locks)

如果想要同时集合上面的记录锁和间隙锁,也就是既想锁住某条记录,又想阻止其他事务在该记录前边的间隙插入新记录,所以InnoDB就提出了临键锁(Next-Key Locks),默认锁定的范围是左开右闭。InnoDB存储引擎默认的锁单位就是临键锁(Next-Key Locks),怎么理解呢?

也就是锁加锁都是按照临键锁加锁,但是会根据一定的规律退化为记录锁和间隙锁。具体规律如下:

唯一索引等值查询:

  • 当查询的记录是存在的,临键锁会退化成「记录锁」。
  • 当查询的记录是不存在的,临键锁 会退化成「间隙锁」。

非唯一索引等值查询:

  • 当查询的记录存在时,除了会加 临键锁外,还额外加间隙锁,也就是会加两把锁。
  • 当查询的记录不存在时,只会加 临键锁,然后会退化为间隙锁,也就是只会加一把锁。

非唯一索引和主键索引的范围查询的加锁规则不同之处在于:

  • 唯一索引在满足一些条件的时候,临键锁退化为间隙锁和记录锁。
  • 非唯一索引范围查询,临键锁不会退化为间隙锁和记录锁。

InnoDB存储引擎中,如果一个表查询或者更新没有走索引,这时候还会创建行级锁吗? 答案是不会,这时候会升级为表级锁。


页级锁


页级锁是 MySQL 中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。因此,采取了折衷的页级锁,一次锁定相邻的一组记录。BDB (BerkeleyDB)存储引擎 支持页级锁。

特点

  • 开销和加锁时间界于表锁和行锁之间
  • 会出现死锁
  • 锁定粒度界于表锁和行锁之间,并发度一般


表级锁


表锁会锁定整张表,它是MySQL中最基本的锁策略,并不依赖于存储引擎,表锁是开销最小的策略。因为表级锁一次会将整个表锁定,所以可以很好的避免死锁问题。但是表锁的并发度很差,那表锁都有哪几种呢?

  1. 表级别的S锁、X锁
  • LOCK TABLES t READ :InnoDB存储引擎会对表 t 加表级别的 S锁 。
  • LOCK TABLES t WRITE:InnoDB存储引擎会对表 t 加表级别的 X锁 。
  1. 意向锁

意向锁也是一种表锁,表示某个事务正在锁定一行或者将要锁定一行,表明一个意图。它不与行级锁冲突。那它究竟有啥作用?

意向锁是在当事务加表锁时发挥作用。比如一个事务想要对表加排他锁,如果没有意向锁的话,那么该事务在加锁前需要判断当前表的每一行是否已经加了锁,如果表很大,遍历每行进行判断需要耗费大量的时间。如果使用意向锁的话,那么加表锁前,只需要判断当前表是否有意向锁即可,这样加快了对表锁的处理速度。

意向锁分为两种:

  • 意向共享锁(intention shared lock, IS):事务有意向对表中的某些行加共享锁(S锁)
  • 意向排他锁(intention exclusive lock, IX):事务有意向对表中的某些行加排他锁(X锁)
  1. 自增锁(AUTO-INC LOCK)

我们都知道在使用创建表的时候有自增主键AUTO_INCREMENT属性,那它是怎么实现自增的呢?

AUTO-INC锁是当向使用含有AUTO_INCREMENT列的表中插入数据时需要获取的一种特殊的表级锁,在执行插入语句时就在表级别加一个AUTO-INC锁,然后为每条待插入记录的AUTO_INCREMENT修饰的列分配递增的值,在该语句执行结束后,再把AUTO-INC锁释放掉。

  1. 元数据锁(MDL锁)

元数据锁可以用来保证读写的正确性。比如,如果一个查询正在遍历一个表中的数据,而执行期间另一个线程对这个 表结构做变更 ,增加了一列,那么查询线程拿到的结果跟表结构对不上,肯定是不行的。

  • 当对一个表做增删改查操作的时候,加 MDL读锁;
  • 当要对表做结构变更操作的时候,加 MDL 写锁。

MDL读锁和读锁之间可以共享兼容,读锁和写锁之间不兼容,会互相阻塞。


全局锁


全局锁就是对 整个数据库实例 加锁。当你需要让整个库处于 只读状态 的时候,可以使用这个命令,之后 其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结 构等)和更新类事务的提交语句。

全局锁的典型使用场是做全库逻辑备份。

## 全局锁命令
flush tables with read lock


按加锁的态度区分


从对待锁的态度来看锁的话,可以将锁分成乐观锁和悲观锁,就像人看待一个事情的态度一样,有的人很乐观,有的人很悲观,这是一种设计思想。


悲观锁


悲观锁是总以最坏的情况假设,比如操作一条数据,总认为也有其他线程要拿这条数据,那就给这条数据上排他锁,让其他事务或者线程阻塞,类似于Java中 synchronized 和 ReentrantLock 等独占锁的思想。

适用场景:

悲观锁适写操作多的场景,因为写的操作具有 排它性 。采用悲观锁的方式,可以在数据库层 面阻止其他事务对该数据的操作权限,防止读 - 写和写 - 写的冲突。

实现思路:

以秒杀商品为例,为了防止超卖,需要加锁。

#第1步: for update 方式查出商品库存
select quantity from items where id 1001 for update;
#第2步:如果库存大于0,则根据商品信息生产订单
insert into orders (item_id)values(1001);
#第3步:修改商品的库存,um表示购买数量
update items set quantity quantity-num where id 1001;

select····for update是MySQL中悲观锁。此时在items表中,id为1001的那条数据就被我们锁定了,其他的要执行select quantity from items where id=1001 for update;语句的事务必须等本次事务提交之后才能执行。这样我们可以保证每次事务能拿到最新的库存数量,从而不会超卖,但是这样的性能很差。


乐观锁


乐观锁认为一个事务发生并发的概率很小,就不加通过数据库加锁实现,因为加锁性能比较差,而是通过程序实现,那如何数据没有被其他事务修改了呢?会在更新数据的时候判断数据的版本或者时间戳是否发生变化。

实现思路:

  1. 版本号机制实现乐观锁
  • 数据表中新增一个version字段
  • 更新前读取出version字段
  • 进行业务逻辑操作,更新数据, UPDATE ... SET version=version+1 WHERE version=version,版本+1
  • 如果有其他线程更新了数据,那么上面的修改不成功,返回值为0,表示已经被人更新了,我们可以根据0去做业务操作
  1. 时间戳机制实现乐观锁

时间戳和版本号机制一样,也是在更新提交的时候,将当前数据的时间戳和更新之前取得的时间戳进行 比较,如果两者一致则更新成功,否则就是版本冲突。

适用场景:

乐观锁适合读操作多的场景,相对来说写的操作比较少。它的优点在于 程序实现 ,不存在死锁问题。


总结


本文对MySQL中的锁从不同的维度做了一个整体的区分介绍,对于InnoDB引擎来说,行级锁非常重要,它默认采用临键锁,当然如果这条语句没有走索引,那么它会直接升级成表锁,锁住整个表,导致性能很糟糕。所以合理的创建索引极为重要。

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
目录
相关文章
|
SQL AliSQL 关系型数据库
MYSQL的全局锁和表锁
本文介绍了MySQL中的锁机制,包括全局锁、表级锁及其应用场景。全局锁通过`Flush tables with read lock (FTWRL)`实现,主要用于全库逻辑备份,但会阻塞更新和结构变更操作。表级锁分为显式表锁(`lock tables`)和元数据锁(MDL),前者用于控制并发访问,后者自动加锁以确保读写正确性。文章还探讨了如何安全地为小表添加字段,建议通过设置DDL等待时间或使用MariaDB/AliSQL的NOWAIT/WAIT功能避免业务阻塞。这些方法有助于在高并发场景下优化数据库性能与安全性。
398 0
|
SQL 关系型数据库 MySQL
MySQL 锁
MySQL里常见的几种锁
409 3
|
关系型数据库 MySQL 网络安全
如何排查和解决PHP连接数据库MYSQL失败写锁的问题
通过本文的介绍,您可以系统地了解如何排查和解决PHP连接MySQL数据库失败及写锁问题。通过检查配置、确保服务启动、调整防火墙设置和用户权限,以及识别和解决长时间运行的事务和死锁问题,可以有效地保障应用的稳定运行。
570 25
|
存储 关系型数据库 MySQL
MySQL进阶突击系列(06)MySQL有几种锁?| 别背答案,现场演示一下
本文详细解析了MySQL InnoDB存储引擎的锁机制,涵盖读锁、写锁、意向锁、记录锁、间隙锁和临键锁等8种锁类型。重点探讨了不同锁类型的加锁与释放方式,以及事务并发场景下的实战验证。通过具体示例,展示了在不同情况下锁的行为及其对事务的影响。文章还特别强调了锁的作用范围主要是索引,并解释了锁如何影响数据的读写操作。最后总结了并发事务中加锁规则,帮助读者深入理解MySQL的锁机制。
|
存储 关系型数据库 MySQL
优化 MySQL 的锁机制以提高并发性能
【10月更文挑战第16天】优化 MySQL 锁机制需要综合考虑多个因素,根据具体的应用场景和需求进行针对性的调整。通过不断地优化和改进,可以提高数据库的并发性能,提升系统的整体效率。
873 1
|
关系型数据库 MySQL Java
MySQL数据锁:Record Lock,Gap Lock 和 Next-Key Lock
本文基于 MySQL 8.0.30 版本及 InnoDB 引擎,深入解析三种行锁机制:记录锁(Record Lock)、间隙锁(Gap Lock)和临键锁(Next-key Lock)。记录锁锁定索引记录,确保事务唯一修改;间隙锁锁定索引间的间隙,防止新记录插入;临键锁结合两者,锁定范围并记录自身,有效避免幻读现象。通过具体示例展示了不同锁的作用机制及其在并发控制中的应用。
1682 2
|
监控 关系型数据库 MySQL
MySQL锁机制与解决死锁问题
MySQL锁机制与解决死锁问题
751 5
|
存储 关系型数据库 MySQL
MySQL数据库锁:共享锁和独占锁
本文详细介绍了`InnoDB`存储引擎中的两种行级别锁:共享锁(S锁)与排他锁(X锁)。通过具体示例展示了这两种锁的工作机制及其在`InnoDB`与`MyISAM`引擎中的表现差异。文章还提供了锁的兼容性矩阵,帮助读者更好地理解锁之间的互斥关系。最后总结了两种锁的特点及适用场景。适合希望深入了解`MySQL`并发控制机制的读者阅读。
771 1
|
关系型数据库 MySQL 数据库
Mysql的锁
本文介绍了MySQL中表级锁和行级锁的区别,其中MyISAM仅支持表级锁,而InnoDB支持表级锁和行级锁,默认为行级锁。表级锁锁定整个表,实现简单,资源消耗少,但并发度低;行级锁仅锁定相关记录,减少冲突,提高并发度,但加锁开销大。此外,还介绍了共享锁和排他锁的概念及意向锁的作用。
255 1
|
SQL 存储 关系型数据库
"MySQL增列必锁表?揭秘InnoDB在线DDL,让你的数据库操作飞一般,性能无忧!"
【8月更文挑战第11天】在数据库领域,MySQL凭借其稳定高效的表现深受开发者喜爱。对于是否会在给数据表添加列时锁表的问题,MySQL的行为受版本、存储引擎等因素影响。从5.6版起,InnoDB支持在线DDL,可在改动表结构时保持表的可访问性,避免长时间锁表。而MyISAM等则需锁表完成操作。例如,在使用InnoDB的表上运行`ALTER TABLE users ADD COLUMN email VARCHAR(255);`时,通常不会完全锁表。虽然在线DDL提高了灵活性,但复杂操作或大表变更仍可能暂时影响性能。因此,进行结构变更前应评估其影响并择机执行。
427 6

推荐镜像

更多