【MySQL 数据库】事务

在数据库开发中,事务是保障数据安全的核心机制。无论是电商下单、银行转账还是火车票售票,一旦涉及多步数据操作,若不加控制,就会出现数据不一致、超卖、重复扣款等严重问题。本文将从实际问题出发,系统拆解 MySQL 事务的核心概念、ACID 特性、隔离级别、底层实现(MVCC)及实战操作,帮你彻底搞懂事务的底层逻辑与使用规范。

一、为什么需要事务?从一个超卖案例说起

先看经典的火车票售票系统超卖问题,这是无事务场景下的典型数据错乱案例。

1.1 场景模拟

有一张火车票表tickets,仅剩 1 张西安 <-> 兰州的车票:

表格

id name nums
10 西安 <-> 兰州 1

此时客户端 A客户端 B同时发起购票请求,伪代码逻辑如下:

java

运行

复制代码
// 客户端A
if (nums > 0) { // 检查有票
    卖票();
    update tickets set nums = nums - 1; // 更新票数
}

// 客户端B
if (nums > 0) { // 同时检查有票(此时A还没更新数据库)
    卖票();
    update tickets set nums = nums - 1; // 再次更新票数
}

1.2 问题结果

  • 客户端 A 检查到票数 = 1,准备卖票,但还没执行 update 更新数据库
  • 客户端 B 同时检查,发现票数仍 = 1,也执行卖票;
  • 最终 A、B 都执行update,票数从 1 变成 - 1,同一张票被卖了两次,出现超卖

1.3 解决方案:事务的四大核心诉求

要解决上述问题,买票操作必须满足 4 个核心属性,这正是事务的设计初衷:

  1. 原子性:买票的检查 + 更新操作,要么全部成功,要么全部失败,不能只执行一半;
  2. 一致性:买票前后,数据库票数始终合法(不能为负、不能超卖);
  3. 隔离性:多个客户端买票时,操作互相隔离,互不干扰;
  4. 持久性:买票成功后,即使服务器宕机,数据也不会丢失。

二、事务的基本概念与核心特性(ACID)

2.1 什么是事务?

事务是一组逻辑相关的 DML 语句集合 (增删改查),这些语句在逻辑上是一个整体,要么全部执行成功,要么全部执行失败,不存在 "部分成功、部分失败" 的中间状态。

简单说,事务就是 "要么全做,要么全不做" 的不可分割工作单元。例如:

  • 银行转账:A 扣钱、B 加钱,必须同时成功或同时失败;
  • 电商下单:扣库存、生成订单、扣余额,必须作为一个整体执行。

2.2 事务的四大核心特性(ACID)

事务必须满足原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability) 四大特性,简称 ACID,这是事务的核心准则。

1. 原子性(Atomicity):不可分割,要么全成要么全败
  • 定义 :一个事务中的所有操作,是一个不可分割的原子,要么全部执行成功,要么全部回滚到事务开始前的状态,不会停留在中间环节。
  • 底层保障 :通过Undo Log(回滚日志) 实现。事务执行前,会把数据修改前的状态记录到 Undo Log;若事务执行失败或崩溃,MySQL 会利用 Undo Log 将数据恢复到事务开始前的状态。
  • 案例:转账时,A 扣了 100 元但 B 没加钱,事务会回滚,A 的 100 元自动恢复,避免数据丢失。
2. 一致性(Consistency):数据合法,状态一致
  • 定义 :事务执行前后,数据库的完整性约束不被破坏,数据从一个合法状态转移到另一个合法状态,不会出现非法数据。
  • 核心要点
    • 数据必须符合预设规则(主键唯一、外键关联、非空、余额非负等);
    • 一致性由原子性、隔离性、持久性共同保障,同时依赖业务逻辑(如余额不能为负)。
  • 案例:火车票售票,无论卖多少次,票数不能为负;转账前后,A 和 B 的总余额始终不变。
3. 隔离性(Isolation):并发隔离,互不干扰
  • 定义 :多个事务并发执行时,互相隔离、互不干扰,一个事务的操作在未提交前,对其他事务不可见,避免并发导致的数据错乱。
  • 核心作用 :解决高并发下的脏读、不可重复读、幻读问题(后文详细讲解)。
  • 底层保障 :通过锁机制MVCC(多版本并发控制) 实现,不同隔离级别对应不同的锁策略。
4. 持久性(Durability):提交即永久,宕机不丢失
  • 定义 :事务一旦提交(COMMIT) ,对数据的修改就是永久生效的,即使服务器断电、宕机,数据也不会丢失。
  • 底层保障 :通过Redo Log(重做日志) 实现。事务提交时,先把修改记录写入 Redo Log,再异步刷到磁盘;若崩溃,重启后通过 Redo Log 恢复数据。

2.3 事务的引擎支持:仅 InnoDB 支持事务

MySQL 中,只有 InnoDB 引擎支持事务,MyISAM、MEMORY 等引擎不支持事务,这也是 InnoDB 成为 MySQL 默认引擎的核心原因。

查看数据库引擎及事务支持情况:

sql

复制代码
-- 查看所有引擎
show engines;

-- 行格式显示,重点看Transactions字段(YES=支持,NO=不支持)
show engines \G;

关键结果:

  • InnoDB:Transactions=YES(支持事务、行锁、外键);
  • MyISAM:Transactions=NO(不支持事务)。

三、事务的提交方式与基础操作

3.1 事务的两种提交方式

MySQL 事务默认自动提交,也可手动控制提交 / 回滚,两种方式:

1. 自动提交(默认)
  • 规则:每条 SQL 语句都是一个独立事务,执行后自动提交,无法回滚;
  • 查看状态

sql

复制代码
show variables like 'autocommit'; -- 默认ON(开启)
  • 关闭自动提交

sql

复制代码
set autocommit=0; -- OFF(关闭),需手动commit/rollback
2. 手动提交(显式事务)
  • 规则 :通过BEGIN/START TRANSACTION显式开启事务,执行 SQL 后,手动 COMMIT 提交(永久生效)或 ROLLBACK 回滚(撤销操作)
  • 核心命令

sql

复制代码
-- 1. 开启事务(二选一,推荐BEGIN)
BEGIN;
START TRANSACTION;

-- 2. 执行DML操作(增删改)
insert into account values (1, '张三', 100);
update account set blance=200 where id=1;

-- 3. 提交事务(永久生效,不可回滚)
COMMIT;

-- 4. 回滚事务(撤销所有未提交操作,恢复到事务开始前)
ROLLBACK;

3.2 事务保存点(SAVEPOINT):部分回滚

事务支持保存点,可在事务中设置多个保存点,回滚时可指定回滚到某个保存点,无需回滚整个事务。

示例:保存点的创建与回滚

sql

复制代码
-- 1. 开启事务
BEGIN;

-- 2. 创建保存点save1
SAVEPOINT save1;
insert into account values (1, '张三', 100);

-- 3. 创建保存点save2
SAVEPOINT save2;
insert into account values (2, '李四', 10000);

-- 4. 回滚到save2(仅撤销李四的插入,张三的数据保留)
ROLLBACK TO save2;

-- 5. 提交事务(最终仅张三的数据生效)
COMMIT;

3.3 事务操作核心结论

  1. 执行BEGIN/START TRANSACTION后,事务必须通过COMMIT提交才会持久化,与autocommit无关;
  2. 事务未提交时,客户端崩溃,MySQL 会自动回滚所有未提交操作;
  3. 事务提交后,客户端崩溃,数据不会丢失,已持久化到数据库;
  4. 单条 SQL 在autocommit=ON时,自动封装为独立事务,执行后永久生效。

四、事务隔离级别:平衡一致性与并发性能

4.1 并发事务的三大问题

多个事务并发执行时,若隔离性不足,会出现脏读、不可重复读、幻读三大问题,严重影响数据一致性。

1. 脏读(Dirty Read)
  • 定义 :一个事务读取到另一个事务未提交的修改数据,后续该事务回滚,导致读取的数据无效。
  • 案例
    • 事务 A:更新张三余额为 123,未提交;
    • 事务 B:读取张三余额 = 123(脏读);
    • 事务 A:回滚,张三余额恢复为 100;
    • 结果:事务 B 读取到无效数据。
2. 不可重复读(Non-Repeatable Read)
  • 定义同一个事务内,多次读取同一数据,结果不一致(因其他事务中途提交修改)。
  • 案例
    • 事务 B:第一次读取张三余额 = 123;
    • 事务 A:更新张三余额为 321,提交;
    • 事务 B:第二次读取张三余额 = 321;
    • 结果:同一事务内,两次读取结果不同。
3. 幻读(Phantom Read)
  • 定义同一个事务内 ,多次查询同一条件的数据,记录数不一致(因其他事务中途插入 / 删除数据)。
  • 案例
    • 事务 B:第一次查询 account 表,有 2 条记录;
    • 事务 A:插入一条新记录(王五),提交;
    • 事务 B:第二次查询 account 表,有 3 条记录;
    • 结果:如同 "幻觉",记录数凭空增加。

4.2 MySQL 的四大隔离级别

SQL 标准定义了 4 种隔离级别,隔离级别越高,一致性越强,并发性能越低 ;MySQL InnoDB 默认采用可重复读(REPEATABLE READ)

隔离级别对比表

表格

隔离级别 脏读 不可重复读 幻读 核心特点 适用场景
读未提交(READ UNCOMMITTED) ✅ 会发生 ✅ 会发生 ✅ 会发生 无隔离,性能最高,数据最不安全 几乎不用
读已提交(READ COMMITTED) ❌ 不会 ✅ 会发生 ✅ 会发生 仅读已提交数据,主流数据库默认(Oracle) 多数业务系统
可重复读(REPEATABLE READ) ❌ 不会 ❌ 不会 ❌ 不会(InnoDB) 同一事务多次读取结果一致,MySQL 默认 MySQL 默认场景
串行化(SERIALIZABLE) ❌ 不会 ❌ 不会 ❌ 不会 事务串行执行,完全隔离,性能最低 金融核心、数据强一致场景
1. 读未提交(READ UNCOMMITTED)
  • 规则 :所有事务可读取其他事务未提交的修改,无任何隔离;
  • 问题:脏读、不可重复读、幻读全部存在;
  • 性能:最高(几乎不加锁);
  • 使用 :生产环境绝对禁止,仅用于测试。
2. 读已提交(READ COMMITTED,RC)
  • 规则 :事务只能读取其他事务已提交的修改,看不到未提交数据;
  • 解决:脏读;
  • 残留:不可重复读、幻读;
  • 特点 :每次SELECT都会生成新快照(Read View),能看到最新提交数据。
3. 可重复读(REPEATABLE READ,RR,MySQL 默认)
  • 规则同一个事务内,多次读取同一数据,结果始终一致;
  • 解决:脏读、不可重复读;
  • 残留:幻读(InnoDB 通过 Next-Key 锁(间隙锁 + 行锁)解决幻读);
  • 特点 :事务内第一次SELECT生成快照(Read View),后续复用,保证可重复读。
4. 串行化(SERIALIZABLE)
  • 规则 :事务串行执行(排队执行),一个事务执行完,下一个才开始;
  • 解决:脏读、不可重复读、幻读全部解决;
  • 性能:最低(全表锁 / 行锁,并发完全阻塞);
  • 使用:仅用于金融核心、数据强一致场景,生产环境极少用。

4.3 隔离级别查看与设置

1. 查看隔离级别

sql

复制代码
-- 查看全局隔离级别
SELECT @@global.tx_isolation;

-- 查看当前会话隔离级别
SELECT @@session.tx_isolation;

-- 简写(默认同会话)
SELECT @@tx_isolation;
2. 设置隔离级别

sql

复制代码
-- 设置当前会话隔离级别(仅当前连接生效)
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 设置全局隔离级别(新连接生效,需重启客户端)
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;

五、隔离级别底层实现:MVCC(多版本并发控制)

5.1 什么是 MVCC?

MVCC(Multi-Version Concurrency Control,多版本并发控制)是 InnoDB 实现读已提交(RC)、可重复读(RR) 隔离级别的核心机制,核心目标是读写不阻塞、读不加锁、提升并发性能

简单说,MVCC 的核心是 **"数据多版本"**:数据修改时,不直接覆盖原数据,而是生成新版本,保留历史版本;读操作时,读取当前事务可见的历史版本,无需加锁。

5.2 MVCC 的三大核心组件

1. 隐藏字段(每行数据自带)

InnoDB 每行数据包含 3 个隐藏字段,用于版本管理:

  • DB_TRX_ID:6 字节,最近修改该记录的事务 ID(插入 / 更新时赋值);
  • DB_ROLL_PTR:7 字节,回滚指针 ,指向该记录的上一个版本(Undo Log 中的历史数据)
  • DB_ROW_ID:6 字节,隐式主键(无主键时自动生成)。
2. Undo Log(回滚日志)
  • 存储数据的历史版本 ,数据更新时,旧版本写入 Undo Log,通过DB_ROLL_PTR形成版本链
  • 作用:事务回滚时恢复数据、MVCC 读取历史版本。
3. Read View(读视图)
  • 事务快照读(普通 SELECT)时生成的可见性判断规则 ,记录当前系统活跃事务 ID 列表,用于判断当前事务能读取哪个版本的数据;
  • 核心字段:
    • m_ids:活跃事务 ID 列表;
    • m_up_limit_id:活跃事务最小 ID;
    • m_low_limit_id:下一个未分配事务 ID;
    • m_creator_trx_id:当前事务 ID。

5.3 快照读 vs 当前读

MVCC 中,读操作分两种,行为完全不同:

1. 快照读(普通 SELECT)
  • SQLSELECT * FROM account WHERE id=1;
  • 规则 :读取历史版本数据不加锁,依赖 MVCC;
  • 场景:RC、RR 隔离级别下的普通查询。
2. 当前读(加锁 / 修改操作)
  • SQLSELECT ... FOR UPDATEUPDATEDELETEINSERT
  • 规则 :读取最新版本数据加锁(行锁 / 间隙锁),防止并发修改;
  • 场景:数据更新、加锁查询。

5.4 RC vs RR:Read View 生成时机的差异

MVCC 中,RC 和 RR 隔离级别的核心区别是Read View 生成时机

  • RC(读已提交)每次快照读(SELECT)都生成新 Read View,能看到其他事务已提交的修改,因此存在不可重复读;
  • RR(可重复读)事务内第一次快照读生成 Read View,后续复用,看不到其他事务提交的修改,因此解决不可重复读。

六、总结:事务核心要点与实战建议

6.1 核心要点回顾

  1. 事务定义:一组 DML 语句的整体,要么全成要么全败,保障数据一致性;
  2. ACID 特性原子性(Undo Log)、一致性(业务 + ACID)、隔离性(锁 + MVCC)、持久性(Redo Log)
  3. 引擎支持 :仅InnoDB支持事务,MyISAM 不支持;
  4. 隔离级别 :MySQL 默认RR(可重复读),平衡一致性与并发;
  5. MVCC :InnoDB 实现 RC/RR 的核心,读写不阻塞、读不加锁,通过隐藏字段、Undo Log、Read View 实现。

6.2 实战使用建议

  1. 优先使用 InnoDB 引擎:所有需事务的表,引擎设为 InnoDB;
  2. 默认隔离级别用 RR:无需特殊场景,保持 MySQL 默认 RR,兼顾安全与性能;
  3. 短事务优先:事务内仅包含必要操作,避免长事务(易锁超时、死锁);
  4. 合理使用保存点:复杂事务中,用 SAVEPOINT 实现部分回滚;
  5. 避免脏写:更新数据时加索引条件,用行锁避免表锁,减少并发冲突。
相关推荐
云边有个稻草人1 小时前
金仓数据库KingbaseES:自动创建表空间目录,简化部署适配云原生
数据库·kingbasees·数据库运维·国产化数据库·云原生适配·表空间管理
坐吃山猪1 小时前
SqlLite数据库-思路拓展
数据库·sqlite
代码中介商1 小时前
从零掌握MySQL:安装配置与C语言连接实战
数据库·mysql
czlczl200209251 小时前
Mysql JOIN 的物理执行流程
数据库·mysql
Java面试题总结1 小时前
MySQL 反模式与排查宝典
数据库·mysql
STARFALL0011 小时前
MySQL 运维
运维·数据库·mysql
XD7429716361 小时前
科技早报晚报|2026年5月14日:数据库沙箱、文档解析与 GPU 共享,今天更值得做成产品的 3 个技术机会
数据库·科技·开源项目·开发者工具·ai基础设施
祀爱1 小时前
ASP.NET Core 集成NLog详细教程
数据库·后端·asp.net
醇氧1 小时前
CentOS 7 安装 MySQL 8.0.28 el7 (完美兼容 OpenSSL 1.1)
linux·mysql·centos