【MySQL】MySQL锁(三)元数据锁与间隙锁

MySQL锁(三)元数据锁与间隙锁

在上篇文章中,我们就提到过 元数据锁 和 间隙锁 这两个名词,不知道有没有吊起大家的胃口。这俩货又是干嘛的呢?别急,我们一个一个来看。

元数据锁

元数据锁,又叫 MDL 锁,它是用于保护 DDL 语句的。什么是 DDL 语句?这个是基础知识哦,就是 CREATE/DROP/ALTER 之类的语句,或者说是除了增删改查之外的语句。

首先要明白一点,这些 DDL 语句都是针对整个表的,会对整个表的结构或全部数据产生影响,因此,它们必然是 表锁 级别的。

-- 事务1
mysql> select * from test_user3;-- 修改表结构 alter table 阻塞
mysql> alter table test_user3 add column d int;-- 事务2 
mysql> update test_user3 set username='abc' where id = 1;
-- 阻塞,与 MDL 冲突-- 事务1 修改结束-- 事务2 操作成功
Query OK, 0 rows affected (42.72 sec)
Records: 0  Duplicates: 0  Warnings: 0mysql> desc test_user3;
+------------+------------------+------+-----+---------+----------------+
| Field      | Type             | Null | Key | Default | Extra          |
+------------+------------------+------+-----+---------+----------------+
| id         | int(11) unsigned | NO   | PRI | NULL    | auto_increment |
| username   | varchar(200)     | YES  | MUL | NULL    |                |
| password   | varchar(200)     | YES  |     | NULL    |                |
| salt       | char(4)          | YES  |     | NULL    |                |
| created_at | datetime         | YES  | MUL | NULL    |                |
| updated_at | int(11)          | YES  |     | NULL    |                |
| status     | tinyint(4)       | YES  |     | NULL    |                |
| gender     | tinyint(4)       | YES  | MUL | NULL    |                |
| d          | int(11)          | YES  |     | NULL    |                |
+------------+------------------+------+-----+---------+----------------+
9 rows in set (0.00 sec)

上面已经说的很清楚了,因为要对表结构或者整表数据进行操作,MDL锁 自然要锁住整个表。不过这里是个读锁,写入无法进行,读取还是可以的。因此,小伙伴们在生产环境进行 DDL 操作的时候一定要小心啊,最好在半夜进行,这就是程序员的悲哀啊!

间隙与临键锁

上回我们已经见过了 行锁 ,也可以叫做 记录锁 的使用。在分析锁的情况时,我们也提到过了 间隙锁 。

间隙锁(GAP)其实就是封锁索引记录中的间隔,比如说主键不连续的数据插入。假设现在有两条记录,id 分别为 5 和 10 ,那么在 5-10 中间就是我们的数据间隙,这里就是 间隙锁 发挥的地方。

临键锁(Next-key Lock),是一个新的概念,但它其实是 记录锁 和 间隙锁 的结合,也是 MySQL 默认的 行锁 。什么意思呢?间隙锁指的是 5-10,但不包括 5 和 10,也就是一个 开区间 (5, 10)。而 临键锁 则是一个前闭后开区间,把 5 包括进来 [5, 10) 。

为什么又说 临键锁 是默认的行锁呢?其实在默认情况下,行锁 就是 临键锁 ,它会锁自己以及附近的数据,但是,如果是主键或者唯一索引,会退化成 记录锁 ,也就是我们习惯说的那个 “行锁” ,而在大部分情况下,普通的间隙空值操作也会退化为 间隙锁 ,只有在一些条件下才会产生 临键锁 。因此,间隙 和 记录 这两种锁其实都是 临键锁 的退化版本,或者说是简易版本。注意,这个退化仅限于主键是由一个列组成的,如果是多个列组成的,则不会发生退化。

间隙锁 和 临键锁 都是为了解决一个问题,那就是 幻读 的问题。如果不记得这个概念了,就赶紧回到之前的文章复习一下吧 MySQL事务的问题:脏读、幻读、不可重复读https://mp.weixin.qq.com/s/mQ4LxwZkVbBQ4-i8g2qrkA 。

间隙锁的产生有三种情况,我们分别来看一下。

主键唯一

在这里我们尝试给不存在的记录加锁时,就会优化为间隙锁。

-- 事务1
mysql> select * from tran_innodb;
+----+------+------+
| id | name | age  |
+----+------+------+
| 19 | Joe2 |   14 |
| 23 | Joe2 |   18 |
+----+------+------+
11 rows in set (0.00 sec)mysql> begin;
mysql> update tran_innodb set name = joe3 where id = 15;
-- 注意这里没有记录为 15 的数据-- 事务2
mysql> select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
+---------------+-------------+------------+-----------+-----------+-----------+
| object_schema | object_name | index_name | lock_type | lock_mode | lock_data |
+---------------+-------------+------------+-----------+-----------+-----------+
| blog_test     | tran_innodb | NULL       | TABLE     | IX        | NULL      |
| blog_test     | tran_innodb | PRIMARY    | RECORD    | X,GAP     | 16        |
+---------------+-------------+------------+-----------+-----------+-----------+
2 rows in set (0.05 sec)
-- 可以看到 lock_mode 中显示的是 X,GAP 产生了间隙锁mysql> insert into tran_innodb(id,name,age) values(14,'Joe2',13);
-- 阻塞-- 事务1 提交
mysql> commit;-- 事务3
mysql> insert into tran_innodb(id,name,age) values(14, 'Joe2', 13);
-- 阻塞

注释中说得很明确,事务1去更新一条不存在的记录 15 ,这个 id 是位于 13 和 16 之间的,事务2 和 事务3 同时也无法增加记录为 14 的数据,这就是因为在 13 和 16 之间产生了间隙锁。

其实在这里,事务2 虽然也产生了阻塞,但是当 事务1 提交之后,事务2 会马上拿到锁,事务3 还是会在阻塞状态,紧接着 事务2 提交后,事务3 就会插入失败。

-- 事务2 提交
mysql> commit;-- 事务3
ERROR 1062 (23000): Duplicate entry '14' for key 'tran_innodb.PRIMARY'

非唯一索引(普通索引)

普通索引产生的间隙和主键索引是一样的。

-- 事务1
mysql> select * from tran_innodb;
+----+------+------+
| id | name | age  |
+----+------+------+
| 19 | Joe2 |   14 |
| 23 | Joe2 |   18 |
+----+------+------+
10 rows in set (0.00 sec)-- 加排它锁
begin;
mysql> select * from tran_innodb where age = 16 lock in share mode;-- 事务2
mysql> select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
+---------------+-------------+------------+-----------+-----------+-----------+
| object_schema | object_name | index_name | lock_type | lock_mode | lock_data |
+---------------+-------------+------------+-----------+-----------+-----------+
| blog_test     | tran_innodb | NULL       | TABLE     | IS        | NULL      |
| blog_test     | tran_innodb | idx_age    | RECORD    | S,GAP     | 18, 18    |
+---------------+-------------+------------+-----------+-----------+-----------+
2 rows in set (0.00 sec)-- 事务2 插入数据
mysql> insert into tran_innodb(id,name,age) values(24,'Joe2',15);-- 事务3 插入数据
mysql> insert into tran_innodb(id,name,age) values(31,'Joe2',11);
Query OK, 1 row affected (0.00 sec)-- 事务4 插入数据
mysql> insert into tran_innodb(id,name,age) values(32,'Joe2',13);
Query OK, 1 row affected (0.00 sec)
-- 阻塞

在针对普通索引的情况下,也会产生 间隙锁 这个锁和 主键 就没什么关系了,而是根据索引字段的情况产生阻塞。

假设我们把锁正好打在一个存在的数据上,会发生什么?注意,普通索引是非唯一的,可能有多条数据会加锁。

-- 事务1
mysql> begin;
Query OK, 0 rows affected (0.00 sec)mysql> select * from tran_innodb where age = 18 lock in share mode;
+----+------+------+
| id | name | age  |
+----+------+------+
| 18 | Joe2 |   18 |
| 23 | Joe2 |   18 |
+----+------+------+
2 rows in set (0.01 sec)-- 事务1
mysql> select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
+---------------+-------------+------------+-----------+---------------+------------------------+
| object_schema | object_name | index_name | lock_type | lock_mode     | lock_data              |
+---------------+-------------+------------+-----------+---------------+------------------------+
| blog_test     | tran_innodb | NULL       | TABLE     | IS            | NULL      |
| blog_test     | tran_innodb | idx_age    | RECORD    | S             | 18, 18    |
| blog_test     | tran_innodb | idx_age    | RECORD    | S             | 18, 23    |
| blog_test     | tran_innodb | PRIMARY    | RECORD    | S,REC_NOT_GAP | 23        |
| blog_test     | tran_innodb | PRIMARY    | RECORD    | S,REC_NOT_GAP | 18        |
| blog_test     | tran_innodb | idx_age    | RECORD    | S,GAP         | 25, 29    |                 |
+---------------+-------------+------------+-----------+---------------+------------------------+
6 rows in set (0.00 sec)

感觉一下加了好多锁啊,注意看,现在 age 从 18 到 24 全部被锁,整个区间范围内都上了 S 锁。另外还包括 18 自己的普通记录锁,整个合起来就形成了前闭后开的 [18, 25) 临键锁 。需要注意的是,lock_data 表示的是锁住的当前数据和主键,不是区间范围哦,我一开始就以为它是锁的区间范围,结果其实是 数据键,主键 的意思。

如果是唯一索引进行等值加锁的话,其实就只是一个行锁了,为啥呢?唯一的值嘛,就一条,给这一行锁上就行啦。

范围查询

最后就是范围查询的间隙锁。

mysql> begin;
Query OK, 0 rows affected (0.00 sec)mysql> select * from tran_innodb where id > 33 lock in share mode;
+----+------+------+
| id | name | age  |
+----+------+------+
| 35 | Joe2 |   25 |
+----+------+------+
1 row in set (0.00 sec)-- 事务2
mysql> select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
+---------------+-------------+------------+-----------+-----------+------------------------+
| object_schema | object_name | index_name | lock_type | lock_mode | lock_data              |
+---------------+-------------+------------+-----------+-----------+------------------------+
| blog_test     | tran_innodb | NULL       | TABLE     | IS        | NULL                   |
| blog_test     | tran_innodb | PRIMARY    | RECORD    | S         | supremum pseudo-record |
| blog_test     | tran_innodb | PRIMARY    | RECORD    | S         | 35                     |
+---------------+-------------+------------+-----------+-----------+------------------------+
3 rows in set (0.00 sec)mysql> insert into tran_innodb(id,name,age) values(45,'Joe2',10);
-- 阻塞

普通范围内的我们就不说了,在这里我们的范围是 id 整个大于30的,在 35 之后就没有数据了,其实现在形成的区间就是 (30, 无穷大) 。此时产生的记录锁中,有一条的 lock_data 就是 supremum pseudo-record ,它表明的就是到无穷大的记录间隙都被锁了。因此,后面我们插入一条 id 为 45 的数据也会阻塞。

因此,在 更新/删除 数据时,如果是范围条件,即使有索引,也会锁很多间隙,特别是 id 或数据不连续(普通索引)的情况下。插入数据的情况稍微好一点,毕竟我们会更习惯于自增主键的形式插入,但也要注意 自增锁 的情况,这个锁我们下回再说。

总结

是不是感觉 间隙锁 非常复杂?的确,它真的是很复杂,也是高级码农们面试的时候最容易被问到的。为啥呢?它要解决的可是 幻读 问题啊,也就是我们事务隔离问题中最麻烦的那个问题。因此,如果事务隔离级别是 REPEATABLE-READ 重复读 以下的级别,就不会有 间隙锁 ,这个大家可以自己试试哦。

最后,还有几个锁相关的知识,我们下回见。

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

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

相关文章

matlab简单统计学预测方法分析

基础的统计学预测方法分析,内容参考国防工业出版社-司守奎,孙玺菁主编-《数学建模算法与应用(第三版)》。本文结合实际应用对文章内容进行了提取,结合matlab算法进行程序编写。 本文所涉及的所有代码内容可通过百度网…

用队列实现栈(力扣第225题)

#include "stdio.h" #include "stdbool.h" #include "string.h" #include "stdlib.h" #include "assert.h"//初始化队列 typedef int QueueDataType;typedef struct queue {QueueDataType val;struct queue* next; }Qnode;t…

每天学习一个Linux命令之rsyslog

每天学习一个Linux命令之rsyslog 介绍 rsyslog是一个强大的日志处理系统,常用于Linux系统中的日志管理。它可以收集、过滤和路由日志消息,并将其发送到不同的目标,如文件、远程服务器或数据库。本篇博客将详细介绍rsyslog命令的使用方法及其…

微信小程序地图polyline坐标太多异常显示BUG

描述 微信小程序map地图上显示polyline线,点位超过1250个出现bug,(仅真机上出现,模拟器上正常) 这里以加载四川省边界为例, 以下是示例代码 // 读取geojson数据 uni.request({url: https://geo.datav.aliyun.com/a…

openplc Linux 使用modbus RTU 从机通讯

1.Linux 环境下,openplc 默认使用的是modbus tcp协议通信。 想要使用串口 modbus rtu 通讯可以通过在runtime中添加SlaveDevices从机设备 2.添加设备,分配地址。 左边添加串口配置,右边是需要通讯的地址,从机地址都是从100开始&am…

yolov8 区域计数

yolov8 区域计数 1. 基础2. 计数功能2.1 计数模块2.2 判断模块 3. 主代码4. 实验结果5. 源码 1. 基础 本项目是在 WindowsYOLOV8环境配置 的基础上实现的,测距原理可见上边文章 2. 计数功能 2.1 计数模块 在指定区域内计数模块 def count_objects_in_region(bo…

STM32 堆栈内存以及变量存储分布

STM32的程序存储器、数据存储器、寄存器和输入输出端口被组织在同一个4GB的线性地址空间内, 地址范围为0x0000 0000至0xFFFF FFFF。其中FLASH为ROM类型,储存的数据掉电不易失;RAM中存储的数据掉电易失。以STM32F103系列为例,最多有512KB的FLA…

Discuz! X3.4 升级至 Discuz! X3.5 详细教程

第一步:从其他以前的 Discuz! X 版本升级Discuz! X3.4 请先升级到Discuz! X3.4,升级教程网上比较普遍,在此不再论述。 第二步:Discuz! X3.4 升级至 Discuz! X3.5 (Discuz 从 X3.5 以后,不在发布GBK版本&…

【软考】UML中的图之类图

目录 1. 说明2. 图示3. 类图使用方式3.1 对系统的词汇建模3.2 对简单的协作建模3.3 对逻辑数据库模式建模 1. 说明 1.类图(Class Diagram)展现了一组对象、接口、协作和它们之间的关系。2.在面向对象系统的建模中所建立的最常见的图是类图。3.类图给出系…

离线数仓数据导出-hive数据同步到mysql

离线数仓数据导出-hive数据同步到mysql MySQL建库建表数据导出 为方便报表应用使用数据,需将ads各指标的统计结果导出到MySQL数据库中。 datax支持hive同步MySQL:仅仅支持hive存储的hdfs文件导出。所以reader选hdfs-reader,writer选mysql-wri…

python输入输出特殊处理

输出 需要满足输出一行后,再输出一行,行中每个元素用空格隔开 length len(tri) tmp [] for i in range(len(tri)):tmp tri[i]for j in range(len(tri[i])):print(tmp[j],end )print()输入p 一次性输入6个数字到列表中,并且输入的每个数…

怎样在外网登录访问CRM管理系统?

一、什么是CRM管理系统? Customer Relationship Management,简称CRM,指客户关系管理,是企业利用信息互联网技术,协调企业、顾客和服务上的交互,提升管理服务。为了企业信息安全以及使用方便,企业…

SSM小程序作品集展示微信小程序

采用技术 SSM小程序作品集展示微信小程序的设计与实现~ 开发语言:Java 数据库:MySQL 技术:SpringMVCMyBatis 工具:IDEA/Ecilpse、Navicat、Maven 页面展示效果 用户功能 用户注册 用户首页 作品集 优秀作者 我的分享 管…

Linux嵌入式驱动开发-内核定时器

文章目录 内核时间管理处理jiffies绕回的APIExample:使用 jiffies 判断超时jiffies与ms、us、ns转换的API 内核定时器内核定时器APIinit_timer: 初始化 timer_list 类型变量add_timer: 向 Linux 内核注册定时器del_timer: 删除一个定时器del_timer_sync: del_timer …

powershell@命令行提示符样式配置自定义@pwsh重写prompt显示电量内存时间等信息

文章目录 abstract流行的powershell prompt模块示例 powershell原生修改Prompt函数配置文档Prompt命令来自哪里 简单修改带上电量和时间的Prompt 复杂修改预览FAQ:没有必要修改相关仓库地址样式选择平衡样式花哨样式响应性能 小结 abstract 在 PowerShell 中,可以通…

CSDN积分和等级和 能创建专栏数量的关系。还差1000多分!

积分查询:CSDN 博客积分规则 博客积分是CSDN对用户努力的认可和奖励,也是衡量博客水平的重要标准。博客等级也将由博客积分唯一决定。积分规则具体如下: 1、每发布一篇原创或者翻译文章:可获得10分; 2、每发布一篇转载…

做一个答题pk小程序多少钱

在探讨“做一个答题pk小程序多少钱”这一问题时,我们首先需要明确的是,小程序的价格并非固定不变,而是受到多种因素的影响。这些因素包括但不限于小程序的复杂度、功能需求、开发周期、技术难度以及开发团队的规模和经验等。因此,…

Docker-volume创建数据卷

创建一个名为myvol的数据卷: [rootlocalhost ~]# docker volume create myvol myvol[rootlocalhost ~]# docker volume ls DRIVER VOLUME NAME local myvol查看数据卷: [rootlocalhost ~]# docker volume inspect myvol [{&…

Web前端 JavaScript笔记7

js的执行机制 js是单线程 同步:前面一个任务执行结束之后,执行后一个 异步:异步任务,引擎放在一边,不进入主线程,而进入任务队列的任务 js通过浏览器解析,浏览器靠引擎解析 回调函数同步任务执行…

微服务与Web服务:定义、优势、挑战与实践指南20240416

引言 在当前的软件开发领域,微服务和Web服务是两个频繁被讨论的术语。随着企业应用的复杂性和规模的增加,深入理解这两种服务架构的优势和挑战变得极为关键。本文将探讨微服务和Web服务的核心概念、优缺点,并通过Go语言示例展示它们的实现。…