TCL 事务控制语言
SQL事务控制语言TCL:BEGIN、COMMIT、ROLLBACK、SAVEPOINT的语法、嵌套事务与并发控制
1. TCL 概述
事务控制语言(Transaction Control Language,TCL)用于管理 SQL 事务的生命周期,确保数据操作的原子性和一致性。
1.1 事务的生命周期
BEGIN → SQL操作 → COMMIT/ROLLBACK
↘ SAVEPOINT → 部分回滚 → COMMIT/ROLLBACK
1.2 核心语句
| 语句 | 作用 |
|---|---|
| BEGIN | 开始事务 |
| COMMIT | 提交事务,持久化所有变更 |
| ROLLBACK | 回滚事务,撤销所有变更 |
| SAVEPOINT | 设置保存点,允许部分回滚 |
2. BEGIN 开始事务
2.1 语法
-- SQL 标准
BEGIN;
BEGIN TRANSACTION;
BEGIN WORK;
-- PostgreSQL
BEGIN ISOLATION LEVEL READ COMMITTED;
BEGIN ISOLATION LEVEL SERIALIZABLE;
BEGIN READ ONLY;
BEGIN READ WRITE;
2.2 自动提交模式
-- 大多数数据库默认自动提交(autocommit)
-- 每条 SQL 语句自动成为一个事务
-- 关闭自动提交(MySQL)
SET autocommit = 0;
-- 显式事务(推荐)
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
3. COMMIT 提交事务
3.1 基本用法
BEGIN;
INSERT INTO orders (user_id, amount) VALUES (42, 99.99);
UPDATE inventory SET stock = stock - 1 WHERE product_id = 100;
COMMIT; -- 两条语句的变更同时持久化
3.2 提交的保证
COMMIT 成功后,数据库保证:
- 变更已写入重做日志(redo log / WAL)
- 即使系统崩溃,变更也不会丢失
- 其他事务可以看到这些变更
3.3 链式提交
-- PostgreSQL:COMMIT AND CHAIN 自动开始新事务
COMMIT AND CHAIN;
-- 等价于 COMMIT; BEGIN;
4. ROLLBACK 回滚事务
4.1 完全回滚
BEGIN;
UPDATE accounts SET balance = balance - 1000000 WHERE id = 1;
-- 发现操作错误
ROLLBACK; -- 撤销所有变更,恢复到事务开始前的状态
4.2 隐式回滚
-- 以下情况事务自动回滚:
-- 1. 连接断开
-- 2. 语句执行错误(部分数据库)
-- 3. 死锁被选中牺牲
-- PostgreSQL:错误后事务进入 aborted 状态
BEGIN;
INSERT INTO orders VALUES (1, 99.99);
INSERT INTO orders VALUES ('invalid', 99.99); -- 类型错误
-- 事务进入 aborted 状态,后续语句都被忽略
INSERT INTO orders VALUES (2, 49.99); -- 被忽略
ROLLBACK; -- 必须显式回滚
4.3 链式回滚
-- PostgreSQL:ROLLBACK AND CHAIN
ROLLBACK AND CHAIN;
-- 等价于 ROLLBACK; BEGIN;
5. SAVEPOINT 保存点
5.1 基本用法
BEGIN;
INSERT INTO orders (id, amount) VALUES (1, 100);
SAVEPOINT sp1;
UPDATE inventory SET stock = stock - 1 WHERE product_id = 100;
-- 发现库存不足,回滚到保存点
ROLLBACK TO SAVEPOINT sp1;
-- 尝试其他操作
UPDATE inventory SET stock = stock - 1 WHERE product_id = 200;
COMMIT;
-- 最终:订单1已插入,库存200已减少,库存100未变更
5.2 多级保存点
BEGIN;
SAVEPOINT level1;
INSERT INTO table_a VALUES (1);
SAVEPOINT level2;
INSERT INTO table_b VALUES (2);
SAVEPOINT level3;
INSERT INTO table_c VALUES (3);
-- 回滚到 level2,level3 的变更被撤销
ROLLBACK TO SAVEPOINT level2;
-- level1 和 level2 的变更仍然保留
COMMIT;
5.3 释放保存点
-- RELEASE SAVEPOINT:释放保存点,不能再回滚到该点
BEGIN;
SAVEPOINT sp1;
INSERT INTO table_a VALUES (1);
RELEASE SAVEPOINT sp1;
-- ROLLBACK TO SAVEPOINT sp1; -- 错误!保存点已释放
ROLLBACK; -- 回滚整个事务
6. 嵌套事务
6.1 SQL 标准不支持真正的嵌套事务
-- 大多数数据库不支持嵌套 BEGIN
BEGIN;
INSERT INTO table_a VALUES (1);
BEGIN; -- 错误或被忽略
INSERT INTO table_b VALUES (2);
COMMIT;
COMMIT;
6.2 使用保存点模拟嵌套事务
-- 外层事务
BEGIN;
SAVEPOINT outer;
INSERT INTO table_a VALUES (1);
-- 模拟内层事务
SAVEPOINT inner;
INSERT INTO table_b VALUES (2);
-- 内层回滚
ROLLBACK TO SAVEPOINT inner;
-- 外层继续
INSERT INTO table_c VALUES (3);
COMMIT;
6.3 SQL Server 嵌套事务
-- SQL Server 支持嵌套 BEGIN TRANSACTION
BEGIN TRANSACTION outer;
INSERT INTO table_a VALUES (1);
BEGIN TRANSACTION inner;
INSERT INTO table_b VALUES (2);
COMMIT TRANSACTION inner; -- 减少嵌套计数
COMMIT TRANSACTION outer; -- 真正提交
7. 事务与并发
7.1 事务隔离级别
-- 设置事务隔离级别
BEGIN ISOLATION LEVEL READ COMMITTED;
BEGIN ISOLATION LEVEL REPEATABLE READ;
BEGIN ISOLATION LEVEL SERIALIZABLE;
-- MySQL
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
7.2 只读事务
-- 只读事务:优化器可以做更多优化
BEGIN READ ONLY;
SELECT * FROM employees WHERE dept_id = 5;
COMMIT;
7.3 事务超时
-- PostgreSQL:设置事务超时
SET idle_in_transaction_session_timeout = '5min';
-- 事务空闲超过5分钟自动回滚
-- MySQL:锁等待超时
SET innodb_lock_wait_timeout = 50; -- 50秒
8. 事务最佳实践
8.1 事务应尽可能短
-- 不推荐:事务中包含耗时操作
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 调用外部 API(耗时数秒)
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
-- 推荐:先完成耗时操作,再开启事务
-- 调用外部 API
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
8.2 避免大事务
-- 不推荐:一次性更新百万行
BEGIN;
UPDATE large_table SET status = 'archived' WHERE date < '2020-01-01';
COMMIT;
-- 推荐:分批更新
BEGIN;
UPDATE large_table SET status = 'archived'
WHERE date < '2020-01-01' LIMIT 10000;
COMMIT;
-- 重复执行直到影响行数为0
BEGIN
单行写法:开启事务
BEGIN;
-- 开启一个事务
BEGIN;
单行写法:MySQL 开启事务
START TRANSACTION;
-- MySQL 开启事务
START TRANSACTION;
单行写法:开启只读事务
BEGIN READ ONLY;
-- 开启只读事务(PostgreSQL)
BEGIN READ ONLY;
COMMIT
单行写法:提交事务
COMMIT;
-- 提交当前事务
COMMIT;
换行写法:完整事务流程
BEGIN; ... COMMIT;
-- 转账事务流程
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
ROLLBACK
单行写法:回滚事务
ROLLBACK;
-- 回滚当前事务
ROLLBACK;
换行写法:事务回滚示例
BEGIN; ... ROLLBACK;
-- 转账失败时回滚
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
ROLLBACK;
SAVEPOINT
单行写法:设置保存点
SAVEPOINT <保存点名>;
-- 在事务中设置保存点
SAVEPOINT sp1;
单行写法:回滚到保存点
ROLLBACK TO SAVEPOINT <保存点名>;
-- 回滚到指定保存点(不回滚整个事务)
ROLLBACK TO SAVEPOINT sp1;
单行写法:释放保存点
RELEASE SAVEPOINT <保存点名>;
-- 释放保存点(保存点之后不能再回滚到该点)
RELEASE SAVEPOINT sp1;
换行写法:保存点完整示例
BEGIN; ... SAVEPOINT ...; ... ROLLBACK TO ...; COMMIT;
-- 使用保存点实现部分回滚
BEGIN;
INSERT INTO orders (id, amount) VALUES (1, 100);
SAVEPOINT sp1;
INSERT INTO orders (id, amount) VALUES (2, 200);
ROLLBACK TO SAVEPOINT sp1;
INSERT INTO orders (id, amount) VALUES (3, 300);
COMMIT;
SET TRANSACTION
单行写法:设置事务隔离级别
SET TRANSACTION ISOLATION LEVEL <隔离级别>;
-- 设置事务隔离级别为可重复读
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
换行写法:开启事务时指定隔离级别
BEGIN ISOLATION LEVEL <隔离级别>;
-- PostgreSQL 开启事务并指定隔离级别
BEGIN ISOLATION LEVEL SERIALIZABLE;
单行写法:设置只读事务
SET TRANSACTION READ ONLY;
-- 设置当前事务为只读
SET TRANSACTION READ ONLY;
隔离级别
单行写法:读未提交
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
-- 设置隔离级别为读未提交(允许脏读)
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
单行写法:读已提交
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 设置隔离级别为读已提交(防止脏读)
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
单行写法:可重复读
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- 设置隔离级别为可重复读(防止脏读和不可重复读)
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
单行写法:可串行化
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- 设置隔离级别为可串行化(最高隔离级别)
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
MySQL 自动提交
单行写法:查看自动提交状态
SELECT @@autocommit;
-- 查看当前自动提交状态
SELECT @@autocommit;
单行写法:关闭自动提交
SET autocommit = 0;
-- 关闭自动提交,需手动 COMMIT
SET autocommit = 0;
单行写法:开启自动提交
SET autocommit = 1;
-- 开启自动提交(默认)
SET autocommit = 1;
锁
单行写法:共享锁
SELECT ... LOCK IN SHARE MODE;
-- 加共享锁读取数据
SELECT * FROM accounts WHERE id = 1 LOCK IN SHARE MODE;
单行写法:排他锁
SELECT ... FOR UPDATE;
-- 加排他锁读取数据
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
单行写法:PostgreSQL 跳过已锁行
SELECT ... FOR UPDATE SKIP LOCKED;
-- 跳过已被锁定的行(用于任务队列)
SELECT * FROM task_queue WHERE status = 'pending' FOR UPDATE SKIP LOCKED LIMIT 1;
单行写法:PostgreSQL 锁定指定列
SELECT ... FOR UPDATE OF <表别名>
-- 仅锁定指定表的行
SELECT * FROM orders o JOIN users u ON o.user_id = u.id WHERE u.id = 1 FOR UPDATE OF o;
死锁处理
换行写法:死锁示例
BEGIN; ... BEGIN; ...
-- 事务 A 锁定行 1,事务 B 锁定行 2,互相等待导致死锁
-- 事务 A
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 事务 B
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 2;
-- 事务 A 请求锁定行 2(被 B 持有)
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- 事务 B 请求锁定行 1(被 A 持有)→ 死锁
UPDATE accounts SET balance = balance + 100 WHERE id = 1;
单行写法:设置锁超时
SET lock_timeout = '<时间>';
-- 设置锁等待超时为 5 秒
SET lock_timeout = '5s';
单行写法:MySQL 查看锁信息
SELECT * FROM information_schema.INNODB_LOCKS;
-- 查看 InnoDB 锁信息
SELECT * FROM information_schema.INNODB_LOCKS;