一、ACID 理论:事务的四大基石
事务(Transaction)是一组数据库操作的逻辑单元,这些操作要么全部成功执行,要么全部不执行。为了确保事务的可靠性,数据库系统遵循ACID原则:
- Atomicity(原子性):事务中的所有操作被视为一个不可分割的整体。如果其中任何一个操作失败,整个事务将被回滚,数据库状态保持不变。
- Consistency(一致性):事务执行前后,数据库必须从一个一致状态转移到另一个一致状态。例如,转账操作中,A账户减少100 元,B 账户必须增加 100元,总金额不变。
- Isolation(隔离性):多个并发事务之间互不干扰。即使多个事务同时执行,每个事务看到的数据状态应如同串行执行一样。
- Durability(持久性):一旦事务提交,其对数据库的修改就是永久性的,即使系统崩溃也不会丢失。
MySQL的 InnoDB 存储引擎完整支持 ACID特性,而 MyISAM 引擎则不支持事务。因此,讨论 MySQL 事务机制时,我们默认指 InnoDB。
二、MySQL 的锁机制
为了实现事务的隔离性,MySQL 使用锁机制来控制并发访问。InnoDB支持多种粒度的锁,主要包括:
1. 行级锁(Row-Level Locking)
InnoDB 默认使用行级锁,这意味着事务在修改某一行数据时,只会锁定该行,而不是整张表。这大大提高了并发性能。
行级锁又分为:
- 共享锁(S锁):允许多个事务同时读取同一行数据,但阻止其他事务对该行进行写操作。
- 排他锁(X锁):阻止其他任何事务读取或写入该行。
例如:
SELECT * FROMaccount WHERE id =1 LOCK IN SHARE MODE; -- 加 S锁
SELECT * FROMaccount WHERE id = 1 FOR UPDATE; -- 加 X 锁
2.表级锁(Table-Level Locking)
虽然 InnoDB 主要使用行锁,但在某些情况下(如 DDL操作、未使用索引的全表扫描等),会升级为表锁。此外,MyISAM 引擎只支持表级锁。
3.意向锁(Intention Locks)
为了提高加锁效率,InnoDB 引入了意向锁。意向锁是表级锁,用于表明事务打算在表中的某些行上加锁。
- 意向共享锁(IS):表示事务准备在某些行上加 S 锁。
- 意向排他锁(IX):表示事务准备在某些行上加 X 锁。
意向锁之间不会冲突,但与表级 S/X 锁存在兼容性规则。例如,IX 与表级 X 锁冲突,因为表级 X 锁要求独占整张表。
4. 间隙锁(Gap Lock)与临键锁(Next-Key Lock)
在可重复读(Repeatable Read)隔离级别下,InnoDB 使用临键锁来防止幻读(Phantom Read)。临键锁 = 行锁 + 间隙锁。
- 间隙锁:锁定索引记录之间的“间隙”,防止其他事务在该范围内插入新记录。
- 临键锁:锁定一个范围,包括记录本身及其前后的间隙。
例如,假设账户表中有 id 为 10 和 20 的记录。执行:
SELECT * FROM account WHERE id > 15 AND id < 25 FOR UPDATE;
InnoDB 会对 (15, 20] 和 (20, 25)的间隙加锁,防止其他事务插入 id = 18 或 id = 22 的记录,从而避免幻读。
三、事务隔离级别
SQL 标准定义了四种隔离级别,MySQL InnoDB 支持全部四种,并在可重复读级别下通过 MVCC 和临键锁解决了幻读问题。
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 实现机制 |
|---|---|---|---|---|
| 读未提交(Read Uncommitted) | ✅ | ✅ | ✅ | 无 MVCC,直接读最新数据 |
| 读已提交(Read Committed) | ❌ | ✅ | ✅ | 每次SELECT 生成新快照 |
| 可重复读(Repeatable Read) | ❌ | ❌ | ❌(InnoDB 特有) | 事务开始时生成快照 + 临键锁 |
| 串行化(Serializable) | ❌ | ❌ | ❌ | 所有 SELECT 自动加共享锁 |
注意:标准 SQL 中,可重复读不能防止幻读,但 InnoDB 通过临键锁扩展实现了幻读防护,这是其优于标准的地方。
查看与设置隔离级别
-- 查看当前会话隔离级别
SELECT @@transaction_isolation;
-- 设置会话隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
默认情况下,MySQL 5.7+的默认隔离级别是 REPEATABLE READ。
四、多版本并发控制(MVCC)
MVCC 是 InnoDB 实现高并发读写的关键技术。它允许读操作不阻塞写操作,写操作也不阻塞读操作,从而大幅提升并发性能。
1. MVCC 的核心组件
- 隐藏字段:每行记录包含两个隐藏字段:
DB_TRX_ID:最近一次修改该行的事务ID。DB_ROLL_PTR:指向 undo log的指针,用于构建历史版本。 -Read View(读视图):事务在首次执行 SELECT时创建,用于判断哪些版本的数据对该事务可见。 -Undo Log(回滚日志):存储数据的历史版本,用于事务回滚和MVCC 快照读。
2.可见性判断规则
当事务 T执行 SELECT 时,InnoDB 会根据 ReadView 判断某行是否可见:
- 如果行的
DB_TRX_ID<Read View 的最小活跃事务 ID(min_trx_id),说明该行在事务开始前已提交,可见。 - 如果行的
DB_TRX_ID>=Read View 的最大事务ID(max_trx_id),说明该行在事务开始后才创建,不可见。 - 如果
DB_TRX_ID在 [min_trx_id, max_trx_id) 范围内,则检查该事务ID 是否在活跃事务列表中: - 若在,说明未提交,不可见;
- 若不在,说明已提交,可见。
若当前版本不可见,则通过 DB_ROLL_PTR遍历 undolog,查找历史版本,直到找到可见版本或遍历完。
3. 快照读vs 当前读
- 快照读(Snapshot Read):普通的 SELECT 语句,使用MVCC 读取历史快照,不加锁。
SELECT * FROM account WHERE id = 1;
- 当前读(Current Read):读取最新数据,并加锁。包括:
SELECT ...LOCK IN SHARE MODE;
SELECT ...FOR UPDATE;
UPDATE/ DELETE / INSERT;
在可重复读隔离级别下,快照读在整个事务期间看到的是同一个快照,因此不会出现不可重复读;而当前读会获取最新数据并加锁,可能看到其他事务提交后的结果。
五、MySQL集群与分布式事务
随着业务规模扩大,单机MySQL 无法满足高可用与扩展性需求,集群架构成为常态。常见的 MySQL 集群方案包括主从复制、MGR(MySQL Group Replication)、InnoDB Cluster等。
1. 主从复制与事务一致性
在异步主从复制中,主库提交事务后立即返回客户端,从库异步拉取 binlog 并重放。这可能导致:
- 数据延迟:从库数据落后于主库。
- 读写分离下的不一致:应用从从库读取未同步的数据。
为缓解此问题,可采用:
- 半同步复制(Semi-Sync Replication):主库至少等待一个从库 ACK后才提交事务,提高一致性。
- 读写分离中间件:如 ShardingSphere、MyCat,可配置强制读主库。
2. 分布式事务:XA与两阶段提交(2PC)
当事务涉及多个数据库实例(如分库分表),需使用分布式事务协议。MySQL支持 XA 事务,基于两阶段提交:
- Prepare阶段:协调者询问所有参与者是否可以提交。
- Commit 阶段:若所有参与者同意,则提交;否则回滚。
示例:
XA START 'xid1';
UPDATE account SET balance = balance- 100 WHERE user_id = 1;
XA END 'xid1';
XA PREPARE 'xid1';
-- 在其他数据库执行类似操作
XA COMMIT 'xid1'; --或 XA ROLLBACK
但 XA 性能较差,且在 MySQL中存在诸多限制(如不支持 DDL、临时表等),生产环境较少使用。
3.最终一致性与柔性事务
现代分布式系统更倾向于最终一致性模型,采用柔性事务方案,如:
- TCC(Try-Confirm-Cancel):业务层面实现补偿机制。
- 消息队列事务:通过本地事务 + 消息表 + 幂等消费保证一致性。
- Saga 模式:长事务拆分为多个子事务,失败时依次补偿。
例如,在电商下单场景中:
- 扣减库存(本地事务)
- 发送“创建订单”消息到 MQ(与步骤1同事务)
- 订单服务消费消息创建订单(幂等处理)
这种方式牺牲了强一致性,但换取了高可用与高性能。
总结
MySQL 的事务机制是一个精妙的工程系统,它将理论(ACID、隔离级别)与实践(锁、MVCC、日志)紧密结合。理解这些机制不仅有助于写出正确的并发程序,还能在性能调优、故障排查中提供关键洞察。
在单机场景下,InnoDB 通过 MVCC 和临键锁实现了高性能与强一致性;在分布式场景下,虽然原生 XA 支持有限,但结合中间件与柔性事务模式,仍能构建可靠的分布式系统。
作为开发者,我们应:
- 根据业务需求选择合适的隔离级别;
- 避免长事务,减少锁竞争;
- 在分布式环境中权衡一致性与可用性;
- 善用 EXPLAIN 和 performance_schema分析锁等待与事务行为。
事务虽小,却是数据库世界的基石。掌握其原理,方能在复杂系统中游刃有余。