数据库的MVCC如何理解?

news/2025/2/25 13:10:03/

数据库的MVCC如何理解?

MVCC(多版本并发控制,Multi-Version Concurrency Control)是数据库系统中的一种并发控制机制,用于允许多个事务在不互相干扰的情况下并行执行,同时保持数据的一致性和隔离性。

MVCC的核心思想是,数据库中的每一行数据都有多个版本(version),每个事务访问的是它开始时的数据版本。这样可以解决传统锁机制中的冲突问题(例如,读-写冲突),同时提高并发性。

关键概念:

  1. 版本控制
    每次对数据的修改(如插入、更新、删除)都会产生一个新的数据版本,而原始版本依然存在,直到它不再被需要。每个数据版本通常会有一个时间戳或者事务ID标记,表明它是由哪个事务创建的,及其生命周期。

  2. 事务视图
    每个事务看到的都是某一时刻一致的数据快照。事务在执行时,并不会直接读取数据库中当前的数据,而是读取符合其开始时间之前提交的事务所做修改的数据。

  3. 快照隔离
    MVCC提供了类似于快照隔离(Snapshot Isolation)的效果。即事务读取到的始终是事务开始时的数据快照,不会受到其他并发事务的修改影响。

  4. 死锁和写冲突的避免
    由于MVCC允许并行读取不同版本的数据,事务在读取数据时不会加锁,避免了读取时阻塞。但是,如果一个事务需要修改数据,它必须确保该数据没有被其他事务修改,或者修改的数据版本符合某些条件。

操作过程:

  1. 读取:当事务读取数据时,它会看到在事务开始时存在的数据版本(即该事务的视图),而不会被其他并发事务的修改影响。

  2. 写入:事务修改数据时,数据库会创建该数据的新版本,而原始版本不会立刻被删除。这时,其他事务仍然可以读取到旧版本的数据,直到当前事务提交。

  3. 提交与回滚

    • 提交:事务提交时,新的数据版本会成为有效版本,其他事务可以看到并使用这个版本。
    • 回滚:如果事务回滚,数据库会将事务的修改从版本链中删除,不会产生可见影响。

MVCC的优势:

  • 高并发:通过避免锁机制,允许更多的并发事务执行,提高性能。
  • 提高事务隔离性:各事务可以看到不同的数据版本,从而减少了事务间的冲突。
  • 避免脏读和不可重复读:由于每个事务只看到一致的数据快照,能有效避免脏读(读到未提交事务的数据)和不可重复读(同一事务两次读取到不同的数据)问题。

常见的MVCC实现:

  • PostgreSQL:通过系统列(例如xminxmax)来标记每行数据的版本。
  • MySQL(InnoDB):通过隐藏的系统列来实现行级版本控制,并使用Undo Log记录事务之前的数据版本。
  • Oracle:通过撤销日志(Undo Logs)和回滚段来实现MVCC。

总的来说,MVCC通过给每个事务提供一个数据快照,并允许多个版本并存,优化了并发性能,同时保证了数据的一致性和隔离性。

MySQL数据库MVCC的应用过程

在 MySQL 中,特别是在 InnoDB 存储引擎中,MVCC 是通过系统版本管理和回滚日志来实现的。以下是一个简单的例子,演示了 MVCC 在 MySQL 中的应用过程。

1. 准备工作:

假设我们有一个名为 users 的表,内容如下:

CREATE TABLE users (id INT PRIMARY KEY,name VARCHAR(50),age INT
);

我们插入一条数据:

INSERT INTO users (id, name, age) VALUES (1, 'Alice', 30);

2. 事务A:读取数据(开始时的数据快照)

假设事务A在时间T1开启:

START TRANSACTION;

此时,users 表中有一行数据:(1, 'Alice', 30)

事务A执行读取操作:

SELECT * FROM users WHERE id = 1;

事务A看到的数据是事务A开始时的快照:(1, 'Alice', 30)

3. 事务B:修改数据(创建新版本)

在事务A执行读取操作后,事务B在时间T2开启,并对 users 表的数据进行更新:

START TRANSACTION;
UPDATE users SET age = 31 WHERE id = 1;

此时,事务B对数据 id = 1age 值进行更新,变成了 31。但是,这个修改操作不会影响事务A的读取,因为事务A仍然看到它开始时的数据版本。

在内部,InnoDB 创建了一个新的数据版本,标记该版本为由事务B创建。此时,数据库中会有两个版本的 id = 1 行数据:

  • 版本1:(1, 'Alice', 30),由事务A读取。
  • 版本2:(1, 'Alice', 31),由事务B修改。

4. 事务A:提交(无影响)

事务A继续执行并提交:

COMMIT;

虽然事务A提交了,但它看到的仍然是事务开始时的快照(版本1),因为事务B修改的数据对事务A不可见。

5. 事务B:提交(创建新版本)

事务B提交时,新的数据版本((1, 'Alice', 31))会被正式保存,并且对其他事务可见:

COMMIT;

此时,版本1的数据 age = 30 不再有效,版本2成为当前最新的数据。其他事务(如事务C)将看到版本2的数据。

6. 最终效果

  • 事务A:在提交之前,事务A看到的是版本1(age = 30)。
  • 事务B:修改并提交后,版本2(age = 31)成为数据库的最新版本。
  • 其他事务:如果另一个事务在事务B提交后执行,它将看到版本2的数据(age = 31)。

MVCC实现细节:

  1. Undo Log:在事务B修改数据时,InnoDB 会将原始数据(版本1)记录在 Undo Log 中。这样,事务A可以读取旧版本的数据,不受事务B修改的影响。

  2. 隐藏列:每行数据会有两个隐藏的列(在查询时不可见),例如:

    • xmin:表示哪个事务创建了这个版本。
    • xmax:表示哪个事务删除了这个版本。

    事务A在读取数据时,检查每一行数据的 xminxmax,确保读取的是在事务A开始时已经提交的版本。

总结:

  • 事务A 看到的是它开始时的数据快照,而不会受到 事务B 的更新影响,即使 事务B 已经修改了数据。
  • 事务B 在提交时创建了一个新的数据版本,其他事务在提交后会看到这个新的版本。
  • 事务A 在提交后无法看到 事务B 的修改,直到它自己开始一个新的事务并获得新的数据版本。

通过这样的方式,MVCC 让多个事务并行执行而不会相互干扰,提高了数据库的并发性,同时保持了数据的一致性和隔离性。

MVCC带来的缺点有哪些?如何解决?

尽管 MVCC(多版本并发控制)数据库并发性和事务隔离性方面具有明显优势,但也带来了一些潜在的缺点和挑战。以下是 MVCC 的几个常见缺点以及可能的解决方案:

1. 空间消耗(存储开销)

MVCC 通过保存多个数据版本来实现并发控制。每次数据发生变化时,都会创建一个新版本,这会导致数据库存储空间的增加,尤其是在高并发、高频繁更新的场景下。

解决方案:
  • 版本清理:大多数 MVCC 实现(如 MySQL InnoDB)使用了自动清理机制,例如垃圾回收(GC),定期删除那些不再有任何事务需要的旧版本数据。InnoDB 使用 purge 操作清理已提交事务的过时版本。
  • 定期维护:定期执行数据库优化操作,如 OPTIMIZE TABLE,以清理碎片和释放空间。
  • 合理的保留策略:通过配置合理的事务超时和垃圾回收策略,确保旧版本的数据被及时回收,减少存储压力。

2. 性能下降(GC 和清理延迟)

由于 MVCC 需要维护多个数据版本,数据库在执行查询、插入、更新时需要额外的检查和操作,尤其是清理过时版本时,可能会影响数据库的性能。在高并发的环境中,版本的管理和回收(如清理)也可能导致性能瓶颈。

解决方案:
  • 延迟清理:通过延迟清理机制,将垃圾回收操作放在低峰时段进行,减少对高并发事务的影响。
  • 优化回收算法:优化版本清理和垃圾回收的算法,例如使用 undo logs 或者通过增量清理技术,以减少性能开销。
  • 表分区:对于大表,可以采用分区表的方式,将数据分散存储和管理,避免单表的巨大开销。

3. “幻读”问题(Phantom Read)

MVCC 通过提供不同事务的数据快照来解决脏读、不可重复读的问题,但它不能完全解决 幻读(Phantom Read) 问题。幻读是指在一个事务中进行多次查询时,查询结果发生变化。例如,一个事务查询了某个范围的数据,但在该事务期间,其他事务插入了新数据,导致查询的结果在同一事务中发生变化。

解决方案:
  • 加强隔离级别:使用 Serializable(可串行化) 隔离级别来避免幻读。在这个隔离级别下,数据库会锁住读到的数据范围,防止其他事务在该范围内插入或修改数据。
  • 基于锁的控制:结合 锁机制(如行锁、表锁等)来避免幻读。在某些数据库系统中,您可以使用 SELECT FOR UPDATE 来锁定查询结果集中的行,防止其他事务对这些行进行修改。
  • 使用查询范围控制:在设计应用程序时,尽量避免需要跨越多个查询的数据修改。通过在单次操作中完成数据变更,可以避免部分幻读的场景。

4. 长事务的影响

MVCC 使得每个事务都有自己独立的数据快照,但这也意味着长事务(运行时间较长的事务)会导致数据库的资源被占用更长时间。特别是在高并发环境下,长事务会导致更多的旧数据版本被保留,增加存储压力,并可能阻塞其他事务。

解决方案:
  • 事务控制:避免长时间运行的事务,尽量将事务操作分解为多个短小事务。
  • 事务超时设置:为事务设置合理的超时机制,避免事务运行时间过长。
  • 定期检查和优化:监控事务的执行时间,定期优化慢查询和数据库的执行计划,确保事务能尽快完成。

5. 死锁问题

尽管 MVCC 在一定程度上减少了锁争用,但它仍然不能完全避免死锁的发生,尤其是在事务需要多个版本的数据时,可能会引发死锁。

解决方案:
  • 死锁检测:许多数据库系统(如 InnoDB)内置了死锁检测机制,会自动检测并处理死锁。当检测到死锁时,数据库会回滚某个事务以解除死锁。
  • 合理的事务顺序:避免多个事务同时请求相同的数据项,采用一种统一的事务执行顺序(如锁顺序规则)来减少死锁的可能性。
  • 行级锁优化:通过尽量使用行级锁,而不是表级锁,减少事务之间的竞争和死锁发生的几率。

总结:

虽然 MVCC 在并发控制方面提供了显著的优势,但它的缺点主要包括存储空间消耗、性能开销、幻读问题、长事务的影响以及死锁问题。为了应对这些挑战,可以通过优化存储管理、使用更强的隔离级别、控制事务的长度、引入死锁检测等手段来解决这些问题,从而保持系统的高效运行。


http://www.ppmy.cn/news/1574847.html

相关文章

system运行进程以及应用场景

使用 system 函数运行进程的场景通常是在程序中需要执行外部命令或脚本时。system 是 C/C 标准库中的一个函数,用于调用操作系统的命令行解释器(如 /bin/sh 或 cmd.exe)来执行指定的命令。以下是常见的使用场景: 1. 执行简单的系统…

Python爬虫-破解字体加密技术

前言 本文是该专栏的第77篇,后面会持续分享python爬虫干货知识,记得关注。 字体加密是一种常见的反爬虫技术,通过自定义字体文件和字符映射来保护网页内容,防止爬虫直接获取文本信息。 而本文,笔者将针对“如何解决目标平台的字体加密技术,并获取目标数据”,进行详细介…

初识.git文件泄露

.git 文件泄露 当在一个空目录执行 git init 时,Git 会创建一个 .git 目录。 这个目录包含所有的 Git 存储和操作的对象。 如果想备份或复制一个版本库,只需把这个目录拷贝至另一处就可以了 这是一种常见的安全漏洞,指的是网站的 .git 目录…

[Android]让APP进入后台后不被杀掉(保活)

在 Android 系统中,应用在进入后台后,系统可能会因为各种原因(如内存不足、电池优化等)对其进行强制退出。虽然无法完全保证应用永远不会被系统强制退出,但可以采取一些措施来减少这种情况的发生。以下是几种常见的方法…

005:Cesium.viewer 知识详解、示例代码

查看本专栏目录 - 本文是第 005个API内容详解 vue+cesium 示例教程200+目录 文章目录 一、Cesium.Viewer 知识详解1. 主要用途2. 构造函数与参数3. 常用属性(1)`viewer.scene`(2)`viewer.camera`(3)`viewer.entities`(4)`viewer.clock`4. 常用方法(1)`viewer.zoomTo(…

2024年第十五届蓝桥杯青少 图形化编程(Scratch)省赛中级组真题——截取递增数

截取递增数 背景信息 递增数:如果一个大于9的正整数各个数位上的数,从左到右是逐渐变大的,那么就称这个数为递增数。 例如124、248 是递增数。 给你一个不含0的九位数,请找出从这个九位数中能截取出的所有递增数。例如:115367…

Starlink卫星动力学系统仿真建模第十讲-基于SMC和四元数的卫星姿态控制示例及Python实现

基于四元数与滑模控制的卫星姿态控制 一、基本原理 1. 四元数姿态表示 四元数运动学方程: 3. 滑模控制设计 二、代码实现(Python) 1. 四元数运算工具 import numpy as npdef quat_mult(q1, q2):"""四元数乘法""…

子集II力扣--90

目录 题目 思路 代码 题目 给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的 子集(幂集)。 解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。 示例 1: 输入…