源本科技 | 码上会

MySQL 事务机制

2026/03/14
29
0

为什么需要事务

在数据库操作中,我们经常需要执行一系列逻辑上紧密相关的操作。最经典的案例就是银行转账

  • 用户 A 转账 500 元给用户 B。

  • 这个动作包含两个步骤:

    1. A 的账户余额 减去 500。

    2. B 的账户余额 加上 500。

如果没有事务保护,假设在第 1 步执行成功后,服务器突然断电或程序抛出异常,导致第 2 步未执行。结果就是:A 的钱少了,B 的钱没变,总金额凭空消失了 500 元。这将导致严重的数据不一致

事务(Transaction) 就是为了解决这个问题而生的。它是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。

  • 核心原则要么全部成功,要么全部失败

  • 目标:确保数据库从一个一致性状态变换到另一个一致性状态。


事务核心语法

在 MySQL 中,默认情况下每条 SQL 语句执行后都会自动提交(Auto-commit)。对于涉及多步操作的场景,我们需要手动控制事务

环境准备

DROP TABLE IF EXISTS account;

CREATE TABLE account (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(20) NOT NULL,
    money DECIMAL(10, 2) NOT NULL DEFAULT 0.00
);

-- 初始化数据:张三和李四各有 1000 元
INSERT INTO account (name, money) VALUES ('张三', 1000.00), ('李四', 1000.00);

事务控制命令

命令

语法

作用

开启事务

START TRANSACTION;BEGIN;

标记事务的开始。从此之后的 SQL 操作暂不写入磁盘,直到提交或回滚。

提交事务

COMMIT;

确认事务成功。将暂存的所有修改永久写入数据库。

回滚事务

ROLLBACK;

撤销事务。将所有修改撤销,恢复到开启事务之前的状态。

实战练习

模拟转账异常

不使用事务

-- 1. 李四扣钱
UPDATE account SET money = money - 500 WHERE name = '李四';
-- [模拟此处发生异常/断电]
-- 2. 张三加钱 (未执行)
UPDATE account SET money = money + 500 WHERE name = '张三';

-- 结果:李四剩 500,张三还是 1000。数据不一致!

使用事务

在命令行或客户端工具中执行:

-- 1. 开启事务
START TRANSACTION;

-- 2. 执行操作
UPDATE account SET money = money - 500 WHERE name = '李四';
-- [模拟此处发生异常]
UPDATE account SET money = money + 500 WHERE name = '张三'; -- 这行不会执行

-- 3. 决策
-- 如果一切正常,执行:
-- COMMIT; 

-- 如果发生异常,执行:
ROLLBACK; 

结果:执行 ROLLBACK 后,查询表数据,发现李四和张三的余额都恢复到了 1000 元。数据安全性得到保障

开发提示
在实际的 Java (Spring)、Python (Django/SQLAlchemy) 等后端开发中,我们通常不需要手动写 START/COMMIT/ROLLBACK。框架会通过 AOP (面向切面编程)注解(如 Spring 的 @Transactional)自动管理事务:代码无异常自动 COMMIT,捕获到异常自动 ROLLBACK


事务的四大特性

事务之所以可靠,是因为它具备以下四个核心特性,统称为 ACID

原子性 (Atomicity)

  • 定义:事务是不可分割的最小工作单元。

  • 含义:事务中的所有操作要么同时成功,要么同时失败。不可能只执行了一半。

  • 对应机制:由数据库的 Undo Log (回滚日志) 保证。如果失败,利用 Undo Log 回滚。

一致性 (Consistency)

  • 定义:事务执行前后,数据库必须保持一致性状态。

  • 含义:数据的总量、约束规则(如外键、唯一性)不被破坏。

  • 案例:转账前后,张三 + 李四的总金额必须保持 2000 元不变。

  • 注意:一致性是最终目的,原子性、隔离性、持久性是实现一致性的手段。

隔离性 (Isolation)

  • 定义:多个事务并发执行时,一个事务的执行不应被其他事务干扰。

  • 含义:每个事务都感觉不到其他事务在并发进行,就像自己在独占数据库一样。

  • 挑战:如果不设置隔离,会出现脏读不可重复读幻读等问题(详见下文)。

持久性 (Durability)

  • 定义:一旦事务提交,其对数据的改变就是永久的。

  • 含义:即使随后发生系统崩溃、断电,数据也不会丢失。

  • 对应机制:由数据库的 Redo Log (重做日志) 保证。提交时先写日志,再写数据文件。


并发事务与隔离级别

当多个用户同时操作数据库时,如果没有适当的隔离,会产生三种典型问题。MySQL 提供了四种隔离级别来解决这些问题。

三大并发问题

问题名称

描述

通俗案例

脏读
(Dirty Read)

读取了其他事务未提交的数据。

事务 A 修改了数据但没提交,事务 B 读到了。随后 A 回滚了,B 读到的就是“脏”数据。

不可重复读
(Non-repeatable Read)

在同一事务内,两次读取同一数据,结果不一致(因为其他事务修改并提交了该数据)。

事务 A 第一次查余额是 1000。事务 B 把余额改成 800 并提交。事务 A 第二次查变成 800 了。

幻读
(Phantom Read)

在同一事务内,两次查询返回的行数不一致(因为其他事务插入或删除了数据)。

事务 A 查所有工资 >5000 的员工有 10 人。事务 B 插入了一个工资 6000 的员工并提交。事务 A 再查变成 11 人了,像出现幻觉。

四种隔离级别

MySQL 的 InnoDB 引擎支持以下四种级别,级别越高,数据越安全,但性能越低。

隔离级别

脏读

不可重复读

幻读

说明

Read Uncommitted
(读未提交)

✅ 可能

✅ 可能

✅ 可能

最低级别,几乎不用。允许读未提交数据。

Read Committed
(读已提交)

❌ 避免

✅ 可能

✅ 可能

Oracle/SQL Server 默认。只能读到已提交的数据,解决脏读。

Repeatable Read
(可重复读)

❌ 避免

❌ 避免

⚠️ 理论上可能
(InnoDB 已解决)

MySQL 默认。保证同一事务内多次读取结果一致。

Serializable
(串行化)

❌ 避免

❌ 避免

❌ 避免

最高级别,强制事务排队执行。性能最差,极少使用。

MySQL 的特殊优化
虽然标准定义中 Repeatable Read 无法解决幻读,但 MySQL 的 InnoDB 存储引擎通过 MVCC (多版本并发控制)Next-Key Lock (间隙锁) 机制,在 Repeatable Read 级别下基本解决了幻读问题。因此,MySQL 默认使用 Repeatable Read 是一个兼顾性能与安全的选择。

设置隔离级别

-- 查看当前会话的隔离级别
SELECT @@transaction_isolation; 
-- 旧版本 MySQL 使用: SELECT @@tx_isolation;

-- 查看全局隔离级别
SELECT @@global.transaction_isolation;

-- 设置当前会话隔离级别 (仅对当前连接有效)
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 设置全局隔离级别 (需重启或新连接生效,需 SUPER 权限)
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;

关于自动提交

默认行为

MySQL 默认开启自动提交 (autocommit = 1)。

  • 这意味着每执行一条 INSERT, UPDATE, DELETE 语句,数据库会立即隐式地执行 COMMIT

  • 这也是为什么我们在不加 START TRANSACTION 时,即使报错,前面的语句也已经生效的原因(针对单条语句而言)。

关闭自动提交

可以通过以下命令关闭自动提交,使后续所有操作都处于一个长事务中,直到手动 COMMITROLLBACK

-- 查看状态 (1=开启, 0=关闭)
SELECT @@autocommit;

-- 关闭自动提交
SET @@autocommit = 0;

-- 此时执行 SQL 不会立即生效,必须手动 COMMIT
UPDATE account SET money = 100 WHERE name = '张三'; 
-- 此时其他会话查不到变化
COMMIT; 
-- 此时变化才永久生效

警告:在生产环境中,不建议全局关闭 autocommit。应在代码逻辑中显式地使用 START TRANSACTION ... COMMIT 块来管理业务事务,这样逻辑更清晰,风险更可控。


总结

  1. 事务的作用:保证一组操作要么全成,要么全败,维护数据一致性。

  2. ACID 特性:原子性、一致性、隔离性、持久性是事务的基石。

  3. 并发控制

    • 脏读:读未提交 -> 用 Read Committed 解决。

    • 不可重复读:读已提交 -> 用 Repeatable Read 解决。

    • 幻读:插入删除 -> MySQL Repeatable Read 已基本解决。

  4. 最佳实践

    • 默认使用 MySQL 的 Repeatable Read 隔离级别。

    • 在业务代码中使用声明式事务(如 Spring @Transactional)。

    • 尽量缩短事务持续时间,避免长事务导致锁竞争和性能下降。