浅谈 Mysql 事务 由 ACID 到 MVCC

本文深入解析MySQL事务的ACID特性,探讨隔离级别如何防止脏读、不可重复读和幻读,详解锁和MVCC在并发控制中的角色及区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近公司的技术分享一直在分享 mysql 事务,隔离级别,锁和 MVCC 方面的知识,但是都是基于单个点进行分析,没有一步一步来阐述,所以在这里我整理了一下 mysql 由 ACID 到 MVCC 这一块的知识准备分享给大家。

咱们开门见山先谈 事务
  • 首先思考事务有什么作用?
    • 事务是为了保证数据库中数据的完整性和一致性
  • 事务的四个基本要素 ACID ?这四要素是怎么实现的?
    • 原子性 Atomicity
      • 原子性通过 Innodb undo log 即回滚日志实现
    • 一致性 Consistency
      • 一致性通过 lock 锁来实现
    • 隔离性 Isolation
      • 隔离性通过 lock 锁和 MVCC 来实现
    • 持久性 Durability
      • 持久性通过 Innodb redo log 即重写日志实现 ,和 doublewrite 两次写技术来实现

现在我们开始谈 隔离级别
  • 什么是隔离级别 ?
    • 并发事务之间相互影响的程度,隔离级别越高影响程度就越低
    • 并发事务会造成的问题
      • 脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
      • 不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。
      • 幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读
  • 隔离级别的种类
    • read-uncommitted 读未提交
      • 读数据时不加锁,写数据时加行级共享锁
    • read-committed 读已提交
      • 读数据使用 MVCC,写数据加行级排它锁
    • repeatable-read 可重复读
      • 读数据使用 MVCC,写数据加行级排它锁和 gap、next-key lock
        • 如果是用到了主键的话加锁
    • serializable 串行化
      • 读数据加表级共享锁,写数据加表级排它锁

在 Mysql 的并发控制中我们常常需要使用到 锁 和 MVCC,那么这两者的使用场景有什么区别呢,为什么有了锁还要引入 MVCC 呢?下面我将为大家娓娓道来这两者使用上的区别
基于锁的并发控制
  • LBCC (Lock-Based Concurrent Control) 即基于锁的并发控制
  • Mysql 的锁由 共享锁(读锁)排它锁(写锁) 组成
  • 按粒度也可以分为表锁和行锁
  • 加锁和解锁遵循 2PL两阶段锁原则
  • 2PL就是将加锁/解锁分为两个完全不相交的阶段。加锁阶段:只加锁,不放锁。解锁阶段:只放锁,不加锁
  • 若所有事务均遵守两段锁协议,则这些事务的所有交叉调度都是可串行化1

多版本的并发控制
  • MVCC 即 (Multi-Version Concurrency Control) 多版本的并发控制协议
  • MVCC 最大的好处可以概括为读不加锁,读写不冲突
  • MVCC 并发控制中,读操作可以分成两类:快照读 (snapshot read)当前读 (current read)
    • 快照读,读取的是记录的可见版本 (有可能是历史版本),不用加锁
      • 简单的select操作,属于快照读,不加锁。
        • SELECT * FROM user WHERE ?
    • 当前读,读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录
      • 特殊的读操作,插入/更新/删除操作,属于当前读,需要加锁。
        • 特殊读 (加锁读)
          • SELECT * FROM user WHERE ? LOCK IN SHARE MODE;
        • 插入/更新/删除
          • SELECT * FROM user WHERE ? FOR UPDATE;
          • INSERT INTO user VALUES (…);
          • UPDATE user SET ? WHERE ?;
          • DELETE FROM user WHERE ?;

  1. 当且仅当某组并发事务的交叉调度产生的结果和这些事务的某一串行调度的结果相同,则称这个交叉调度是可串行化 ↩︎

### MySQL 事务ACID 特性和 MVCC 实现机制 #### 1. **MySQL 事务ACID 特性** 事务的四个核心特性 (ACID) 是指原子性 (Atomicity)、一致性 (Consistency)、隔离性 (Isolation) 和持久性 (Durability),这些特性共同保障了数据库系统的可靠性。 - **原子性 (Atomicity)** 原子性确保事务中的所有操作要么全部成功完成,要么完全回滚到初始状态。如果事务的一部分失败,则整个事务都会被撤销。这种行为由 InnoDB 存储引擎通过重做日志 (redo log) 和 undo 日志来实现[^1]。 - **一致性 (Consistency)** 一致性保证数据库始终处于一致的状态,事务完成后不会破坏数据库的整体约束条件或业务逻辑。这依赖于事务的正确设计以及存储引擎的支持[^2]。 - **隔离性 (Isolation)** 隔离性是指多个并发事务之间互不影响。InnoDB 使用多版本并发控制 (MVCC) 来提供不同级别的隔离效果,从而减少锁的竞争并提高性能。常见的隔离级别包括读未提交 (Read Uncommitted)、读已提交 (Read Committed)、可重复读 (Repeatable Read) 和串行化 (Serializable)。 - **持久性 (Durability)** 持久性意味着一旦事务提交成功,其更改将永久保存在磁盘上,即使发生系统崩溃也能恢复数据。这一特性的实现主要依靠 redo log 和 binlog 的配合工作[^3]。 --- #### 2. **MVCC 工作原理** MVCC(Multi-Version Concurrency Control)是一种用于管理并发访问的技术,在保持高吞吐量的同时减少了锁定开销。以下是其实现的关键点: - **版本链与隐藏字段** 每一行记录都维护着两个隐式的元数据列:`DB_TRX_ID` 表示最后一次修改该行的事务 ID,`DB_ROLL_PTR` 则指向该行的历史版本所在的位置。当新版本创建时,旧版本会被保留下来形成一条版本链[^4]。 - **Read View 的生成规则** 当一个事务启动时会生成一个只属于它的 `read view`,它包含了当前活跃事务列表及其范围信息。基于此视图可以判断某条记录对于本事务来说是否可见。例如,只有那些满足以下任一条件的记录才是可见的: - 记录的 `trx_id` 小于等于 `read view` 中低水位值; - 或者记录的 `trx_id` 属于当前事务本身。 如果某个记录不符合上述任何一种情况,则认为它是不可见的,并继续沿着版本链查找更早的版本直到找到符合条件为止或者到达链末端。 - **四种常见场景下的表现** 不同隔离级别下 MVCC 的行为有所差异: - 在 RR (Repeatable Read) 下,每次查询使用的都是同一个固定 read-view,因此能够避免幻读现象的发生; - RC (Read Committed) 则会在每一次新的 SELECT 查询前重新构建一个新的 read-view ,这样可能会看到其他事务最新提交的结果,但仍然能防止脏读; - RU (Read Uncommitted) 几乎没有任何保护措施,允许查看尚未提交的数据; - Serializable 强制采用严格的两阶段封锁协议,虽然最安全但也带来了最高的锁争用成本。 ```sql -- 示例代码展示如何利用 MVCC 查看历史版本 SELECT * FROM table_name WHERE id = ? FOR UPDATE; ``` 以上 SQL 可以用来演示在一个显式开启事务的情况下,尝试更新某些特定条件下匹配的行并将它们锁定起来供后续进一步处理之前独占使用的情形。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值