MySQL 数据库事务
一、事务基础
1. 什么是事务?
事务(Transaction)是一组 SQL 语句的逻辑执行单元,这组操作要么全部成功执行,要么全部失败回滚,不会出现部分执行、部分失败的中间状态。
经典场景:银行转账
- 张三给李四转 100 元,需要执行两个操作:
UPDATE bank_account SET balance = balance - 100 WHERE name = '张三';UPDATE bank_account SET balance = balance + 100 WHERE name = '李四';
- 这两个操作必须作为一个整体:要么都成功,要么都失败,否则会出现张三余额减少但李四余额未增加的异常。
2. 事务的核心目标
在事务执行过程中,必须保证以下四点(即事务的ACID 特性):
- 原子性 (Atomicity):操作要么全成,要么全败。
- 一致性 (Consistency):事务前后数据的完整性不被破坏。
- 隔离性 (Isolation):多个事务并发执行时互不干扰。
- 持久性 (Durability):事务提交后,数据修改永久保存到磁盘,即使数据库崩溃也不会丢失。
二、事务的 ACID 特性(面试核心)
1. 原子性 (Atomicity)
- 定义:一个事务中的所有操作,要么全部执行成功,要么全部执行失败,不会出现部分执行的情况。
- 实现机制:通过回滚 (Rollback)保证。如果事务执行过程中发生错误(如服务器宕机、SQL 异常),数据库会将数据恢复到事务开始前的状态,就像这个事务从未执行过一样。
- 关键操作:
ROLLBACK语句用于手动回滚事务。
2. 一致性 (Consistency)
- 定义:事务执行前后,数据库的完整性约束(如数据总和、业务规则)不会被破坏,数据始终符合预期。
- 例子:转账前张三 + 李四余额总和是 2000,转账后也必须是 2000(900 + 1100),不能变成 1900。
- 实现机制:
- 数据库层面:通过约束(非空、唯一、外键等)保证。
- 故障恢复层面:通过重做日志 (Redo Log)和回滚日志 (Undo Log)保证事务执行过程中服务器宕机后的数据一致性。
3. 隔离性 (Isolation)
- 定义:多个并发事务同时对数据进行读写时,彼此之间相互隔离,一个事务的执行不会影响其他事务。
- 核心问题:并发事务会带来数据不一致问题,需要通过隔离级别在安全性和性能之间做权衡。
- 实现机制:通过锁机制(行锁、表锁)和 MVCC(多版本并发控制)实现不同的隔离级别。
4. 持久性 (Durability)
- 定义:事务一旦提交(
COMMIT),对数据的修改就会永久保存到磁盘,即使数据库服务器崩溃、操作系统崩溃或断电,数据也不会丢失。 - 实现机制:
- 事务提交时,修改会先写入重做日志 (Redo Log)并持久化到磁盘。
- 即使数据库宕机重启,也会通过 Redo Log 恢复已提交的事务数据。
三、事务的基本操作
1. 查看支持事务的存储引擎
SHOW ENGINES;- InnoDB:MySQL 默认存储引擎,完整支持事务,是生产环境的首选。
- MyISAM、MEMORY 等引擎不支持事务,只适合只读场景。
2. 事务控制语法
2.1 开启事务
-- 方式一:标准语法 START TRANSACTION; -- 方式二:简写,与 START TRANSACTION 等价 BEGIN; -- 方式三:BEGIN WORK,与 BEGIN 等价 BEGIN WORK;开启事务后,MySQL 会自动关闭自动提交 (autocommit),后续的修改操作需要手动
COMMIT或ROLLBACK。
2.2 提交事务
-- 提交事务,将修改永久保存到磁盘 COMMIT;- 提交后,事务结束,数据修改对其他事务可见。
2.3 回滚事务
-- 回滚事务,撤销所有未提交的修改 ROLLBACK;- 回滚后,事务结束,数据恢复到事务开始前的状态。
3. 自动提交与手动提交
3.1 查看自动提交状态
SHOW VARIABLES LIKE 'autocommit';ON:默认值,自动提交事务,单条 DML(INSERT/UPDATE/DELETE)执行后会自动提交。OFF:手动提交,所有修改必须显式调用COMMIT才会持久化。
3.2 修改自动提交
-- 开启自动提交 SET AUTOCOMMIT = 1; -- 或 SET AUTOCOMMIT = ON; -- 关闭自动提交 SET AUTOCOMMIT = 0; -- 或 SET AUTOCOMMIT = OFF;⚠️ 注意:手动开启
START TRANSACTION或BEGIN时,会临时覆盖autocommit设置,必须手动COMMIT/ROLLBACK结束事务。
4. 保存点 (Savepoint)
保存点用于在事务内部设置还原点,可以将事务回滚到指定保存点,而不是回滚整个事务。
4.1 语法
-- 1. 开启事务 START TRANSACTION; -- 2. 执行 SQL 操作... -- 3. 设置保存点 SAVEPOINT savepoint_name; -- 4. 继续执行 SQL 操作... -- 5. 回滚到指定保存点 ROLLBACK TO savepoint_name; -- 6. 提交或回滚整个事务 COMMIT; -- 或 ROLLBACK;4.2 示例
START TRANSACTION; -- 操作1:张三余额减100 UPDATE bank_account SET balance = balance - 100 WHERE name = '张三'; SAVEPOINT sp1; -- 设置保存点sp1 -- 操作2:李四余额加100 UPDATE bank_account SET balance = balance + 100 WHERE name = '李四'; SAVEPOINT sp2; -- 设置保存点sp2 -- 操作3:新增王五账户 INSERT INTO bank_account VALUES (3, '王五', 5000); -- 回滚到sp1,撤销操作2和操作3,保留操作1 ROLLBACK TO sp1; COMMIT; -- 最终只保留张三余额减100的操作四、事务的隔离性与隔离级别
1. 并发事务带来的问题
多个事务并发执行时,会出现以下三种数据不一致问题:
表格
| 问题 | 定义 | 场景 |
|---|---|---|
| 脏读 (Dirty Read) | 一个事务读取到了另一个事务未提交的数据 | 事务 A 修改了数据但未提交,事务 B 就读到了这个修改值,若 A 回滚,B 读到的数据就是脏数据 |
| 不可重复读 (Non-repeatable Read) | 同一个事务内,两次相同条件查询得到的结果不一致 | 事务 A 第一次查询得到数据 X,事务 B 修改了 X 并提交,事务 A 再次查询得到了不同的数据 |
| 幻读 (Phantom Read) | 同一个事务内,两次范围查询得到的记录数不一致 | 事务 A 第一次查询得到 10 条记录,事务 B 插入了新记录并提交,事务 A 再次查询得到 11 条记录 |
2. 四种隔离级别
MySQL InnoDB 支持四种隔离级别,从低到高依次为:
表格
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 特点 |
|---|---|---|---|---|
| READ UNCOMMITTED (读未提交) | ❌ 存在 | ❌ 存在 | ❌ 存在 | 隔离性最低,性能最高,几乎不用 |
| READ COMMITTED (读已提交) | ✅ 解决 | ❌ 存在 | ❌ 存在 | Oracle、SQL Server 默认级别,避免脏读 |
| REPEATABLE READ (可重复读) | ✅ 解决 | ✅ 解决 | ⚠️ 部分解决 | MySQL InnoDB 默认级别,通过 MVCC 解决不可重复读,通过 Next-Key Lock 解决大部分幻读 |
| SERIALIZABLE (串行化) | ✅ 解决 | ✅ 解决 | ✅ 解决 | 隔离性最高,性能最差,完全串行执行,避免所有并发问题 |
3. 隔离级别详解
3.1 READ UNCOMMITTED(读未提交)
- 问题:允许事务读取其他事务未提交的数据,会出现脏读、不可重复读、幻读。
- 场景:几乎不使用,仅用于理论演示。
3.2 READ COMMITTED(读已提交)
- 解决:脏读,事务只能读取其他事务已提交的数据。
- 存在:不可重复读、幻读。
- 设置语句:
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED; SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
3.3 REPEATABLE READ(可重复读)
- 解决:脏读、不可重复读,同一个事务内多次查询结果一致。
- 存在:部分幻读,MySQL 通过Next-Key Lock(间隙锁 + 行锁)机制,在一定程度上避免了幻读。
- 设置语句:
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ; SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; - 核心实现:InnoDB 的 MVCC(多版本并发控制)为每个事务生成数据快照,保证事务期间读到的数据是一致的。
3.4 SERIALIZABLE(串行化)
- 解决:所有并发问题,强制事务串行执行,完全避免竞争。
- 缺点:性能极差,并发能力几乎为零,仅用于对数据一致性要求极高的场景(如金融核心系统)。
- 设置语句:
SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE; SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
4. 查看与设置隔离级别
4.1 查看当前隔离级别
-- 查看全局隔离级别 SELECT @@GLOBAL.transaction_isolation; -- 查看当前会话隔离级别 SELECT @@SESSION.transaction_isolation;4.2 设置隔离级别
-- 方式一:设置全局(所有新连接生效) SET GLOBAL TRANSACTION ISOLATION LEVEL [LEVEL]; -- 方式二:设置当前会话(仅当前连接生效) SET SESSION TRANSACTION ISOLATION LEVEL [LEVEL];
[LEVEL]可替换为:READ UNCOMMITTED/READ COMMITTED/REPEATABLE READ/SERIALIZABLE
五、事务的底层实现(补充)
1. MVCC(多版本并发控制)
- 作用:在REPEATABLE READ隔离级别下,实现读写不阻塞,提升并发性能。
- 原理:
- 为每行数据保存多个版本,事务读取时选择合适的版本(快照)。
- 通过
undo log保存历史版本,通过read view决定可见版本。
- 优势:读操作不加锁,写操作只加行锁,大幅提升并发读写性能。
2. 锁机制
- 行锁 (Row Lock):锁定单行数据,并发粒度最小,性能最高。
- 间隙锁 (Gap Lock):锁定索引间隙,防止其他事务在间隙中插入数据,避免幻读。
- Next-Key Lock:行锁 + 间隙锁,是 InnoDB 在REPEATABLE READ隔离级别下避免幻读的核心手段。
- 表锁 (Table Lock):锁定整张表,并发粒度最大,性能最差,仅用于 DDL 操作。
六、事务使用最佳实践
- 事务要短小:避免长事务(如事务执行时间超过几秒),长事务会占用锁资源,导致其他事务阻塞,甚至引发死锁。
- 避免在事务中执行无关操作:如网络请求、IO 操作,这些操作会延长事务执行时间,增加锁竞争。
- 选择合适的隔离级别:
- 普通业务:使用 MySQL 默认的REPEATABLE READ。
- 对一致性要求极高的金融场景:可考虑SERIALIZABLE。
- 读多写少、对一致性要求不高的场景:可使用READ COMMITTED提升性能。
- 显式控制事务边界:不要依赖
autocommit,在业务代码中显式使用BEGIN/COMMIT/ROLLBACK,保证事务逻辑清晰。 - 谨慎使用保存点:保存点会增加事务复杂度,仅在需要部分回滚的场景下使用。
- 处理事务异常:在代码中捕获异常,异常发生时必须
ROLLBACK,避免数据不一致。
七、事务面试高频考点
- 事务的 ACID 特性是什么?分别怎么实现?
- 并发事务会带来哪些问题?对应的隔离级别是什么?
- MySQL InnoDB 默认的隔离级别是什么?怎么解决幻读?
- 什么是 MVCC?它的作用是什么?
- 什么是脏读、不可重复读、幻读?分别怎么解决?
- 事务的提交和回滚有什么区别?
- 什么是自动提交?如何关闭自动提交?
