404 Not Found

404 Not Found


nginx

事务处理

事务处理

🌍 生活类比

想象一次银行转账——你从 A 账户转 1000 元到 B 账户:

  1. 从 A 账户扣除 1000
  2. 向 B 账户增加 1000

如果第 1 步成功、第 2 步失败(比如系统崩溃),A 的钱少了但 B 没收到——这就出大问题了。

事务就是把这两步绑定为一个"原子操作":要么两步都成功(COMMIT),要么两步都撤销(ROLLBACK),绝不会出现"半完成"的状态。


🎯 核心概念

什么是事务

事务是一组不可分割的 SQL 操作序列,要么全部成功执行,要么全部回滚撤销。它是数据库保证数据一致性的核心机制。

ACID 特性

特性 英文 含义
原子性 Atomicity 事务中的操作要么全部成功,要么全部回滚
一致性 Consistency 事务前后数据库从一个一致状态变到另一个一致状态
隔离性 Isolation 并发事务之间互不干扰,如同串行执行
持久性 Durability 事务提交后,数据永久保存,即使系统崩溃也不丢失
💡 记忆技巧:A-C-I-D,原子性打头,持久性收尾,像一个完整的保护链。

BEGIN / COMMIT / ROLLBACK

SQL
-- 开启事务
BEGIN;  -- 或 START TRANSACTION

-- 执行一系列操作
UPDATE accounts SET balance = balance - 1000 WHERE id = 1;
UPDATE accounts SET balance = balance + 1000 WHERE id = 2;

-- 全部成功,提交
COMMIT;

-- 出现问题,回滚
ROLLBACK;

SAVEPOINT — 保存点

在事务内部设置"检查点",可以回滚到指定保存点,而不影响保存点之前的操作。

SQL
BEGIN;

INSERT INTO orders VALUES (1020, 101, 1, '2026-06-28', 500.00, 'pending');
SAVEPOINT sp1;

INSERT INTO order_items VALUES (1020, 1, 2, 100.00);
SAVEPOINT sp2;

-- 如果第二步出错,只回滚到 sp1
ROLLBACK TO sp1;

-- 保留第一步的操作
COMMIT;
命令 作用
SAVEPOINT name 设置保存点
ROLLBACK TO name 回滚到指定保存点(保留保存点之前的操作)
RELEASE SAVEPOINT name 删除保存点

事务隔离级别

当多个事务同时操作相同数据时,可能出现以下问题:

问题 说明 示例
脏读 读到其他事务未提交的数据 事务A修改了数据但未提交,事务B读到了修改后的值
不可重复读 同一事务内两次读取同一行,结果不同 事务A两次读同一行,中间事务B修改并提交了该行
幻读 同一事务内两次执行同一查询,行数不同 事务A两次查询,中间事务B插入了新行

SQL 定义了四个隔离级别:

隔离级别 脏读 不可重复读 幻读 说明
READ UNCOMMITTED ✅ 可能 ✅ 可能 ✅ 可能 最低级别,几乎不用
READ COMMITTED ❌ 不会 ✅ 可能 ✅ 可能 Oracle/PostgreSQL 默认
REPEATABLE READ ❌ 不会 ❌ 不会 ✅ 可能 MySQL 默认
SERIALIZABLE ❌ 不会 ❌ 不会 ❌ 不会 最高级别,性能最差
SQL
-- 查看当前隔离级别
SELECT @@transaction_isolation;

-- 设置隔离级别
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

死锁

当两个事务互相等待对方释放资源时,就会发生死锁:

事务A:锁住表1 → 等待表2
事务B:锁住表2 → 等待表1
→ 互相等待,永远无法完成

数据库的处理方式:自动检测死锁,选择一个事务作为"牺牲者"进行回滚,另一个事务继续执行。

避免死锁的策略

最佳实践

原则 说明
事务尽量短 减少锁持有时间,提高并发性能
不要在事务中等待用户输入 事务应快速完成
使用合适的隔离级别 不要默认用最高级别
统一访问顺序 减少死锁概率
加错误处理 出错时及时 ROLLBACK

📝 基本语法

SQL
-- 开启事务
BEGIN;  -- MySQL、PostgreSQL
START TRANSACTION;  -- MySQL 也支持

-- 提交事务(所有操作永久生效)
COMMIT;

-- 回滚事务(撤销所有操作)
ROLLBACK;

-- 保存点
SAVEPOINT savepoint_name;
ROLLBACK TO savepoint_name;
RELEASE SAVEPOINT savepoint_name;

-- 设置隔离级别
SET TRANSACTION ISOLATION LEVEL 隔离级别;
-- 隔离级别:READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE

-- 设置全局隔离级别(影响所有新连接)
SET GLOBAL TRANSACTION ISOLATION LEVEL 隔离级别;

-- 自动提交开关(MySQL 默认开启)
SET autocommit = 0;  -- 关闭自动提交,需要手动 COMMIT
SET autocommit = 1;  -- 开启自动提交(默认)
💡 提示

  • MySQL 默认 autocommit = 1,每条 SQL 自动提交。使用事务前需先 BEGINSET autocommit = 0
  • DDL 语句(CREATE、ALTER、DROP)会隐式提交事务
  • 连接断开时,未提交的事务会自动回滚

📌 示例

示例:订单创建事务(订单 + 库存扣减)

SQL
-- 场景:客户下单购买产品,需要同时创建订单和扣减库存
BEGIN;

-- 第一步:创建订单
INSERT INTO orders (order_id, customer_id, employee_id, order_date, total_amount, status)
VALUES (1020, 101, 1, '2026-06-28', 6999.00, 'pending');

-- 第二步:扣减库存
UPDATE products SET stock = stock - 1 WHERE product_id = 1;

-- 第三步:检查库存是否足够(如果 stock < 0 则回滚)
-- 在实际应用中,这一步通常用程序逻辑或触发器实现

-- 全部成功,提交
COMMIT;
▶ 试一试

如果中间出错

SQL
BEGIN;

INSERT INTO orders (order_id, customer_id, employee_id, order_date, total_amount, status)
VALUES (1021, 102, 3, '2026-06-28', 129.00, 'pending');

UPDATE products SET stock = stock - 1 WHERE product_id = 2;

-- 假设发现库存不足,回滚整个事务
ROLLBACK;
-- 订单和库存都恢复到事务之前的状态

示例:使用 SAVEPOINT 部分回滚

SQL
BEGIN;

-- 第一笔订单:成功
INSERT INTO orders VALUES (1022, 103, 4, '2026-06-28', 3500.00, 'pending');
UPDATE products SET stock = stock - 1 WHERE product_id = 4;
SAVEPOINT after_first_order;

-- 第二笔订单:出错
INSERT INTO orders VALUES (1023, 104, 4, '2026-06-28', 2200.00, 'pending');
UPDATE products SET stock = stock - 1 WHERE product_id = 5;
SAVEPOINT after_second_order;

-- 发现第二笔订单有问题,回滚到第一笔之后
ROLLBACK TO after_first_order;

-- 第一笔订单仍然有效,提交
COMMIT;
-- 结果:订单1022被保留,订单1023被撤销
▶ 试一试

执行验证

SQL
SELECT order_id, total_amount, status FROM orders WHERE order_id >= 1022;
TEXT
order_id | total_amount | status
---------+-------------+--------
    1022 |     3500.00 | pending

说明ROLLBACK TO after_first_order 只撤销了保存点之后的操作,第一笔订单和库存扣减仍然保留。


🎬 场景演练

场景一:批量员工工资调整(带错误处理)

SQL
BEGIN;

-- 给销售部涨薪 10%
UPDATE employees 
SET salary = salary * 1.10 
WHERE department_id = (SELECT department_id FROM departments WHERE department_name = '销售部');

SAVEPOINT after_sales_raise;

-- 给技术部涨薪 8%
UPDATE employees 
SET salary = salary * 1.08 
WHERE department_id = (SELECT department_id FROM departments WHERE department_name = '技术部');

SAVEPOINT after_tech_raise;

-- 假设预算审批未通过,只撤销技术部涨薪
ROLLBACK TO after_sales_raise;

-- 销售部涨薪保留,提交
COMMIT;

要点SAVEPOINT 实现"部分回滚",适合需要灵活控制的批量操作场景。

场景二:模拟转账并处理死锁

SQL
-- 会话A:从部门1转预算到部门2
BEGIN;
UPDATE departments SET budget = budget - 50000 WHERE department_id = 1;
-- 等待一会儿...
UPDATE departments SET budget = budget + 50000 WHERE department_id = 2;
COMMIT;

-- 会话B:同时从部门2转预算到部门1(另一个连接)
BEGIN;
UPDATE departments SET budget = budget - 30000 WHERE department_id = 2;
UPDATE departments SET budget = budget + 30000 WHERE department_id = 1;
COMMIT;

避免死锁的做法

SQL
-- 两个会话都按 department_id 升序访问
-- 会话A:
BEGIN;
UPDATE departments SET budget = budget - 50000 WHERE department_id = 1;  -- 先锁1
UPDATE departments SET budget = budget + 50000 WHERE department_id = 2;  -- 再锁2
COMMIT;

-- 会话B:
BEGIN;
UPDATE departments SET budget = budget + 30000 WHERE department_id = 1;  -- 也先锁1
UPDATE departments SET budget = budget - 30000 WHERE department_id = 2;  -- 再锁2
COMMIT;

要点:统一访问顺序是避免死锁最简单有效的方法。


❓ 常见问题

Q:MySQL 默认是自动提交的,怎么使用事务? A: MySQL 默认 autocommit = 1,每条 SQL 都会自动提交。使用事务时,先执行 BEGIN(或 START TRANSACTION)开启事务,之后的 SQL 不会自动提交,直到你执行 COMMITROLLBACK

Q:COMMIT 之后还能 ROLLBACK 吗? A: 不能。COMMIT 是事务的终点,提交后数据已永久保存,无法撤销。如果需要"撤销"已提交的数据,只能通过新的事务执行反向操作(如 UPDATE 回原值)。

Q:应该选择哪个隔离级别? A: 大多数场景用数据库默认级别即可(MySQL 默认 REPEATABLE READ,Oracle/PostgreSQL 默认 READ COMMITTED)。只有在明确需要防止幻读且能接受性能损失时,才用 SERIALIZABLE

Q:DDL 语句(CREATE TABLE、ALTER TABLE)会受事务控制吗? A: 不会。DDL 语句会隐式提交当前事务。在事务中执行 DDL 后,之前的 SQL 无法再回滚。这是 MySQL 的行为,PostgreSQL 中 DDL 可以被回滚。


📖 小节

概念 说明
事务 一组不可分割的数据库操作
ACID 原子性、一致性、隔离性、持久性
BEGIN 开启事务
COMMIT 提交事务,永久生效
ROLLBACK 回滚事务,全部撤销
SAVEPOINT 事务内的保存点,支持部分回滚
隔离级别 控制并发事务之间的可见性
死锁 两个事务互相等待,数据库自动检测并回滚其中一个

📝 作业

  1. 编写一个事务:向 orders 表插入一条新订单,同时扣减 products 表对应产品的库存。如果库存不足,回滚整个事务。
  2. 使用 SAVEPOINT 实现一个事务中插入 3 条订单记录,然后回滚第 3 条,保留前 2 条。
  3. 查看你数据库的默认隔离级别,并尝试设置为 READ COMMITTED,观察对并发读写的影响。
  4. 思考:如果一个事务执行时间很长,会对其他事务产生什么影响?如何优化?

下一课

👉 22-stored-procedures - 存储过程:学习存储过程的创建和调用、输入输出参数、流程控制语句,以及存储过程在实际业务中的应用。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏