当前位置: 首页 > news >正文

MySQL 事务

       在数据库开发中,事务是处理 “多步操作原子性” 和 “并发数据一致性” 的关键。无论是电商订单创建、银行转账,还是日常的用户数据修改,都需要依赖事务避免 “操作一半失败” 或 “并发冲突” 导致的数据错乱。作为最主流的关系型数据库,MySQL 的 InnoDB 存储引擎对事务提供了完善支持。

一、什么是 MySQL 事务?—— 用场景理解 “原子性” 核心

事务(Transaction)是数据库中一组不可分割的操作单元,这组操作要么 “全部执行成功并持久化”,要么 “全部执行失败并回滚”,不存在 “部分生效” 的中间状态。

1. 生活中的事务案例:为什么需要事务?

最经典的例子是 “银行转账”:用户 A 向用户 B 转账 100 元,操作拆解为两步:

  1. 从 A 的账户余额中扣除 100 元(UPDATE account SET balance = balance - 100 WHERE user_id = 'A');
  2. 向 B 的账户余额中增加 100 元(UPDATE account SET balance = balance + 100 WHERE user_id = 'B')。

如果没有事务:

  • 若第一步执行成功,但第二步因网络中断、数据库崩溃等原因失败,会导致 A 的钱被扣但 B 未收到,数据出现 “不一致”;
  • 若第二步先成功、第一步失败,会导致 B 多收钱,同样错乱。

事务的作用就是将这两步 “绑定” 为一个整体,确保要么全成、要么全败,从根本上避免数据不一致。

2. MySQL 事务的适用范围

需明确:MySQL 中只有 InnoDB 存储引擎支持事务,MyISAM、Memory 等引擎不支持事务(这也是 InnoDB 成为主流存储引擎的核心原因)。因此,在创建表时需指定ENGINE=InnoDB,例如:

CREATE TABLE account (user_id VARCHAR(20) PRIMARY KEY,balance DECIMAL(10,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

二、MySQL 事务执行

MySQL 事务的操作通过 SQL 命令实现,核心包括 “开启事务”“提交事务”“回滚事务”,同时支持 “保存点” 实现部分回滚。

1. 三大基础命令:开启、提交、回滚

事务的生命周期围绕 “开启→执行操作→提交 / 回滚” 展开,核心命令如下:

命令作用关键说明
BEGIN / START TRANSACTION开启事务两种命令等价,执行后后续所有 SQL 操作将纳入事务管理,不会自动提交
COMMIT提交事务将事务中所有 SQL 的修改永久写入数据库,事务结束后无法回滚
ROLLBACK回滚事务撤销事务中所有 SQL 的修改,恢复到事务开始前的状态,事务结束
实战示例:转账事务

以 “用户 A 向 B 转账 100 元” 为例,完整事务流程如下:

-- 1. 开启事务(二选一)
BEGIN;
-- 或 START TRANSACTION;-- 2. 执行核心操作(两步必须同时成功/失败)
-- 步骤1:A扣减100元
UPDATE account SET balance = balance - 100 WHERE user_id = 'A';
-- 步骤2:B增加100元
UPDATE account SET balance = balance + 100 WHERE user_id = 'B';-- 3. 提交或回滚(根据操作结果判断)
-- 若两步均无错误,提交事务(数据永久生效)
COMMIT;
-- 若任一操作失败(如A余额不足),回滚事务(数据恢复初始状态)
-- ROLLBACK;

2. 进阶命令:保存点(SAVEPOINT)

当事务包含多个操作时,若仅需撤销 “部分操作”(而非全量回滚),可通过 “保存点” 实现:

  • SAVEPOINT [保存点名称]:在事务中设置 “中间节点”;
  • ROLLBACK TO [保存点名称]:回滚到指定保存点,保存点之前的操作仍保留在事务中。
示例:部分回滚

假设 “创建订单” 和 “扣减库存” 两步操作,若库存不足,仅回滚 “扣减库存”,保留 “订单记录” 并标记为 “库存不足”:

BEGIN;
-- 操作1:创建订单(成功)
INSERT INTO `order` (order_id, user_id, amount) 
VALUES ('20240601001', 'A', 199.9);-- 创建保存点:标记“订单创建后”的节点
SAVEPOINT after_order_create;-- 操作2:扣减库存(失败,如库存为0)
UPDATE product SET stock = stock - 1 WHERE product_id = 'P001';-- 回滚到保存点:仅撤销“扣减库存”操作,订单记录保留
ROLLBACK TO after_order_create;-- 补充操作:标记订单为“库存不足”
UPDATE `order` SET status = 'stock_insufficient' WHERE order_id = '20240601001';-- 提交事务:最终订单记录生效,库存未变
COMMIT;

三、事务的四大特性(ACID):数据一致性的基石

所有支持事务的数据库都需遵循 ACID 特性,这是事务的核心标准。MySQL InnoDB 引擎完全实现了 ACID,确保数据在各种场景下的一致性。

1. 原子性(Atomicity):要么全成,要么全败

  • 定义:事务中的所有操作是一个不可分割的整体,不存在 “部分执行”。若任一操作失败,整个事务的所有修改都会被撤销;只有所有操作成功,事务才会提交。
  • MySQL 实现原理:依赖undo log(回滚日志)
    事务执行时,InnoDB 会记录每个操作的 “反向逻辑”(如INSERT对应DELETEUPDATE对应 “恢复原数据的UPDATE”)。当需要回滚时,InnoDB 通过 undo log 反向执行这些操作,将数据恢复到事务开始前的状态。
    例如:执行UPDATE account SET balance = 900 WHERE user_id = 'A'时,undo log 会记录 “若回滚,将 A 的 balance 改回 1000”。

2. 一致性(Consistency):事务前后数据合法

  • 定义:事务执行前后,数据库的 “完整性约束”(如主键唯一、外键关联、字段非空、自定义检查约束)不被破坏,数据始终处于 “合法状态”。
  • MySQL 实现原理:一致性是事务的 “最终目标”,由原子性、隔离性、持久性共同保障,同时依赖数据库约束和应用层逻辑。
    例如:转账前 A 余额 1000、B 余额 500,总余额 1500;事务执行后,无论成功(A900、B600)或失败(恢复原余额),总余额始终为 1500,不会出现 “总余额 1400” 或 “1600” 的非法状态。

3. 隔离性(Isolation):并发事务互不干扰

  • 定义:多个事务同时执行时,一个事务的操作不会被其他事务 “干扰”,每个事务都感觉自己在 “独占数据库”。
  • MySQL 实现原理:通过锁机制MVCC(多版本并发控制) 实现:
    • 锁机制:防止并发修改同一数据(如写操作加 “排他锁”,其他事务无法同时修改);
    • MVCC:允许 “读 - 写并发”(读操作不加锁,写操作不阻塞读),通过 “数据多版本” 实现非阻塞读。

4. 持久性(Durability):提交后数据永久保存

  • 定义:事务一旦提交(COMMIT),其对数据的修改会 “永久保存” 到数据库中,即使后续数据库崩溃(如断电、宕机),数据也不会丢失。
  • MySQL 实现原理:依赖redo log(重做日志)
    InnoDB 采用 “WAL(Write-Ahead Logging)” 机制:事务执行时,先将数据修改写入redo log(内存缓冲区),事务提交时将redo log刷盘(写入磁盘文件);后台线程再定期将内存中修改的数据页刷盘到数据文件(.ibd 文件)。
    即使数据库在 “数据页刷盘前” 崩溃,重启后 InnoDB 可通过redo log恢复已提交的事务数据,确保持久性。
    关键配置:innodb_flush_log_at_trx_commit = 1(默认值),表示 “事务提交时立即将 redo log 刷盘”,确保持久性(若设为 0 或 2,会牺牲部分持久性换取性能)。

四、MySQL 事务的底层实现:三大核心组件

InnoDB 通过 “redo log、undo log、锁 + MVCC” 三大组件,支撑事务的 ACID 特性。理解这些组件,是掌握事务原理的关键。

1. redo log(重做日志):保障持久性

(1)为什么需要 redo log?

InnoDB 的数据最终存储在数据文件(.ibd)中,但数据文件的 IO 是 “随机写”(修改数据需先定位到对应数据页),速度较慢。若每次事务提交都直接修改数据文件,高并发场景下性能会严重下降。

redo log 是 “顺序写” 的物理日志(记录 “某个数据页修改了什么”,如 “表 account 的页 100 中,偏移量 500 的位置值从 1000 改为 900”),顺序写比随机写快得多。通过 “先写 redo log,再写数据文件” 的 WAL 机制,InnoDB 平衡了 “性能” 与 “持久性”。

(2)redo log 的工作流程
  1. 事务执行UPDATE/INSERT时,先修改内存中的数据页(Buffer Pool 中的页);
  2. 同时将修改内容写入redo log buffer(内存中的 redo log 缓冲区);
  3. 事务提交时,将redo log buffer中的日志 “刷盘”(写入磁盘上的 redo log 文件);
  4. 后台线程定期将 Buffer Pool 中修改的数据页刷盘到.ibd 文件(称为 “checkpoint”)。

即使数据库在 “数据页刷盘前” 崩溃,重启后 InnoDB 可通过 redo log 恢复已提交的修改(redo log 已刷盘,数据不会丢失)。

2. undo log(回滚日志):保障原子性 + 支撑 MVCC

(1)undo log 的核心作用
  • 回滚事务:记录操作的反向逻辑,事务回滚时通过 undo log 撤销修改(保障原子性);
  • 支撑 MVCC:存储数据的 “历史版本”,读操作可读取历史版本(避免读阻塞写),这是 InnoDB 实现 “非阻塞读” 的关键。
(2)undo log 的工作流程
  1. 事务执行INSERT时,undo log 记录 “DELETE该条记录” 的反向操作;
  2. 事务执行UPDATE时,undo log 记录 “将字段恢复为修改前的值” 的反向操作;
  3. 事务回滚时,InnoDB 遍历 undo log,执行反向操作,恢复数据;
  4. 事务提交后,undo log 不会立即删除,而是标记为 “可回收”,后台线程在 “无事务依赖” 时清理(避免影响 MVCC 的读操作)。

3. 锁机制 + MVCC:保障隔离性

(1)锁机制:解决并发写冲突

InnoDB 支持 “行锁” 和 “表锁”,核心用于防止 “多个事务同时修改同一数据”:

  • 行锁:锁定单行数据(如UPDATE account SET balance=900 WHERE user_id='A',仅锁定 user_id='A' 的行),粒度细,并发度高;
  • 表锁:锁定整个表(如LOCK TABLES account WRITE),粒度粗,并发度低(InnoDB 中仅全表扫描的UPDATE/DELETE会触发);
  • 共享锁(S 锁):用于读操作(如SELECT ... LOCK IN SHARE MODE),多个事务可同时加 S 锁(读 - 读不阻塞);
  • 排他锁(X 锁):用于写操作(INSERT/UPDATE/DELETE),加 X 锁后其他事务无法加 S 锁或 X 锁(写 - 读、写 - 写阻塞)。
(2)MVCC(多版本并发控制):实现 “读不加锁,写不阻塞读”

锁机制能解决并发写冲突,但会导致 “读阻塞写”(如事务 A 加 X 锁修改数据时,事务 B 读该数据会被阻塞)。MVCC 通过 “为每行数据保存多个版本”,实现 “非阻塞读”:

  • 核心组件
    1. 隐藏列:InnoDB 每行数据包含DB_TRX_ID(最后修改该数据的事务 ID)、DB_ROLL_PTR(指向 undo log 中该数据的历史版本);
    2. undo log 版本链:通过DB_ROLL_PTR将数据的历史版本串联成链;
    3. Read View(读视图):事务启动时生成的 “可见性规则”,判断数据的历史版本是否对当前事务可见(如 “只可见事务 ID 小于当前 Read View 的版本”)。
  • 效果:读操作无需加锁,直接读取数据的 “历史版本”;写操作加 X 锁,但不阻塞读操作,极大提升并发性能。

五、并发事务出现的问题

1. 脏读(Dirty Read):读取未提交的数据

  • 定义:事务 A 读取了事务 B “尚未提交” 的修改数据,若事务 B 后续回滚,事务 A 读取到的数据就是 “无效的脏数据”。
  • 示例
    1. 事务 B 执行UPDATE account SET balance=1100 WHERE user_id='A'(未提交);
    2. 事务 A 执行SELECT balance FROM account WHERE user_id='A',读取到 1100;
    3. 事务 B 因错误执行ROLLBACK,A 的余额恢复为 1000;
    4. 事务 A 基于 “1100” 进行后续操作(如转账),导致数据逻辑错误。

2. 不可重复读(Non-Repeatable Read):同一事务内多次读结果不一致

  • 定义:事务 A 在同一事务内,多次读取同一数据,若事务 B 在两次读取之间 “修改并提交” 了该数据,事务 A 会发现 “两次读取结果不一样”。
  • 示例
    1. 事务 A 第一次读:SELECT balance FROM account WHERE user_id='A' → 1000;
    2. 事务 B 执行UPDATE account SET balance=1100 WHERE user_id='A'COMMIT
    3. 事务 A 同一事务内第二次读:SELECT balance FROM account WHERE user_id='A' → 1100,结果不一致。
  • 与脏读的区别:脏读读取 “未提交的数据”,不可重复读读取 “已提交的数据”,但同一事务内结果不同。

3. 幻读(Phantom Read):同一事务内多次范围查询行数不一致

  • 定义:事务 A 在同一事务内,多次执行 “范围查询”,若事务 B 在两次查询之间 “插入 / 删除” 了符合范围条件的数据,事务 A 会发现 “两次查询的行数不一样”(像出现了 “幻觉”)。
  • 示例
    1. 事务 A 第一次查:SELECT COUNT(*) FROM account WHERE balance > 1000 → 2 条;
    2. 事务 B 执行INSERT INTO account (user_id, balance) VALUES ('C', 1200)COMMIT
    3. 事务 A 同一事务内第二次查:SELECT COUNT(*) FROM account WHERE balance > 1000 → 3 条,行数不一致。
  • 与不可重复读的区别:不可重复读是 “单行数据的修改”,幻读是 “范围数据的插入 / 删除”,导致行数变化。

六、MySQL 事务的隔离级别

为解决并发事务的问题,MySQL 定义了四种隔离级别,不同级别对并发问题的解决程度不同,同时影响并发性能。开发者需根据业务需求选择合适的级别。

1. 四种隔离级别的定义与效果

MySQL 的隔离级别从低到高分为:读未提交(Read Uncommitted)读已提交(Read Committed)可重复读(Repeatable Read)串行化(Serializable)。其中,可重复读(Repeatable Read)是 MySQL 的默认隔离级别(区别于 SQL 标准的 “读已提交”)。

四种级别的对比如下(“√” 表示允许该问题,“×” 表示禁止):

隔离级别脏读不可重复读幻读丢失修改并发性能适用场景
读未提交(RU)最高极少使用(如对一致性无要求的临时统计)
读已提交(RC)×较高大多数互联网场景(如商品详情、用户信息查询)
可重复读(RR)×××(InnoDB 优化)×中等核心业务(如订单、支付、库存)
串行化(S)××××最低极高一致性场景(如银行转账、财务对账)

关键说明:SQL 标准中 “可重复读” 不禁止幻读,但 InnoDB 通过 “间隙锁(Gap Lock)” 和 “临键锁(Next-Key Lock)” 优化,在 RR 级别下也禁止了幻读,这是 MySQL 的特色实现。

2. 隔离级别的实现原理

不同隔离级别的核心区别在于 “Read View 的生成时机” 和 “锁的使用策略”:

  • 读未提交(RU):不生成 Read View,直接读取数据的 “当前版本”,因此能看到未提交的数据(脏读);
  • 读已提交(RC):每次执行SELECT时生成 Read View,只能看到 “已提交的版本”(禁止脏读),但同一事务内多次SELECT生成不同 Read View,可能看到不同结果(允许不可重复读);
  • 可重复读(RR):事务启动时生成 Read View,同一事务内所有SELECT共用同一个 Read View,因此多次读结果一致(禁止不可重复读);同时通过间隙锁防止范围插入 / 删除(禁止幻读);
  • 串行化(S):放弃 MVCC,直接对所有操作加表锁或行锁,事务 “串行执行”(禁止所有并发问题),但性能极低。

七、总结

MySQL 事务是保障数据一致性的核心机制,其设计围绕 ACID 特性展开,通过 redo log(持久性)、undo log(原子性)、锁 + MVCC(隔离性)三大组件实现可靠的事务处理。在并发场景下,四种隔离级别提供了 “一致性” 与 “性能” 的权衡选择 —— 日常开发中,互联网场景常用 “读已提交” 平衡性能与一致性,核心业务(如订单、支付)用默认的 “可重复读” 确保数据安全。


文章转载自:

http://NXCv6ZKb.crtgd.cn
http://09EjyMXq.crtgd.cn
http://hJXcWMza.crtgd.cn
http://vXh8bSMA.crtgd.cn
http://YFrHQssn.crtgd.cn
http://nfn6rFiP.crtgd.cn
http://hLfhxj8C.crtgd.cn
http://Ro7JIKoT.crtgd.cn
http://31me9hAE.crtgd.cn
http://A7sFQlUW.crtgd.cn
http://A13GZ1fu.crtgd.cn
http://LQ3R9ypS.crtgd.cn
http://xUxY2KeJ.crtgd.cn
http://JQJHLe7L.crtgd.cn
http://MMuiDn2B.crtgd.cn
http://wRnQgiPS.crtgd.cn
http://tcLvB4lg.crtgd.cn
http://RxXmE7cn.crtgd.cn
http://eFT3Fiu1.crtgd.cn
http://wQ9w7mpv.crtgd.cn
http://q47jl50s.crtgd.cn
http://ytpKloc1.crtgd.cn
http://BFsR0D11.crtgd.cn
http://Hru9e5LB.crtgd.cn
http://Wa7AVISv.crtgd.cn
http://27ZBaJBF.crtgd.cn
http://Q0GPtol8.crtgd.cn
http://4ZLD48cN.crtgd.cn
http://7nDE46AN.crtgd.cn
http://kY0emwLS.crtgd.cn
http://www.dtcms.com/a/381117.html

相关文章:

  • claude code使用小窍门
  • Recaptcha2 图像识别 API 对接说明
  • Spring中 @Value注解设置默认值
  • Linux / Windows 下连续发送多帧 8 字节指令,下位机只响应第一帧,第二帧“丢失”。
  • RStudio 教程:以抑郁量表测评数据分析为例
  • 驱动程序介绍及其安装说明
  • Day03 前缀和 | 1248. 统计「优美子数组」、53. 最大子数组和
  • 现代化心理中心场室建设与规划之道
  • 面向小白用户的多集群云原生应用管理平台设计
  • 怎么设计一个高效的任务调度器,避免任务饥饿
  • Linux运维核心知识体系总结:从安全加密到服务部署
  • 50期权日内交易技巧
  • 枚举算法和排序算法能力测试
  • 未来之窗昭和仙君 (四) 前端网页分页 — 东方仙盟筑基期
  • Class50 LSTM
  • Redis是什么?一篇讲透它的定位、特点与应用场景
  • [zlaq.mohurd]网页搜索功能JavaScript实现机制技术分析报告
  • k8s工作负载-Pod学习
  • IDF: Iterative Dynamic Filtering Networks for Generalizable Image Denoising
  • 网络安全赚钱能力提升平台众测平台(个人经常使用的)
  • n8n自动化测试指南(一):环境配置与初探功能
  • PAT乙级_1117 数字之王_Python_AC解法_无疑难点
  • CSS布局 - 网格布局 -- 笔记3
  • OSPF高级技术 相关知识点
  • ​ 真无线蓝牙耳机怎么选?舒适与实用如何兼得?
  • 4. 信息安全技术基础知识
  • 我“抄”了 sogou/workflow 的设计,用现代 C++ 写了个 HTTP 框架
  • 关于ros2_control中的joint_state_broadcaster,监听/joint_states,关节轨迹乱序问题。
  • 【Anaconda】Conda 与 Pip 在包管理方面的区别
  • 【卷积神经网络详解与实例】6——经典CNN之LeNet