MySQL 事务

事务的概念

事务(transaction)是一个不可分割的操作序列,包含一系列相关的数据库操作指令,这些操作要么全部执行,要么全部不执行,以此保证数据操作的完整性与一致性。事务通常以 BEGIN TRANSACTION 启动,最终通过 COMMIT(提交事务,确认所有操作)或 ROLLBACK(回滚事务,撤销所有操作)结束。

事务的核心特性(ACID)

事务具备四大核心特性,简称 ACID,是保障数据可靠性的关键:

1. 原子性(Atomicity)

事务在逻辑上是不可分割的最小操作单元,其包含的所有语句要么全部执行成功,要么全部撤销回滚,不存在部分执行的情况。

示例:A 账户向 B 账户转账 100 元,包含两个关键操作——A 账户减少 100 元、B 账户增加 100 元。这两个操作必须同时成功或同时失败,若其中任一操作出错,整个转账流程需回滚,确保账户数据无误。

2. 一致性(Consistency)

事务的本质是将数据库数据从一种一致性状态转换为另一种一致性状态,事务执行前后,数据的总规则(如业务约束、数据完整性)保持不变。

示例:A 账户与 B 账户总余额为 5000 元,无论两者之间发生多少次转账,事务结束后,两个账户的余额总和仍需保持 5000 元,不会因转账操作出现总额增减。

3. 隔离性(Isolation)

隔离性针对并发事务设计,数据库服务器同时处理多个事务时,需通过隔离机制避免事务间相互干扰,防止数据不一致或错误。理想状态下,对于任意两个并发事务 T1、T2,在 T1 视角中,T2 要么在 T1 开始前已结束,要么在 T1 结束后才启动,每个事务都感觉不到其他事务的并发执行。数据库提供多种隔离级别,以平衡隔离效果与并发效率,具体见下文详述。

4. 持久性(Durability)

一旦事务提交成功(COMMIT),其对数据的修改将永久生效,不受系统故障影响。数据更新结果会从内存同步至磁盘等外部存储设备,即便后续发生服务器宕机、断电等故障,已提交事务的修改也不会丢失。

不考虑隔离性的并发问题

当多个事务并发执行且未采取隔离措施时,可能出现以下三类数据问题:

1. 脏读(Dirty Read)

一个事务读取了另一个事务已修改但尚未提交的数据。若修改数据的事务最终回滚,读取到的数据即为“脏数据”,导致后续操作基于错误数据执行。

示例:用户 A 向用户 B 转账 1000 元,对应两条 SQL 命令:

UPDATE account SET money = money + 1000 WHERE name = 'B';
UPDATE account SET money = money - 1000 WHERE name = 'A';

若第一条 SQL 执行成功,第二条未执行且事务未提交,此时另一并发事务查询 B 账户余额,会得到增加 1000 元后的结果。但该转账事务最终可能因第二条 SQL 失败而回滚,此前查询到的 B 账户余额即为脏数据。

2. 不可重复读(Non-repeatable Read)

在同一事务内,对同一行数据的多次查询结果不一致。原因是两次查询间隔期间,该数据被其他事务修改并提交。

示例:事务 T1 首次查询某用户余额为 2000 元;随后事务 T2 修改该用户余额为 2500 元并提交;T1 再次查询同一用户余额时,结果变为 2500 元,出现不可重复读。

3. 幻读(Phantom Read)

在同一事务内,同一查询语句多次执行返回的结果集行数不同。原因是其他事务在两次查询间隔期间执行了插入操作并提交,新增数据符合当前查询条件。

示例:事务 T1 执行 UPDATE account SET status = 2 WHERE type = 1,意图将所有 type=1 的账户状态改为 2;此时事务 T2 插入一条 type=1、status=1 的新账户并提交;T1 再次查询 type=1 的账户时,会发现仍有一条 status=1 的记录,如同“幻觉”,即幻读。

各类并发问题的核心区别

1. 脏读 vs 不可重复读

  • 脏读:读取的是其他事务未提交的修改数据,数据本身可能因回滚而失效;

  • 不可重复读:读取的是其他事务已提交的修改数据,问题核心是同一行数据的内容在事务内多次查询不一致。

2. 幻读 vs 不可重复读

  • 相同点:两者均读取了其他事务已提交的数据;

  • 不同点:不可重复读针对单条数据的内容修改(UPDATE/DELETE),幻读针对数据集合的行数变化(INSERT)。

五、事务的四种隔离级别

数据库通过隔离级别控制并发事务的相互影响,不同级别对应不同的隔离效果和并发性能,MySQL 支持以下四种标准隔离级别(从低到高排序):

隔离级别 英文名称 核心说明 可能出现的问题 适用场景
未提交读 Read Uncommitted 所有事务可查看其他未提交事务的修改结果,隔离级别最低 脏读、不可重复读、幻读 极少使用,仅适用于对数据一致性要求极低、追求极致并发的场景
提交读 Read Committed 事务仅能查看其他已提交事务的修改结果,Oracle、SQL Server 默认隔离级别 不可重复读、幻读 对数据一致性有基本要求,允许一定程度不可重复读的场景
可重复读 Repeatable Read 同一事务内多次读取同一数据,结果始终一致,MySQL 默认隔离级别 幻读(InnoDB 已解决) 大多数业务场景,兼顾一致性与并发效率
可串行化 Serializable 最高隔离级别,强制事务串行执行(按顺序排队),避免所有并发冲突 无(但会导致大量超时和锁竞争) 数据一致性要求极高、可接受无并发的场景(如财务对账)

关键补充

  • MySQL 的 InnoDB 和 Falcon 存储引擎通过 MVCC(多版本并发控制) 机制,解决了可重复读级别下的不可重复读问题;

  • 结合 间隙锁(Gap Lock),InnoDB 进一步解决了可重复读级别下的幻读问题,使 MySQL 默认隔离级别具备接近串行化的一致性。

MySQL 事务常见问题解析

1. 事务内部分 SQL 执行失败,COMMIT 后结果如何?

  • 结果:执行成功的 SQL 会保留修改,执行失败的 SQL 不会生效,且不会自动回滚整个事务;

  • 注意:MySQL 事务的回滚需手动触发(如通过 ROLLBACK 语句)。在 PHP 等开发语言中,通常通过 try-catch 捕获异常,在异常处理逻辑中执行回滚操作,确保事务原子性。

2. 事务执行中服务器突然宕机,MySQL 如何处理?

(1)事务执行的内部日志机制

MySQL 事务依赖两类核心日志保障数据安全:

  • undo.log(回滚日志):事务启动后,每执行一条修改类 SQL(INSERT/UPDATE/DELETE),MySQL 会记录一条反向 SQL(如 INSERT 对应 DELETE、UPDATE 对应反向 UPDATE)到 undo.log;

  • redo.log(重做日志):记录事务对数据的修改操作,用于故障恢复时重做已提交的修改。

(2)故障恢复流程

  1. 事务执行过程中,服务器宕机时,undo.log 中会记录未完成的事务操作(无 checkpoint 标记);

  2. MySQL 重启后,会检查 undo.log:

    • 若 checkpoint 之后存在未提交的操作记录,说明事务未完成,MySQL 执行 undo.log 中的反向 SQL,回滚未提交的修改;
  3. 同时,MySQL 会执行 redo.log 中的操作,重做已提交但未同步到磁盘的数据修改,确保已提交事务的持久性。