MySQL事务的原理ACID

MySQL 事务的原理(ACID)

关键词:ACID、redo log、undo log、锁、MVCC、隔离级别。
目标:搞清楚“事务是什么、为什么能回滚、为什么能隔离”。


一、什么是事务?

在 MySQL(主要指 InnoDB 引擎)中,**事务(Transaction)**就是:

一组要么全部成功、要么全部失败的操作。

典型例子:转账

A 给 B 转 100 元:
1)A 账户减 100
2)B 账户加 100

这两个更新必须要么都成功,要么都不生效,否则就会出现“钱凭空出现或消失”的情况。

事务就是为了保证这类操作的整体性和安全性

数据库里对事务的要求就是那 4 个字母:ACID


二、ACID 四大特性概览

ACID 是事务必须满足的四个特性:

  1. 原子性(Atomicity)
  2. 一致性(Consistency)
  3. 隔离性(Isolation)
  4. 持久性(Durability)

下面分别说是什么 + MySQL(InnoDB)是怎么做到的。


三、A:原子性(Atomicity)

3.1 概念

原子性 = 事务中的操作要么全部成功,要么全部失败。
不存在“只执行了一半”的状态对外可见。

转账例子:

  • A 减了 100,B 没加 100 ⇒ 不允许;
  • A 没减,B 加了 100 ⇒ 也不允许;
  • 要么两个操作都成功,要么都回滚。

3.2 InnoDB 如何实现原子性?—— 依靠 undo log

  • InnoDB 在执行更新前,会先记录一份“之前的数据”到 undo log(回滚日志)
  • 如果事务回滚,就根据 undo log 把数据恢复到修改前的样子。

举个简单例子:

原始:A = 1000

事务中:UPDATE account SET balance = balance - 100 WHERE id = 'A';

内部分两部分:

  1. 把 A 从 1000 改成 900;
  2. 在 undo log 里记录一条信息:A 的 balance 从 1000 改成 900,可以还原为 1000。

如果事务失败 / 回滚:

  • 通过 undo log 把 balance 从 900 改回 1000;
  • 对外看起来,就像这个事务从来没发生过一样。

小结:

  • undo log 是实现事务回滚的关键
  • 有了 undo log,才能保证“要么全做,要么全不做”的原子性。

四、C:一致性(Consistency)

4.1 概念

一致性 = 事务前后,数据库从一种合法状态转换到另一种合法状态

“合法状态”通常指:

  • 满足各种约束:主键约束、唯一约束、外键约束、检查约束等;
  • 满足业务规则:比如总资产 = 各账户余额之和,不允许负数账户等。

一致性是一个结果属性

  • 它依赖于原子性、隔离性、持久性 + 应用自身的业务逻辑一起保障。

常见例子:

  • 转账过程中的中间状态(比如 A 已减、B 未加)是不会对外暴露的;
  • 事务提交成功后,数据必须满足各种约束条件,否则会回滚。

4.2 约束失败导致事务回滚

比如:

CREATE TABLE user (
  id INT PRIMARY KEY,
  name VARCHAR(20) UNIQUE
);

如果事务中插入了两个相同 name 的用户:

START TRANSACTION;

INSERT INTO user(id, name) VALUES(1, 'Tom');
INSERT INTO user(id, name) VALUES(2, 'Tom');  -- 冲突

COMMIT;  -- 提交时发现唯一约束冲突

这个事务会失败并回滚,不一致的数据不会被提交

小结:

  • 一致性更多依赖于:数据库的各种约束 + 应用层逻辑 + 事务本身的 A / I / D。
  • MySQL 负责在约束被破坏时让事务提交失败,从而保证最终一致性。

五、I:隔离性(Isolation)

5.1 概念

隔离性 = 并发执行的多个事务之间,在一定程度上互相“隔离”,互不干扰。

理想情况:

  • 多个事务并发执行的结果,应该等价于某种顺序(串行)执行的结果。
  • 这就是所谓的“可串行化(Serializable)”。

但完全串行执行会让性能掉死,所以数据库提供了多个隔离级别,在“性能”和“隔离性”之间做平衡。

5.2 隔离级别(四种)

SQL 标准定义了 4 个隔离级别(MySQL InnoDB 全支持):

  1. READ UNCOMMITTED(读未提交)
  2. READ COMMITTED(读已提交)
  3. REPEATABLE READ(可重复读)——MySQL InnoDB 默认
  4. SERIALIZABLE(串行化)
5.2.1 读未提交(READ UNCOMMITTED)

问题:

  • 可能出现脏读(Dirty Read)
    读到别的事务还没提交的修改。

一般不开这个级别。

5.2.2 读已提交(READ COMMITTED)

特点:

  • 只能读到已经提交的数据;
  • 避免了脏读。

问题:

  • 可能出现不可重复读(Non-Repeatable Read)
    同一个事务中,两次查询同一行数据,结果可能不一样。
5.2.3 可重复读(REPEATABLE READ)——InnoDB 默认

特点:

  • 同一个事务里,多次读取同一行数据,结果保持一致;
  • 避免了脏读 + 不可重复读。

问题:

  • 仍然可能出现幻读(Phantom Read)
    同一个事务中,多次按条件查询,行数可能不一样(有新插入的数据“凭空出现”)。

InnoDB 在这个级别下,通过 MVCC + 间隙锁(Next-Key Lock) 组合手段来“解决/缓解”幻读问题。

5.2.4 串行化(SERIALIZABLE)

特点:

  • 强制所有事务串行执行,或者在读写时加上更严格的锁;
  • 隔离性最好,但并发能力最差;
  • 一般只在对数据一致性要求极端严格且并发不高时使用。

5.3 InnoDB 如何实现隔离?—— MVCC + 锁

隔离性背后有两套核心机制:

  1. MVCC(多版本并发控制)
  2. 锁(行锁、间隙锁、意向锁等)
5.3.1 MVCC(多版本并发控制)

MVCC 的目标:

在大多数“读”的场景下,让读操作不阻塞写,写操作也尽量不阻塞读。

做法:

  • 每行记录有多个版本(通过 undo log + 隐藏列 来实现);
  • 每个事务在开始时,会拿到一个“事务 ID”并创建一致性视图(Read View);
  • 读数据时:根据事务 ID + 行的版本信息,找到对当前事务可见的版本

通俗理解:

  • 写数据时,会产生新版本,旧版本保留在 undo log 中;
  • 读数据时,如果当前最新版本对该事务不可见,就去看更早的版本;
  • “读已提交 / 可重复读”的区别,就是“视图”是每次查询新建还是事务开始时固定。
5.3.2 锁

简单按粒度分:

  • 行锁:锁某一行记录;
  • 间隙锁(Gap Lock):锁一个范围(间隙),防止插入;
  • Next-Key Lock:行锁 + 间隙锁,锁 [前一个值, 当前值] 区间;
  • 表锁:锁整张表(InnoDB 也有,但事务中一般用的是行锁+间隙锁)。

按用途分:

  • 共享锁(S 锁):读锁,可多事务共享;
  • 排他锁(X 锁):写锁,互斥。

InnoDB 默认是 行级锁 + MVCC

  • 查询时尽量通过 MVCC 读取“历史版本”,避免加锁;
  • 修改时才会加行锁 / 间隙锁,保证并发修改不会乱。

小结:

  • MVCC 负责“读写互不挡”
  • 锁负责“写写不乱 + 控制插入/删除范围”
  • 两者配合实现各种隔离级别。

六、D:持久性(Durability)

6.1 概念

持久性 = 一旦事务提交,它对数据库所做的修改就是永久性的,即使发生故障也不会丢失。

比如:

  • 事务提交成功 → 服务器立刻宕机 → 重启后,已提交的数据仍然存在。

6.2 InnoDB 如何实现持久性?—— redo log + binlog

主要依赖两类日志:

  1. redo log(重做日志)—— InnoDB 引擎级别
  2. binlog(二进制日志)—— MySQL Server 层级别
6.2.1 redo log:物理日志,保证崩溃恢复
  • 记录的是“对某个数据页做了怎样的物理改动”;
  • 写入顺序追加,I/O 顺序性较好;
  • 在事务提交前,相关的 redo log 必须刷到磁盘(或根据参数控制刷盘策略)。

简化流程:

  1. 事务执行期间:
    • 修改内存中的数据页(Buffer Pool);
    • 生成 redo log 写入 redo 日志缓冲区;
  2. 事务提交时:
    • 至少要保证 redo log 已经刷新到磁盘(取决于 innodb_flush_log_at_trx_commit 设置);
  3. 崩溃恢复:
    • 重启时读取 redo log,根据其内容重新把“已经提交但尚未写入磁盘的数据页”恢复出来。

有 redo log,在“先改内存、后刷磁盘”的情况下,仍能保证事务一旦提交就不会丢。

6.2.2 binlog:逻辑日志,主从复制 & 恢复用
  • 记录的是“SQL 语句或行级别的逻辑变更”;
  • 是 MySQL Server 层的日志,不只 InnoDB 使用;
  • 常用于:主从复制、数据恢复(如 mysqlbinlog 回放)。
6.2.3 redo log + binlog + 两阶段提交

为保证:

  • 事务提交后既不会丢(依赖 redo log);
  • 又能被正确复制到从库/用于恢复(依赖 binlog);

InnoDB 使用两阶段提交机制协调 redo log 和 binlog:

大致过程:

  1. 写入 redo log,标记为 prepare 状态;
  2. 写入 binlog;
  3. 将 redo log 标记为 commit 状态;

这样:

  • 崩溃恢复时,如果 redo 是 prepare 而 binlog 不完整,可以回滚;
  • 如果 redo 是 commit,说明 binlog 也已经写好了,可以重放。

小结:

  • redo log:保证“崩溃恢复”级别的持久性;
  • binlog:保证“复制 / 审计 / 逻辑恢复”;
  • 两者配合 + 两阶段提交,确保事务提交后既不丢,又能被正确复制与恢复。

七、ACID 与 MySQL 关键机制的对应关系

可以简单打个表:

特性主要依赖的机制
原子性 Aundo log(回滚日志)、崩溃恢复流程
一致性 C约束(PK/UK/FK 等)+ 应用逻辑 + A/I/D 综合效果
隔离性 I锁(行锁、间隙锁)+ MVCC + 隔离级别
持久性 Dredo log + binlog + 刷盘策略 + 两阶段提交

注意:

  • 一致性并不是由某一个内部机制直接保证,而是 ACID 四个特性 + 业务规则共同保证。

八、总结:从“表面行为”到“内部原理”

  1. 事务是什么?

    • 一组操作的逻辑整体,要么全部成功、要么全部失败。
  2. 为什么能回滚?

    • 因为有 undo log,记录了修改前的值。
  3. 为什么不会互相乱改?

    • 因为有 控制并发写;
    • MVCC 让读尽量不用加锁,又能看到一致视图。
  4. 为什么断电后数据还在?

    • 因为有 redo log 刷盘;
    • 重启时可以根据 redo 把已经提交的数据恢复出来。
  5. 为什么叫 ACID?

    • A:undo log 支撑回滚 → 原子性;
    • C:约束 + 逻辑 + A/I/D 综合保证一致性;
    • I:锁 + MVCC + 隔离级别控制并发可见性;
    • D:redo log + binlog + 两阶段提交保证持久性。

理解 ACID 背后的这些“日志 + 锁 + 版本”的组合拳,你基本就把 MySQL 事务的核心原理拿下来了。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值