MySQL事务
MySQL 事务是一组原子性的 SQL 操作(DML)集合,它将多个 SQL 操作绑定为一个不可拆分的逻辑执行单元,核心规则是:所有操作要么全部执行成功,要么全部失败回滚,是数据库保障数据一致性、并发操作安全性的核心机制。
MySQL 中只有 InnoDB 存储引擎原生支持完整的事务特性
事务的核心基石:ACID 四大特性
ACID 是事务的灵魂,四个特性相辅相成,最终目标是保障数据的一致性,每个特性都有明确的业务含义和底层实现支撑。
| 特性 | 核心定义 | 业务示例 | 底层实现保障 |
|---|---|---|---|
| 原子性(Atomicity) | 事务是最小不可分割的执行单元,不存在 “部分成功”,所有操作要么全提交生效,要么全失败回滚 | 转账场景中,“扣款” 和 “收款” 必须同时成功,绝不能出现扣了钱没到账的情况 | undo log(回滚日志):记录操作的反向 SQL,事务失败时通过 undo log 还原数据到初始状态 |
| 一致性(Consistency) | 事务执行前后,数据库的完整性约束(主键、外键、非空、业务规则)不被破坏,数据从一个合法状态转换为另一个合法状态 | 转账前后两个账户的总余额必须相等,账户余额不能为负数,不能凭空产生或消失金额 | 是事务的最终目标,由原子性、隔离性、持久性共同保障,同时需要业务代码配合约束 |
| 隔离性(Isolation) | 多个并发执行的事务之间相互隔离,一个事务的执行不能被其他事务干扰,避免并发操作导致的数据错乱 | A、B 同时给同一个账户转账,两个事务不能互相覆盖修改,最终余额必须是两次转账的累加结果 | 锁机制 + MVCC(多版本并发控制):不同隔离级别通过不同的锁策略和多版本机制,平衡并发性能与数据安全性 |
| 持久性(Durability) | 事务一旦提交成功,对数据的修改就是永久的,后续任何操作、系统故障(宕机、断电)都不会丢失已提交的数据 | 转账成功收到银行通知后,哪怕银行机房断电,转账记录和余额变化也不会消失 | redo log(重做日志):基于 WAL 预写日志机制,修改数据前先写日志,宕机后可通过 redo log 恢复已提交的数据 |
事务的基础语法
MySQL 默认开启 autocommit = 1,即每一条单独的 SQL 语句都会被当成一个独立事务,执行完成后自动提交,无法回滚。
若要手动控制事务,必须显式开启事务,或关闭自动提交 SET autocommit = 0;。
| 语句 | 核心作用 |
|---|---|
| BEGIN; / START TRANSACTION; | 显式开启一个新的手动事务 |
| COMMIT; | 提交事务,永久保存所有修改,释放占用的锁资源 |
| ROLLBACK; | 回滚事务,撤销所有未提交的修改,释放占用的锁资源 |
| SAVEPOINT 保存点名称; | 事务内设置保存点,支持部分回滚,不影响保存点之前的操作 |
| ROLLBACK TO SAVEPOINT 保存点名称; | 回滚到指定保存点,仅撤销保存点之后的操作 |
以下操作会触发 MySQL隐式提交事务(等同于自动执行 COMMIT),哪怕你未手动提交,也无法回滚之前的操作:
- 执行 DDL 语句:
CREATE/ALTER/DROP/TRUNCATE等表结构操作
- 执行管理类语句:
LOCK TABLES、UNLOCK TABLES、SET AUTOCOMMIT=1 - 事务嵌套时,开启新事务会隐式提交上一个未完成的事务
- 数据导入语句:
LOAD DATA INFILE
-- 开启事务BEGIN;-- 步骤1:张三账户扣款1000元UPDATE account SET balance = balance - 1000 WHERE id = 1;
SAVEPOINT sp1; -- 设置保存点
-- 步骤2:李四账户加款1000元UPDATE account SET balance = balance + 1000 WHERE id = 2;
-- 若步骤2执行异常,仅回滚步骤2,保留步骤1的操作ROLLBACK TO sp1;
-- 最终提交事务COMMIT;隔离级别
隔离级别是事务隔离性的具体落地,核心解决「并发性能」和「数据安全」的平衡问题:隔离级别越高,数据异常越少,并发性能越差。
在完全无隔离的情况下,多个事务同时操作同一份数据,会出现 3 种核心数据异常:
-
脏读:一个事务读取到了另一个事务
未提交的数据。 示例:事务 A 修改了账户余额但未提交,事务 B 读取到了这个未提交的数据,随后事务 A 回滚,事务 B 读到的就是无效的脏数据。 -
不可重复读:同一个事务内,多次读取同一条数据,由于其他提交事务所做的
修改,使得返回的结果不一致。 示例:事务 A 第一次读取账户余额为 1000 元,期间事务 B 修改了该余额并提交,事务 A 第二次读取时余额变为 2000 元,无法重复读取到一致的结果。 -
幻读:同一个事务内,用相同的条件多次范围查询,由于其他事务所做的
插入或删除操作,使得得到的行数不一致。 示例:事务 A 查询 id BETWEEN 1 AND 10 得到 5 条数据,期间事务 B 插入了一条 id=3 的数据并提交,事务 A 再次查询得到 6 条数据,如同出现了幻觉。
核心区分:不可重复读针对单条数据的修改,幻读针对新增 / 删除导致的行数变化。
综上,SQL 标准定义了 4 个隔离级别,从低到高如下,MySQL InnoDB默认隔离级别为可重复读(RR)。
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 核心特点与适用场景 |
|---|---|---|---|---|
| 读未提交(READ UNCOMMITTED) | ✅ | ✅ | ✅ | 最低隔离级别,几乎无隔离,仅保障持久性,生产环境严禁使用 |
| 读已提交(READ COMMITTED) | ❌ | ✅ | ✅ | 仅能读取已提交的数据,Oracle、PostgreSQL 默认级别,适合读多写少、对实时性要求高的场景 |
| 可重复读(REPEATABLE READ) | ❌ | ❌ | 标准 SQL 中可能,MySQL 中已解决 | MySQL 默认级别,同一事务内多次读取结果完全一致,通过 Next-Key Lock 临键锁解决了幻读问题,平衡了性能与安全性,适合绝大多数业务场景 |
| 串行化(SERIALIZABLE) | ❌ | ❌ | ❌ | 最高隔离级别,强制所有事务串行执行,完全消除并发冲突,但并发性能极差,仅适用于对数据一致性要求极高、并发量极低的金融核心场景 |
隔离级别常用操作:
-- 查看当前会话隔离级别(MySQL 8.0+)SELECT @@session.transaction_isolation;-- 查看全局隔离级别SELECT @@global.transaction_isolation;-- MySQL 5.7及之前版本用 tx_isolationSELECT @@tx_isolation;
-- 修改当前会话隔离级别为读已提交SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;-- 修改全局隔离级别(重启后失效,永久修改需修改my.cnf配置文件)SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;事务底层实现
MySQL 事务的原子性、持久性,核心靠 redo log 与 undo log 两个日志实现,两者相辅相成,缺一不可。
| 特性 | redo log(重做日志) | undo log(回滚日志) |
|---|---|---|
| 核心作用 | 保障事务持久性,实现崩溃恢复 | 保障事务原子性,支撑 MVCC 多版本控制 |
| 记录内容 | 物理级日志,记录「某个数据页做了什么修改」 | 逻辑级日志,记录「如何撤销本次修改的反向操作」 |
| 写入方式 | 固定大小循环写入,写满后会将脏数据刷入磁盘 | 追加式写入,事务提交后不会立即删除,用于 MVCC 快照读 |
| 生命周期 | 事务执行时写入,提交后落盘,崩溃恢复完成后失效 | 事务执行时写入,事务提交后,等对应快照读全部结束后,由 purge 线程清理 |
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!