在数据库操作中,我们经常需要执行一系列逻辑上紧密相关的操作。最经典的案例就是银行转账:
用户 A 转账 500 元给用户 B。
这个动作包含两个步骤:
A 的账户余额 减去 500。
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);模拟转账异常
-- 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:
定义:事务是不可分割的最小工作单元。
含义:事务中的所有操作要么同时成功,要么同时失败。不可能只执行了一半。
对应机制:由数据库的 Undo Log (回滚日志) 保证。如果失败,利用 Undo Log 回滚。
定义:事务执行前后,数据库必须保持一致性状态。
含义:数据的总量、约束规则(如外键、唯一性)不被破坏。
案例:转账前后,张三 + 李四的总金额必须保持 2000 元不变。
注意:一致性是最终目的,原子性、隔离性、持久性是实现一致性的手段。
定义:多个事务并发执行时,一个事务的执行不应被其他事务干扰。
含义:每个事务都感觉不到其他事务在并发进行,就像自己在独占数据库一样。
挑战:如果不设置隔离,会出现脏读、不可重复读、幻读等问题(详见下文)。
定义:一旦事务提交,其对数据的改变就是永久的。
含义:即使随后发生系统崩溃、断电,数据也不会丢失。
对应机制:由数据库的 Redo Log (重做日志) 保证。提交时先写日志,再写数据文件。
当多个用户同时操作数据库时,如果没有适当的隔离,会产生三种典型问题。MySQL 提供了四种隔离级别来解决这些问题。
MySQL 的 InnoDB 引擎支持以下四种级别,级别越高,数据越安全,但性能越低。
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 时,即使报错,前面的语句也已经生效的原因(针对单条语句而言)。
可以通过以下命令关闭自动提交,使后续所有操作都处于一个长事务中,直到手动 COMMIT 或 ROLLBACK:
-- 查看状态 (1=开启, 0=关闭)
SELECT @@autocommit;
-- 关闭自动提交
SET @@autocommit = 0;
-- 此时执行 SQL 不会立即生效,必须手动 COMMIT
UPDATE account SET money = 100 WHERE name = '张三';
-- 此时其他会话查不到变化
COMMIT;
-- 此时变化才永久生效警告:在生产环境中,不建议全局关闭
autocommit。应在代码逻辑中显式地使用START TRANSACTION...COMMIT块来管理业务事务,这样逻辑更清晰,风险更可控。
事务的作用:保证一组操作要么全成,要么全败,维护数据一致性。
ACID 特性:原子性、一致性、隔离性、持久性是事务的基石。
并发控制:
脏读:读未提交 -> 用 Read Committed 解决。
不可重复读:读已提交 -> 用 Repeatable Read 解决。
幻读:插入删除 -> MySQL Repeatable Read 已基本解决。
最佳实践:
默认使用 MySQL 的 Repeatable Read 隔离级别。
在业务代码中使用声明式事务(如 Spring @Transactional)。
尽量缩短事务持续时间,避免长事务导致锁竞争和性能下降。