✅ 一、事务的四大特性(ACID)
特性 | 中文名 | 简单解释 | 举例说明 |
---|---|---|---|
A - Atomicity | 原子性 | 一个事务里的所有操作是一个整体,要么全部执行成功,要么一个不成功就全部失败,不能只执行一半。 | 假如你从账户A转100元到账户B,需要先从A扣掉100,再给B加上100。如果在加给B的时候系统突然崩溃了,原子性保证整个操作会被回滚,不会只扣了A的钱但B没收到。 |
C - Consistency | 一致性 | 执行事务前后,数据库的数据必须是符合规则的、没有错误的。 | 假如银行要求所有账户总金额为2000元,无论有多少人转账,执行完所有操作后,总金额仍然必须是2000元,不能多也不能少。这叫做一致性。 |
I - Isolation | 隔离性 | 多个事务同时执行时,互不影响,彼此之间看不到对方未提交的操作。 | 张三在转账,还没提交,李四正在查账户余额。隔离性保证李四看到的还是“转账前的余额”,而不是张三操作中的“中间状态”。避免脏读、不可重复读、幻读。 |
D - Durability | 持久性 | 一旦事务提交成功,对数据库的修改就是永久保存的,即使系统崩溃、断电也不会丢失。 | 比如你网购后点击“支付”,系统提示支付成功,这时即使服务器突然断电,再次启动时订单依然是“已支付”,不会消失,这就是持久性。 |
📌 补充说明:几个常见名词解释(防止小白看不懂)
术语 | 含义 | 示例 |
---|---|---|
事务(Transaction) | 一组操作,要么一起成功,要么一起失败的执行单元 | 转账中的“扣钱”和“加钱”就组成一个事务 |
脏读(Dirty Read) | 读到了其他事务未提交的数据 | A刚修改了账户余额,B立刻读取,结果A回滚了,但B读到的是“脏”的 |
不可重复读(Non-repeatable Read) | 同一个事务中两次读取结果不同 | 第一次查余额是100元,第二次又变成了80元,因为别的事务中间改了 |
幻读(Phantom Read) | 同一事务中读取到新插入的“额外数据” | 第一次查订单数是5,后面查变成了6,因为别的事务插入了新订单 |
📌注:“不可重复读”好一点,读到的是 已提交 的数据,比如某个读事务持续时间比较长,期间多次读取某个元组,每次读到的都是被别人改过并已提交的不同数据。 可以理解为在执行任务的过程中,领导的指令一直在变。 但好歹是正式下达的指令。 “幻读”是指读的过程中,某些元组被增加或删除,这样进行一些集合操作,比如算总数,平均值等等,就会每次算出不一样的数。 所以“不可重复读”和“幻读”都是读的过程中数据前后不一致,只是前者侧重于修改,后者侧重于增删。 个人认为,严格来讲“幻读”可以被称为“不可重复读”的一种特殊情况,没错的。 但是从数据库管理的角度来看二者是有区别的。
✅ 二、InnoDB 如何保证原子性(Atomicity)
🔍 什么是原子性?
“原子性”表示一个事务中的操作要么全部成功、要么全部失败,中间不能只完成一半。
比如银行转账,“扣钱”和“加钱”必须一起成功或一起失败,不能出现扣了钱但没加上。
✔ 关键机制:Undo Log(回滚日志)
InnoDB 使用一种叫做 Undo Log(撤销日志) 的机制来保证原子性。
📌 Undo Log 是什么?
Undo Log 是数据库记录的**“修改前的数据”,可以让数据库在需要时把数据恢复成修改前的状态**。
⚙ Undo Log 的工作流程:
步骤 | 说明 |
---|---|
1️⃣ | 每当你执行一个会修改数据的操作(如 INSERT、UPDATE、DELETE)时,InnoDB 会先把原始的数据保存到 Undo Log 中 |
2️⃣ | 如果事务中间出现问题,比如程序崩溃、断电,或者你手动回滚(ROLLBACK),InnoDB 会根据 Undo Log,把数据恢复回原来的样子 |
3️⃣ | 只有当你执行 COMMIT 提交事务后,Undo Log 才会被“清理”掉 |
💡 举个具体的例子:
🏦 假设你在使用银行转账应用,执行了以下操作:
BEGIN;
UPDATE account SET balance = balance - 100 WHERE id = 1; -- A账户扣100元
UPDATE account SET balance = balance + 100 WHERE id = 2; -- B账户加100元
COMMIT;
💥 假如在第二步(B账户加钱)时,系统突然崩溃了:
-
InnoDB 已经在第1步时,把账户A“原来的余额”写进了 Undo Log;
-
程序崩溃后,数据库自动回滚事务;
-
InnoDB 会从 Undo Log 中读取 A 账户原来的余额,再把数据恢复回去;
-
最终 A 和 B 账户都不会有任何变化,转账操作就像从没发生过一样。
这就叫做:要么全部成功(扣钱 + 加钱 + 提交),要么全部失败(回滚到原样),符合“原子性”。
🧠 相关名词解释(针对小白)
名词 | 意思 | 举例说明 |
---|---|---|
Undo Log(回滚日志) | 存储被修改前的数据,用于“撤销” | A账户原来有1000元,UPDATE 执行前将 1000 记录到 Undo Log |
事务(Transaction) | 一组操作,要么全部成功,要么全部失败 | 扣钱 + 加钱 = 一个完整的转账操作 |
ROLLBACK | 手动撤销事务 | 比如你中途发现转账金额写错了,可以输入 ROLLBACK; 来撤销 |
COMMIT | 提交事务,表示“确认生效” | 没有 COMMIT ,修改不会真正保存下来 |
📝 总结一句话:
InnoDB 用 Undo Log 记录每一次修改前的数据,在事务失败时可以“反悔”,把所有操作都撤销,从而保证了事务的原子性。
✅ 三、InnoDB 如何保证一致性(Consistency)
🔍 什么是“一致性”?
一致性指的是:事务执行前后,数据库中的数据都必须处于一种“合法的、符合规则的状态”。
通俗讲:
就算系统中断、电源断了、事务失败了,数据库不能出现“逻辑错误”或“数据混乱”的情况,比如不能有两个相同主键,不能引用不存在的外键等等。
✔ 一致性的实现依赖三方面机制:
机制 | 作用 | 通俗理解 |
---|---|---|
✅ 原子性 | 保证操作要么都成功、要么全失败 | 比如转账时钱不能只扣不加 |
✅ 隔离性 | 并发事务之间互不干扰,防止读取或修改到“中间状态”的数据 | 别人在修改时,你读到的数据必须是“稳定”的 |
✅ 数据库约束机制 | 使用主键、外键、唯一约束、触发器等,强制要求数据必须合法 | 插入数据时必须符合规则,否则就不让你插入 |
📌 举例说明(一致性在生活中的体现):
💡 场景1:主键唯一性约束
你有一张用户表 user
,主键是 id
。
INSERT INTO user (id, name) VALUES (1, 'Alice');
INSERT INTO user (id, name) VALUES (1, 'Bob'); -- ❌ 报错,id=1 已存在
🧠 为什么报错?
因为主键必须唯一,不能有两个 id=1
的用户,插入第二条数据会破坏一致性,所以被数据库拒绝了。
💡 场景2:外键约束
你有两张表:订单表 orders
和用户表 user
,orders.user_id
是外键,必须指向 user.id
。
-- 用户表
INSERT INTO user (id, name) VALUES (10, 'Tom');
-- 插入订单,引用已存在的用户
INSERT INTO orders (id, user_id, item) VALUES (1, 10, 'Book'); ✅ 成功
-- 插入订单,引用不存在的用户
INSERT INTO orders (id, user_id, item) VALUES (2, 999, 'Pen'); ❌ 报错
🧠 为什么会失败?
user_id=999
在用户表中不存在,这会破坏数据的“引用关系”,导致数据库不一致,所以插入失败。
💡 场景3:删除有关联的外键数据
-- 如果订单表中有 user_id = 10 的记录:
DELETE FROM user WHERE id = 10; -- ❌ 报错
🧠 原因: 你不能直接删除 id=10
的用户,因为还有订单在用这个用户的信息。要么先删除订单,要么使用 ON DELETE CASCADE
设置级联删除。
🧠 小白术语解释
名词 | 解释 | 举例 |
---|---|---|
一致性 | 数据必须始终符合设定的规则,不能出现逻辑错误 | 不允许重复主键、不允许引用不存在的数据 |
主键(Primary Key) | 表中用来唯一标识一行的字段 | 用户表中的 id 就是主键,不能重复 |
外键(Foreign Key) | 表中的某个字段,必须指向另一个表的主键 | 订单表的 user_id 必须对应用户表的 id |
唯一约束(Unique) | 要求某个字段的值不能重复 | 邮箱字段不能重复,每个用户一个邮箱 |
触发器(Trigger) | 数据库自动执行的一段规则 | 比如插入订单时自动写日志 |
🗣 一句话总结:一致性就是“永远不能让数据库出现脏乱差的状态”。InnoDB 靠原子性、隔离性和规则强校验,确保数据始终干净整洁、逻辑自洽。
✅ 四、InnoDB 如何保证 隔离性(Isolation)
🔍 什么是“隔离性”?
隔离性指的是:
多个事务同时进行时,它们之间互不干扰,每个事务在执行过程中,都感觉自己是“独占”数据库的。
换句话说:
你做事的时候不被别人打扰,你也不会看到别人未完成的“半成品”。
✅ InnoDB 的两大隔离性保障机制:
✔ 机制一:MVCC(多版本并发控制)
📌 核心思想:
不让事务之间抢锁、打架,而是给每个事务看一份“数据快照”版本,只读自己的版本数据,互不干扰。
🔧 MVCC 的构成:
组件 | 含义 | 通俗解释 |
---|---|---|
Undo Log(回滚日志) | 存储旧版本数据,便于回滚或快照读取 | 就像给数据拍了“旧照片”,用于还原 |
版本号字段 | 每条记录有两个隐藏字段:创建版本号 和 删除版本号 | 谁创建、谁删除都有“时间戳”标记,方便判断可见性 |
事务版本号 | 每个事务开始时会获得一个版本号 | 类似于“进房间”的时间,控制它能看什么 |
🧠 怎么工作?
假设当前有一条数据:
订单ID: 1,金额: 200,创建版本号: 10,删除版本号: 0(表示未删除)
-
如果你是一个 版本号为15的事务,你可以看到这条数据;
-
如果你是一个 版本号为9的事务,你看不到,因为它是你之后才创建的
📎 哪些隔离级别用到 MVCC?
隔离级别 | 是否使用 MVCC? | 是否加锁? |
---|---|---|
READ COMMITTED(读已提交) | ✅ 是 | ❌ 否,快照随时更新 |
REPEATABLE READ(可重复读) | ✅ 是 | ❌ 否,事务期间快照固定 |
📌 举个例子:
-- 事务A
BEGIN;
SELECT * FROM orders WHERE amount > 100;
-- 事务B
UPDATE orders SET amount = 80 WHERE id = 1;
COMMIT;
-
事务A查询时,看到的是执行时刻的数据快照;
-
事务B即使修改了数据,但事务A仍然看到“原来的版本”,直到它提交。
✔ 机制二:锁机制
🔐 类型 1:共享锁(S锁)
-
用于读操作
-
允许多个事务同时读
-
但不允许别人写(写时需等读完)
🔐 类型 2:排他锁(X锁)
-
用于写操作
-
拿到排他锁的人,谁也别想读写
-
确保写数据时不会被别人看到或修改
🔐 类型 3:Next-Key Lock(行锁 + 间隙锁)
-
是为了防止“幻读”(新插入的行突然出现)
-
锁住的不仅是已有数据行,还包括数据之间的空隙
-
默认启用在
REPEATABLE READ
隔离级别下
📌 举例说明:防止幻读
💡 场景:
两个事务并发执行:
-- 事务A
BEGIN;
SELECT * FROM orders WHERE amount > 100;
-- 事务B
INSERT INTO orders (id, amount) VALUES (101, 200);
COMMIT;
🔍 如果没有 Next-Key Lock:
-
事务A查到了 0 行;
-
事务B插入了一行 amount = 200;
-
事务A再次查,突然看到这行,就出现了“幻觉”!
🔐 有了 Next-Key Lock:
-
事务A在执行
SELECT
时,会锁住符合条件范围的记录和空隙; -
事务B想插入一行 amount = 200 的订单,就被挡住了,必须等事务A提交。
🧠 这样就实现了隔离性,避免事务B“闯进”事务A的查询区域。
🧠 小白术语解释
名词 | 含义 | 举例 |
---|---|---|
MVCC | 多版本并发控制,为每个事务提供数据快照,避免加锁 | 类似于每人一个账本副本 |
Undo Log | 回滚日志,用于还原或读取旧数据版本 | 数据改动前先存一份原件 |
版本号 | 标记记录创建和删除的事务号 | 谁修改、删除数据都有“时间戳” |
共享锁(S锁) | 多人可以读,不能写 | 多人查图书馆书,但不能改书 |
排他锁(X锁) | 一人能读写,别人不能碰 | 改作业时老师独占操作权限 |
Next-Key Lock | 锁定行和行之间的空隙,防止幻读 | 像电影院锁住你左右空座,防止别人突然后入 |
📋 小结
保障机制 | 描述 | 是否加锁? |
---|---|---|
✅ MVCC(多版本并发控制) | 事务之间各看各的数据快照 | ❌ 不加锁,提升读性能 |
✅ 锁机制(S/X/Next-Key) | 保证事务之间互不干扰,防止幻读等问题 | ✅ 根据操作加锁 |
🗣 总结一句话:MVCC 负责让事务读到稳定版本的数据,锁机制负责在写时隔离事务冲突。两者联手,InnoDB 实现了强大的事务隔离能力。
✅ 五、InnoDB 如何保证 持久性(Durability)
🔍 什么是“持久性”?
持久性指的是:
一旦事务提交,修改的数据就必须被永久保存,即使断电、宕机也不会丢失。
简单来说:
“你说保存了,那就必须真的保存下来,谁也不能改口!”
✔ 关键机制:Redo Log(重做日志)
InnoDB 使用 Redo Log(重做日志)来保证数据最终会写入磁盘,确保数据不会因为系统崩溃而丢失。
📦 Redo Log 是什么?
-
是一份物理日志,记录了哪些数据页发生了什么变化;
-
写入日志的速度比直接写入磁盘要快得多(因为磁盘慢);
-
一旦崩溃,就靠它来“重做”数据操作,让数据恢复到提交状态。
⚙️ 事务提交流程:
🧩 核心原则:WAL 策略(Write-Ahead Logging)
WAL 原则:
先写日志(Redo Log),再写磁盘(数据页)。
⏱ 步骤流程:
-
执行 SQL,如
UPDATE
。 -
把更改内容写入 Redo Log(写日志)。
-
Redo Log 写入成功后,事务才允许 COMMIT 提交。
-
后台慢慢把数据写入真实磁盘文件。
📌 举例说明:
BEGIN;
UPDATE users SET age = 25 WHERE id = 123;
COMMIT;
假设:
-
用户123的年龄从 24 改为 25;
-
COMMIT
后突然 断电 或系统崩溃。
会发生什么?
-
Redo Log 中已经记录了:用户123的 age 改为 25 的修改细节;
-
InnoDB 重启后自动扫描 Redo Log;
-
系统自动重做之前的更新,保证 age = 25 不会丢失!
🧠 小白术语解释:
名词 | 含义 | 小白理解 |
---|---|---|
Redo Log | 记录对数据的“物理更改”,用于崩溃恢复 | 提前写的“备份剧本” |
WAL 策略 | Write-Ahead Logging:先写日志,再写磁盘 | 类似写日记:先记下来,再处理 |
事务提交(COMMIT) | 表示事务完成,数据需要永久保存 | 就像点“保存按钮” |
磁盘刷盘(Flush) | 把数据从内存写入真实硬盘 | 真正落地,数据就稳了 |
✅ 六、总结表格(详细版)
✅ 事务特性 | InnoDB 实现机制 | 解释(通俗易懂) |
---|---|---|
原子性(Atomicity) | ✅ Undo Log(回滚日志) | 每个操作都记录旧值,一旦失败就能“撤回”,事务要么全成功、要么全失败 |
一致性(Consistency) | ✅ 原子性 + 隔离性 + 数据库约束(主键、外键、唯一索引等) | 确保数据始终合法,比如:不能插入重复主键、不能删除被引用的外键 |
隔离性(Isolation) | ✅ MVCC(快照读)、Next-Key Lock(防幻读)、行锁(S锁/X锁) | 每个事务有自己看的“快照”,还用锁机制防止别人插入或修改数据干扰自己 |
持久性(Durability) | ✅ Redo Log(重做日志)+ WAL 策略 | 提交事务前先写日志,哪怕系统崩溃,日志也能把数据恢复回来,不会丢失 |