1.MySQL面试题之innodb如何解决幻读

news2024/11/15 9:31:39

1. 写在前面

在数据库系统中,幻读(Phantom Read)是指在一个事务中,两次读取同一范围的数据集时,由于其他事务的插入操作,导致第二次读取结果集发生变化的问题。InnoDB 作为 MySQL 的一个存储引擎,通过多种机制来解决幻读问题,主要包括锁机制和隔离级别。

2. 幻读问题的产生

假设有一个事务 T1,它在某个条件下查询了一批记录。在 T1 进行第一次查询后,如果另一个事务 T2 在 T1 的查询范围内插入了新的记录,那么当 T1 再次查询时,会发现多出了 T2 插入的记录,这就是幻读。

3. InnoDB 如何解决幻读

InnoDB 通过以下两种主要机制来解决幻读问题:

  1. Next-Key Locks(间隙锁)
  2. MVCC(多版本并发控制)

3.1 Next-Key Locks

Next-Key Locks(间隙锁)是 InnoDB 存储引擎在实现可重复读(REPEATABLE READ)和串行化(SERIALIZABLE)隔离级别时使用的一种锁机制。它结合了记录锁和间隙锁,用于锁定一个记录及其前后的间隙,防止其他事务在间隙中插入新的记录,从而避免幻读。

3.1.1 组成

Next-Key Locks 是记录锁(Record Lock)和间隙锁(Gap Lock)的组合。具体来说:

  • 记录锁(Record Lock):锁定单个记录,防止其他事务对该记录进行修改。
  • 间隙锁(Gap Lock):锁定记录之间的间隙,防止其他事务在间隙中插入新的记录。

3.1.2 工作原理

Next-Key Locks 的工作原理是通过锁定一个记录及其前后的间隙,确保在一个事务中,任何插入操作都不会影响到该事务已经读取的数据范围,从而避免幻读。
假设有一个表 employees,包含以下数据:

CREATE TABLE employees (
    id INT PRIMARY KEY,
    name VARCHAR(100)
);

INSERT INTO employees (id, name) VALUES (1, 'Alice'), (2, 'Bob'), (3, 'Charlie');

在可重复读隔离级别下,事务 T1 和 T2 的操作如下:

-- 事务 T1
START TRANSACTION;
SELECT * FROM employees WHERE id BETWEEN 1 AND 3;

-- 事务 T2
START TRANSACTION;
INSERT INTO employees (id, name) VALUES (4, 'David');
COMMIT;

-- 事务 T1
SELECT * FROM employees WHERE id BETWEEN 1 AND 3;
COMMIT;

在上述操作中,T1 在第一次查询时会锁定 id 在 1 到 3 之间的记录及其前后的间隙:

  • 锁定记录 id=1 及其前后的间隙 (-∞, 1]
  • 锁定记录 id=2 及其前后的间隙 (1, 2]
  • 锁定记录 id=3 及其前后的间隙 (2, 3]
  • 锁定记录 id=4 及其前后的间隙 (3, +∞)

由于 T1 使用的是可重复读隔离级别,InnoDB 通过 Next-Key Locks 确保 T1 在第二次查询时,读取的结果集不会受到 T2 插入操作的影响,从而避免了幻读。

3.1.3 Next-Key Locks 的应用场景

Next-Key Locks 主要应用于以下隔离级别:

  • 可重复读(REPEATABLE READ):在该隔离级别下,InnoDB 使用 Next-Key Locks 确保在一个事务中,读取的数据集在整个事务期间保持一致,避免幻读。
  • 串行化(SERIALIZABLE):在该隔离级别下,InnoDB 通过 Next-Key Locks 确保所有读取操作都加锁,事务之间完全隔离,避免幻读。

3.1.4 Next-Key Locks 的优缺点

优点:

  • 避免幻读:通过锁定记录及其前后的间隙,Next-Key Locks 可以有效避免幻读问题。
  • 数据一致性:在高并发环境下,Next-Key Locks 可以确保数据的一致性

缺点:

  • 锁粒度较大:由于 Next-Key Locks 锁定了记录及其前后的间隙,锁粒度较大,可能会影响并发性能。
  • 死锁风险:在高并发环境下,Next-Key Locks 可能会导致死锁,需要进行死锁检测和处理。
    死锁的详细原因下面我们展开说。

3.2 间隙锁(Gap Lock)

间隙锁是 Next-Key Locks 的一个重要组成部分,用于锁定记录之间的间隙,防止其他事务在间隙中插入新的记录。间隙锁的范围包括:

  • 起始记录之前的间隙,例如 (-∞, 1)
  • 两条记录之间的间隙,例如 (1, 2)
  • 结束记录之后的间隙,例如 (3, +∞)
    通过锁定这些间隙,InnoDB 可以确保在一个事务中,任何插入操作都不会影响到该事务已经读取的数据范围,从而避免幻读。

3.3 MVCC(多版本并发控制)

多版本并发控制(MVCC, Multi-Version Concurrency Control)是一种用于管理数据库并发访问的技术。MVCC 通过为每个事务提供一个一致的视图,确保在高并发环境下,事务可以独立地进行读写操作,而不会相互干扰。InnoDB 存储引擎在实现可重复读(REPEATABLE READ)和读已提交(READ COMMITTED)隔离级别时,广泛使用了 MVCC 技术。

3.3.1 基本原理

MVCC 的核心思想是为每个数据行维护多个版本,并通过版本号或时间戳来区分这些版本。每个事务在读取数据时,会根据事务开始时的快照视图,读取符合其版本号或时间戳的数据。这样,不同事务可以同时读取和写入数据库,而不会相互阻塞。
数据版本
在 InnoDB 中,每行数据都有两个隐藏的列,用于实现 MVCC:

  • 事务 ID(Transaction ID):表示创建或最后修改该行数据的事务 ID。
  • 回滚指针(Rollback Pointer):指向数据行的前一个版本,用于实现回滚操作。

当一个事务对数据行进行修改时,会创建该数据行的一个新版本,并更新事务 ID 和回滚指针。

3.3.2 实现细节

MVCC 主要通过以下两个操作来实现:

  • 快照读(Snapshot Read)
  • 当前读(Current Read)
3.3.2.1 快照读(Snapshot Read)

快照读是指事务读取数据时,读取的是数据的快照版本,而不是当前最新的数据。快照版本是事务开始时的数据状态。快照读不会加锁,因此可以实现高效的并发访问。
快照读的典型操作包括:
SELECT 语句(不带 FOR UPDATE 或 LOCK IN SHARE MODE)
这个面试被问过,大家注意

3.3.2.2 当前读(Current Read)

当前读是指事务读取数据时,读取的是当前最新的数据,并且会对读取的数据加锁,以确保数据一致性。当前读通常用于更新操作。

当前读的典型操作包括:

  • SELECT … FOR UPDATE
  • SELECT … LOCK IN SHARE MODE
  • UPDATE
  • DELETE
  • INSERT

3.3.3 MVCC 在不同隔离级别下的表现

MVCC 在不同的隔离级别下有不同的表现:

  1. 读未提交(READ UNCOMMITTED):在该隔离级别下,事务可以读取其他事务未提交的数据,不使用 MVCC。
  2. 读已提交(READ COMMITTED):在该隔离级别下,事务每次读取数据时,读取的是当前最新的已提交版本。MVCC 确保事务读取的数据是已提交的最新版本。
  3. 可重复读(REPEATABLE READ):在该隔离级别下,事务在整个生命周期内,读取的是事务开始时的一致性视图。MVCC 确保事务读取的数据在整个事务期间保持一致。
  4. 串行化(SERIALIZABLE):在该隔离级别下,事务之间完全隔离,所有读取操作都加锁,不使用 MVCC。

3.3.4 MVCC 的优缺点

优点:

  • 高并发性能:通过快照读,事务可以在不加锁的情况下读取数据,提高了并发性能。
  • 减少锁争用:MVCC 避免了读写锁争用问题,提高了系统的吞吐量。
  • 数据一致性:通过为每个事务提供一致性视图,MVCC 确保了数据的一致性和隔离性。

缺点:

  • 存储开销:由于每行数据需要维护多个版本,MVCC 会增加存储开销。
  • 垃圾回收:需要定期清理过期的版本数据,以防止存储空间的浪费。
  • 实现复杂:MVCC 的实现需要维护复杂的数据结构和版本管理逻辑。

假设有一个表 employees,包含以下数据:

CREATE TABLE employees (
    id INT PRIMARY KEY,
    name VARCHAR(100)
);

INSERT INTO employees (id, name) VALUES (1, 'Alice'), (2, 'Bob'), (3, 'Charlie');

在可重复读隔离级别下,事务 T1 和 T2 的操作如下:

-- 事务 T1
START TRANSACTION;
SELECT * FROM employees WHERE id = 1;

-- 事务 T2
START TRANSACTION;
UPDATE employees SET name = 'Bob Updated' WHERE id = 2;
COMMIT;

-- 事务 T1
SELECT * FROM employees WHERE id = 2;
COMMIT;

在上述操作中,T1 在第一次查询时读取了 id=1 的记录。此时,T2 更新了 id=2 的记录,并提交了事务。由于 T1 使用的是可重复读隔离级别,InnoDB 通过 MVCC 确保 T1 在第二次查询时,读取的 id=2 的记录仍然是事务开始时的一致性视图,而不是 T2 更新后的数据。

4. 高并发环境下,Next-Key Locks 死锁分析

在高并发环境下,Next-Key Locks(间隙锁)可能会导致死锁的原因主要包括以下几个方面:

4.1 锁竞争

在高并发环境中,多个事务可能会同时尝试锁定相同的记录或间隙。由于 Next-Key Locks 锁定的范围较大,锁竞争的概率增加。例如,两个事务可能会同时尝试插入不同的记录,但由于间隙锁的存在,它们可能会互相等待对方释放锁,从而导致死锁。
假设有一个表 employees,包含以下数据:

CREATE TABLE employees (
    id INT PRIMARY KEY,
    name VARCHAR(100)
);

INSERT INTO employees (id, name) VALUES (1, 'Alice'), (3, 'Charlie');

在高并发环境下,事务 T1 和 T2 的操作如下:

-- 事务 T1
START TRANSACTION;
SELECT * FROM employees WHERE id = 1;
-- 锁定记录 id=1 及其前后的间隙 (-∞, 1] 和 (1, 3)

-- 事务 T2
START TRANSACTION;
SELECT * FROM employees WHERE id = 3;
-- 锁定记录 id=3 及其前后的间隙 (1, 3] 和 (3, +∞)

-- 事务 T1
INSERT INTO employees (id, name) VALUES (2, 'Bob');
-- 尝试锁定间隙 (1, 3),但被事务 T2 锁定

-- 事务 T2
INSERT INTO employees (id, name) VALUES (2, 'David');
-- 尝试锁定间隙 (1, 3),但被事务 T1 锁定

在上述操作中,T1 和 T2 互相等待对方释放间隙锁,从而导致死锁。

4.2 锁顺序不一致

如果不同事务获取锁的顺序不一致,也可能导致死锁。例如,一个事务先锁定记录 A 再锁定记录 B,而另一个事务先锁定记录 B 再锁定记录 A,这种锁顺序的不一致可能导致两个事务互相等待对方释放锁,从而导致死锁。
假设有一个表 employees,包含以下数据:

CREATE TABLE employees (
    id INT PRIMARY KEY,
    name VARCHAR(100)
);

INSERT INTO employees (id, name) VALUES (1, 'Alice'), (2, 'Bob'), (3, 'Charlie');

在高并发环境下,事务 T1 和 T2 的操作如下:

-- 事务 T1
START TRANSACTION;
SELECT * FROM employees WHERE id = 1;
-- 锁定记录 id=1 及其前后的间隙 (-∞, 1] 和 (1, 2)

-- 事务 T2
START TRANSACTION;
SELECT * FROM employees WHERE id = 3;
-- 锁定记录 id=3 及其前后的间隙 (2, 3] 和 (3, +∞)

-- 事务 T1
SELECT * FROM employees WHERE id = 3;
-- 尝试锁定记录 id=3 及其前后的间隙 (2, 3] 和 (3, +∞),但被事务 T2 锁定

-- 事务 T2
SELECT * FROM employees WHERE id = 1;
-- 尝试锁定记录 id=1 及其前后的间隙 (-∞, 1] 和 (1, 2),但被事务 T1 锁定

在上述操作中,T1 和 T2 获取锁的顺序不一致,导致互相等待对方释放锁,从而导致死锁。

4.3 锁粒度较大

Next-Key Locks 锁定的范围较大,包括记录及其前后的间隙,这增加了锁冲突的概率。在高并发环境下,锁粒度较大的情况下,多个事务可能会同时尝试锁定相同的间隙,从而导致死锁。
假设有一个表 employees,包含以下数据:

CREATE TABLE employees (
    id INT PRIMARY KEY,
    name VARCHAR(100)
);

INSERT INTO employees (id, name) VALUES (1, 'Alice'), (3, 'Charlie');

在高并发环境下,事务 T1 和 T2 的操作如下:

-- 事务 T1
START TRANSACTION;
SELECT * FROM employees WHERE id BETWEEN 1 AND 3;
-- 锁定记录 id=1 和 id=3 及其前后的间隙 (-∞, 1]、(1, 3] 和 (3, +∞)

-- 事务 T2
START TRANSACTION;
INSERT INTO employees (id, name) VALUES (2, 'Bob');
-- 尝试锁定间隙 (1, 3),但被事务 T1 锁定

-- 事务 T1
INSERT INTO employees (id, name) VALUES (4, 'David');
-- 尝试锁定间隙 (3, +∞),但被事务 T2 锁定

在上述操作中,T1 和 T2 由于锁粒度较大,互相等待对方释放锁,从而导致死锁。

4.4 解决死锁的方法

  • 合理设计事务:尽量减少事务的执行时间,避免长时间持有锁。
  • 统一锁定顺序:确保不同事务获取锁的顺序一致,避免锁顺序不一致导致的死锁。
  • 分解大事务:将大事务分解为多个小事务,减少锁粒度和锁冲突的概率。
  • 死锁检测和回滚:InnoDB 内置了死锁检测机制,可以自动检测到死锁并回滚其中一个事务。应用程序可以捕获死锁异常并重试操作。

粉丝福利

博主经营的脱单圈子,定期组织线下免费活动,有兴趣的单身小伙伴可以添加
在这里插入图片描述

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

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

相关文章

PyTorch深度学习实战(2)——PyTorch快速入门

PyTorch的简洁设计使得它易于入门,在深入介绍PyTorch之前,本文先介绍一些PyTorch的基础知识,以便读者能够对PyTorch有一个大致的了解,并能够用PyTorch搭建一个简单的神经网络。 1 Tensor Tensor是PyTorch中最重要的数据结构&#…

docker、k8s部署 mysql group replication 和 ProxySQL 读写分离

MySQL Group Replication(简称MGR)是MySQL官方推出的一个高可用与高扩展的解决方案。MySQL组复制它提供了高可用、高扩展、高可靠的MySQL集群服务,这里部署的 mysql 版本 5.7.33,架构是一读一写。特别要注意一个关键点: 必须保证各…

sqli-labs-php7-master第11-16关

猜注入点 先来猜数字型 单引号字符型: 发现注入点找到了 猜测数据库有多少个字段: 1’ order by 4 # 密码随便输的。 这里没有使用--注释,因为没作用,可能是过滤掉了 继续猜。刚才没猜对 1 order by 2 # 没报错,猜…

如何将neo4j,4.x版本部署到服务器上

一. 简介 当我们使用neo4j构建知识图谱时,我们希望让别人能和我们共用neo4j进行知识图谱的构建,我们的方法之一就是将neo4j部署到我们的服务器上,然后将7474,7687端口暴露出来,这样就可以通过访问服务器公网IP的7474端口来操作我…

电脑硬盘坏了数据可以恢复吗?如何恢复硬盘数据?

电脑硬盘坏了数据可以恢复吗?对于这种问题,还需要具体问题具体分析的,一般是可以恢复。 硬盘损坏可以分为物理损坏和逻辑损坏两种情况: 1.逻辑损坏 这通常是由于软件问题,如文件系统错误、病毒攻击、误删除、格式化等…

CentOS Linux release 7.9.2009 中sudo命令未找到

先在 Windows 环境中下载 sudo 的安装包 选择适合自己 Centos 版本的安装包下载到本地:https://www.sudo.ws/releases/stable/ 然后把安装包拷贝的 Centos (Linux系统)中,cd 进入安装包所在的目录执行下面的命令: 格…

【Unity】线性代数基础:矩阵、矩阵乘法、转置矩阵、逆矩阵、正交矩阵等

文章目录 矩阵(Matrix)矩阵能干啥?矩阵基本运算矩阵加减法矩阵和标量的乘法矩阵和矩阵的乘法矩阵的转置矩阵相等 特殊的矩阵方块矩阵对称矩阵对角元素(Diagonal Elements)对角矩阵(Diagonal Matrix&#xf…

sqli-labs-master初学者题目练习

Less-1 从源码可以看出id为注入点,且为单引号过滤 使用 闭合 --为注释 原本应该用--‘space’,但-与‘连在一起无法起到注释作用 order by为联合查询——同时查询两张表,但两张表列数必须相同 所有从以上两张图可以看出此表格有三列数据 爆…

计算机网络知识汇总(超详细整理)从零基础入门到精通,看完这一篇就够了

文章目录 前言一、计算机网络概述 1 互联网的构成2.网络分类3.接入网4.网络核心的两大功能 ①路由②转发 5.网络分层 ①OSI 7层模型②TCP/IP 4层模型③两种模型比较 二、物理层 1.物理介质 ①引导型介质②非引导型介质 2.数据交换方式 ①分组交换②电路交换 3.信道复用 …

在亚马逊云科技AWS上利用PEFT和RLHF高效微调AI大模型减少有害回复

简介: 小李哥将继续每天介绍一个基于亚马逊云科技AWS云计算平台的全球前沿AI技术解决方案,帮助大家快速了解国际上最热门的云计算平台亚马逊云科技AWS AI最佳实践,并应用到自己的日常工作里。 本次我将介绍如何用亚马逊云科技的AI模型训练服…

基于K8S部署安装Jenkins

基于K8S部署安装Jenkins 1.Jenkins Kubernetes 清单文件2.Kubernetes Jenkins 部署1:为 Jenkins 创建 Namespace。 最好将所有DevOps工具分类为与其他应用程序分开的命名空间。2:创建“serviceAccount.yaml”文件并复制以下管理员服务帐户清单。1. kubec…

174.地下城游戏——LeetCode

题目 恶魔们抓住了公主并将她关在了地下城 dungeon 的 右下角 。地下城是由 m x n 个房间组成的二维网格。我们英勇的骑士最初被安置在 左上角 的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。 骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻…

文章生成器免费版,自动写作文章让你无创作之忧

对于创作者而言,长期的内容输出都会遇到写作瓶颈发生,这常常让许多创作者陷入写作困难。而当前解决写作困难最好的方法就是文章生成器了,它自动写作文章的优势让广大的创作者有目共睹,并且是很多创作者们在内容创作中人手必备的神…

Gateway实现Redis拉取信息+用户模块开发

文章目录 🌞 Sun Frame:SpringBoot 的轻量级开发框架(个人开源项目推荐)🌟 亮点功能📦 spring cloud模块概览常用工具 🔗 更多信息1.Gateway实现Redis拉取信息1.目录结构2.RedisConfig.java3.Re…

无人机动力系统详解

一、动力源 动力源是无人机动力系统的核心,负责提供飞行所需的能量。 二、传动系统 传动系统负责将动力源产生的能量传递到无人机的旋翼或螺旋桨上,使其产生升力。 三、控制系统 控制系统是无人机动力系统的“大脑”,它根据飞行指令和传…

C++ 正则表达式调试器

设计背景 部分网页正则表达式测试工具,输入$匹配符会卡死,决定自己实现一个 界面设计 关键代码 void RegexpDialog::on_edt_regx_textChanged(const QString &pattern) {auto text ui->edt_content->text();QRegularExpression regxp(patte…

C语言 ——— 学习并使用 strtok 函数

目录 strtok函数的功能 strtok函数的参数以及返回值​编辑 使用strtok函数 使用方法一:根据需要分段的字符串写代码 使用方法二:配合for循环巧妙使用 strtok函数的功能 将字符串拆分为各个段,举例说明: 输入: 第…

LVS-DR模式集群:案例与概念

DR模式(直接路由) 概念 Direct Routing,简称DR模式采用半开放式的网络结构,与TUN模式的结构类似,但内网服务器并不是分散在各地,而是与调度器位于同一个物理网络负载调度器与内网服务器通过本地网络连接&a…

SQL Server 临时存储过程及示例

在本文中,我们将深入探讨 SQL Server 中的临时存储过程,并提供一些实际的示例。在我们之前的文章中,我们讨论了 SQL Server 存储过程中的返回值。本文将详细介绍以下内容: 什么是 SQL Server 临时存储过程? 在数据库…

什么是面包板和杜邦线?

1.面包板:搭建电路 2.杜邦线:电流的连接线 分为三类:公公线,公母线,母母线