前言
在上一篇关于 InnoDB 的文章中,我们提到 InnoDB 相比 MyISAM 最大的优势之一就是支持事务(Transaction)。
初学者往往觉得“事务”这个词很高大上,但其实它解决的问题非常接地气。
试想一个场景:A 向 B 转账 100 元。 在数据库层面,这其实是两步操作:
- A 的账户余额减去 100 元。
- B 的账户余额加上 100 元。
如果在第1步执行完后,服务器突然断电了,或者程序报错了,第2步没执行。结果就是:A 的钱少了,B 的钱也没收到,这就出大事了。
为了解决这个问题,数据库引入了事务的概念。简单来说,事务就是**“一组操作,要么全部成功,要么全部失败,决不允许只做一半”**。
为了衡量一个数据库是否支持事务,计算机科学家提出了著名的 ACID 四大特性。
1. Atomicity(原子性)—— 同生共死
- 定义: 事务包含的所有操作,要么全部执行成功,要么全部失败回滚(Rollback)。
- 通俗解释: 就像原子是不可分割的一样,事务里的操作也是一个整体。
- 案例: 转账过程中,A 扣款成功,但在 B 加钱之前系统崩了。数据库会监测到这个事务没有完成,于是自动把 A 扣掉的钱退回去(回滚),就像这件事从来没发生过一样。
- 底层原理: 靠 Undo Log(回滚日志)实现。每做一步操作,数据库都记了个小本本,一旦失败,就反向操作把数据改回去。
代码示例(Spring):
Java
@Transactional // 开启事务 public void transfer(int fromId, int toId, double amount) { // 1. A 扣款 userDao.decrementBalance(fromId, amount); // 模拟一个异常(比如除以0,或者断电) int i = 1 / 0; // 2. B 加钱(由于上面报错,这行代码不会执行,且第1步会自动回滚) userDao.incrementBalance(toId, amount); }
2. Consistency(一致性)—— 守规矩
- 定义: 事务执行前后,数据库的完整性约束没有被破坏。
- 通俗解释: 能量守恒定律。
- 案例:
- A 有 500 元,B 有 0 元。
- 无论怎么转账,只要钱没转出银行系统,A 和 B 的余额总和永远应该是 500 元。
- 如果转账完,A 剩 400,B 变成了 200,总和变 600 了,那就是破坏了一致性。
- 另外,如果数据库规定余额不能为负数,那么 A 余额只有 100 却要转 200,事务必须失败,这也是一致性。
3. Isolation(隔离性)—— 各玩各的
- 定义: 多个事务并发执行时,不应互相干扰。
- 通俗解释: 你在ATM机上查余额,你老婆正好在用支付宝刷你的卡消费。这两个操作同时进行,应该互相隔离,不能让你看到的数据乱套。
- 并发带来的问题:
- 脏读: 你读到了别人还没提交的数据(万一他回滚了,你读的就是假数据)。
- 不可重复读: 一个事务内两次读到的数据不一样。
- 幻读: 一个事务内读到的行数不一样。
- 解决办法: 数据库提供了 4 种隔离级别(Read Uncommitted, Read Committed, Repeatable Read, Serializable)来平衡性能与隔离性。MySQL InnoDB 默认使用的是 Repeatable Read(可重复读)。
4. Durability(持久性)—— 落袋为安
- 定义: 事务一旦提交(Commit),它对数据的修改就是永久的。
- 通俗解释: 只要你看到了“交易成功”四个字,哪怕下一秒机房爆炸、服务器被雷劈了,你的钱也确确实实转过去了,数据绝不会丢失。
- 底层原理: 靠 Redo Log(重做日志)实现。上一篇文章提到过,数据修改会先写日志。只要日志在磁盘上,重启后数据库就能根据日志重新构建数据。
总结
面试时如果你能用这个逻辑讲出来,稳过:
- 原子性 (A):要么全做,要么全不做(靠 Undo Log)。
- 持久性 (D):由于断电等原因,已提交的数据不能丢(靠 Redo Log)。
- 隔离性 (I):并发事务之间互不干扰(靠 锁 和 MVCC)。
- 一致性 (C):这是最终目的。原子性、隔离性、持久性都是为了保证数据的一致性。