什么是 MySQL 事务?¶
想象一下,你去银行给朋友转账:先从你的账户扣钱,再往朋友账户加钱。这两个步骤必须同时成功,否则钱就会莫名其妙消失或多出来。MySQL 事务就像这个场景:它是一组 SQL 操作的集合,要么全部执行成功(提交),要么全部失败(回滚),不会出现“一半成功一半失败”的中间状态。
事务的四大特性(ACID)¶
事务之所以可靠,是因为它满足 ACID 四个核心特性,每个特性都解决了不同的问题:
1. 原子性(Atomicity)¶
“原子”的意思是“不可分割”。事务中的所有操作要么全部完成,要么一个都不做。
例子:转账时,“扣钱”和“加钱”必须同时成功或失败。如果扣钱成功但加钱失败,系统会自动回滚,让你回到最初的状态(比如转账前的账户余额)。
2. 一致性(Consistency)¶
事务执行前后,数据必须符合业务规则。比如“总金额不变”:你转账 100 元,A 账户减 100,B 账户加 100,最终 A+B 的总金额和转账前一致。
关键点:一致性是事务的最终目标,由原子性和其他特性共同保证。
3. 隔离性(Isolation)¶
多个事务同时执行时,彼此之间不会互相干扰。比如你和朋友同时给同一个账户转账,你的操作不会影响朋友的操作,朋友的操作也不会让你看到“未完成的中间状态”。
注意:隔离性解决的是“并发问题”,不同的隔离级别会影响数据的可见性。
4. 持久性(Durability)¶
事务提交后,数据会永久保存,即使系统崩溃也不会丢失。比如转账成功后,就算数据库重启,A 和 B 账户的余额也不会变。
为什么需要事务?典型使用场景¶
事务在实际业务中无处不在,核心是保证数据的“准确性”和“完整性”:
1. 银行转账¶
- 场景:从 A 账户向 B 账户转 1000 元。
- 问题:如果只扣 A 账户不增 B 账户,A 会少钱,B 没收到钱;反之亦然。
- 解决方案:用事务包裹
UPDATE A 账户和UPDATE B 账户,成功则提交,失败则回滚。
2. 电商订单处理¶
- 场景:用户下单时,需要同时完成“创建订单”和“扣减库存”两个操作。
- 问题:如果只创建订单不扣库存,会导致“超卖”(比如 10 件商品被 100 个用户下单,库存显示 10 但实际卖了 100 件)。
- 解决方案:事务确保“下单+扣库存”要么同时成功,要么同时失败。
3. 支付系统¶
- 场景:用户支付成功后,需要更新订单状态、扣减用户余额、记录支付流水。
- 问题:如果其中一个步骤失败(比如支付成功但订单状态没更新),用户可能重复支付。
- 解决方案:事务保证所有相关操作“要么全做,要么全不做”。
MySQL 中如何使用事务?¶
MySQL 中只有 InnoDB 引擎支持事务,MyISAM 等引擎不支持。以下是基本操作步骤:
1. 显式开启事务¶
默认情况下,MySQL 可能开启了“自动提交”(每条 SQL 语句都是独立事务),需显式开启事务:
START TRANSACTION; -- 或 BEGIN;
2. 执行 SQL 操作¶
在事务中执行需要保证一致性的 SQL 语句,比如转账示例:
-- 假设 A 账户 ID=1,B 账户 ID=2,转账金额=100
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
3. 提交或回滚事务¶
- 提交(COMMIT):事务中所有操作成功完成后,用
COMMIT确认修改永久生效。 - 回滚(ROLLBACK):如果操作出错(比如余额不足),用
ROLLBACK撤销所有修改,恢复到事务开始前的状态。
示例:
START TRANSACTION;
-- 执行操作...
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- 检查是否有错误(比如余额是否足够)
IF 操作成功 THEN
COMMIT; -- 提交,修改永久生效
ELSE
ROLLBACK; -- 回滚,撤销所有修改
END IF;
事务隔离级别与常见问题¶
多个事务并发执行时,可能出现以下问题,需通过“隔离级别”解决:
1. 常见并发问题¶
- 脏读:一个事务读取到另一个事务未提交的数据(比如 A 事务还没提交扣钱操作,B 事务就看到了 A 扣了钱)。
- 不可重复读:同一事务内多次读取同一数据,结果不同(比如 A 事务第一次读余额是 1000,中间 B 事务扣了钱,A 再次读变成 900)。
- 幻读:同一事务内多次查询,结果集行数不同(比如 A 事务第一次查“订单总数”是 10 条,中间 B 事务新增了一条订单,A 再次查变成 11 条)。
2. MySQL 事务隔离级别¶
MySQL InnoDB 支持 4 种隔离级别,默认是 REPEATABLE READ(可重复读),可通过 SELECT @@tx_isolation; 查看当前级别:
- 读未提交(READ UNCOMMITTED):最低级别,可能出现脏读、不可重复读、幻读。
- 读已提交(READ COMMITTED):避免脏读,但可能有不可重复读、幻读(MySQL 中 SET TRANSACTION ISOLATION LEVEL READ COMMITTED;)。
- 可重复读(REPEATABLE READ):MySQL 默认级别,通过多版本控制(MVCC)避免不可重复读和幻读(但可能有部分幻读,具体看版本)。
- 串行化(SERIALIZABLE):最高级别,事务串行执行,完全避免并发问题,但性能最低。
注意事项¶
- 避免长事务:长事务会占用数据库资源,导致锁表,影响并发性能。尽量拆分操作或缩短事务时间。
- 慎用自动提交:默认情况下,MySQL 可能开启自动提交(
autocommit=1),需显式用START TRANSACTION控制事务范围。 - 隔离级别选择:根据业务对一致性和性能的需求选择,比如简单查询用读已提交,核心业务(如支付)用可重复读。
总结¶
事务是 MySQL 保证数据一致性的核心机制,通过 ACID 特性确保操作“要么全做,要么全不做”。理解事务的基本概念、使用场景和操作方法,能帮助你写出更可靠的 SQL 代码。在实际开发中,结合业务需求选择合适的隔离级别,避免并发问题,是保证系统稳定性的关键。