事务
事务(Transaction)是将一组 SQL 操作作为一个整体执行的机制。事务确保这组操作要么全部成功,要么全部失败,是数据库保证数据一致性的核心功能。
为什么需要事务
考虑转账场景:
sql
-- 从账户 A 转账 100 元到账户 B
-- 第一步:A 账户减去 100 元
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 第二步:B 账户加上 100 元
UPDATE accounts SET balance = balance + 100 WHERE id = 2;如果第一步成功但第二步失败,会导致资金丢失。事务可以确保两步操作作为一个整体,要么都成功,要么都失败。
ACID 特性
事务具有四个核心特性:
| 特性 | 英文 | 说明 |
|---|---|---|
| 原子性 | Atomicity | 事务中的操作要么全部执行,要么全部不执行 |
| 一致性 | Consistency | 事务完成后,数据状态保持一致 |
| 隔离性 | Isolation | 并发事务之间相互隔离,互不影响 |
| 持久性 | Durability | 事务提交后,修改永久保存 |
ACID 关系
- 原子性是基础
- 隔离性是手段
- 一致性是约束
- 持久性是目的
事务控制语句
基本语法
sql
-- 开启事务
BEGIN;
-- 或
START TRANSACTION;
-- 提交事务
COMMIT;
-- 回滚事务
ROLLBACK;显式事务示例
sql
-- 转账事务
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;回滚事务
sql
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- 发现问题,回滚
ROLLBACK;保存点(SAVEPOINT)
sql
BEGIN;
INSERT INTO orders (id, amount) VALUES (1, 100);
SAVEPOINT order_created;
INSERT INTO order_items (order_id, product_id) VALUES (1, 101);
SAVEPOINT items_added;
-- 回滚到保存点
ROLLBACK TO items_added;
-- 继续其他操作
INSERT INTO order_items (order_id, product_id) VALUES (1, 102);
COMMIT;删除保存点
sql
RELEASE SAVEPOINT savepoint_name;自动提交
MySQL 自动提交模式
sql
-- 查看自动提交状态
SELECT @@autocommit;
-- 关闭自动提交
SET autocommit = 0;
-- 开启自动提交
SET autocommit = 1;completion_type 参数
sql
-- 查看当前设置
SELECT @@completion_type;
-- 设置 completion_type
SET @@completion_type = 1;| 值 | 说明 |
|---|---|
| 0 | 默认,COMMIT 后需要手动开启新事务 |
| 1 | COMMIT AND CHAIN,提交后自动开启新事务 |
| 2 | COMMIT AND RELEASE,提交后断开连接 |
自动提交与显式事务
sql
-- autocommit = 1 时
BEGIN; -- 显式开启事务
INSERT INTO test VALUES ('数据');
COMMIT; -- 需要显式提交
-- autocommit = 0 时
INSERT INTO test VALUES ('数据'); -- 自动开启事务
COMMIT; -- 需要显式提交隔离级别
四种隔离级别
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | ✓ | ✓ | ✓ |
| READ COMMITTED | ✗ | ✓ | ✓ |
| REPEATABLE READ | ✗ | ✗ | ✓ |
| SERIALIZABLE | ✗ | ✗ | ✗ |
设置隔离级别
sql
-- 设置全局隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 设置当前会话隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 设置下一个事务隔离级别
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 查看当前隔离级别
SELECT @@transaction_isolation;READ UNCOMMITTED(读未提交)
可能读到其他事务未提交的数据(脏读):
sql
-- 事务 A
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN;
UPDATE students SET name = 'Bob' WHERE id = 1;
-- 未提交...
-- 事务 B
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN;
SELECT * FROM students WHERE id = 1; -- 读到 'Bob'(脏读)READ COMMITTED(读已提交)
只能读到已提交的数据,但同一事务内可能读到不同结果(不可重复读):
sql
-- 事务 A
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
UPDATE students SET name = 'Bob' WHERE id = 1;
COMMIT;
-- 事务 B
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
SELECT * FROM students WHERE id = 1; -- 读到 'Alice'
-- 事务 A 提交后
SELECT * FROM students WHERE id = 1; -- 读到 'Bob'(不可重复读)REPEATABLE READ(可重复读)
同一事务内多次读取结果一致,但可能出现幻读:
sql
-- 事务 A
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
INSERT INTO students (id, name) VALUES (99, 'Bob');
COMMIT;
-- 事务 B
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SELECT * FROM students WHERE id = 99; -- 空
-- 事务 A 提交后
SELECT * FROM students WHERE id = 99; -- 仍然空
UPDATE students SET name = 'Alice' WHERE id = 99; -- 更新成功!
SELECT * FROM students WHERE id = 99; -- 出现了!(幻读)SERIALIZABLE(串行化)
最高隔离级别,事务串行执行,避免所有并发问题:
sql
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
SELECT * FROM students WHERE id = 1;
-- 其他事务无法修改 id=1 的记录,直到此事务提交
COMMIT;MySQL 默认隔离级别
MySQL InnoDB 默认使用 REPEATABLE READ。
隔离级别选择
| 场景 | 推荐级别 | 原因 |
|---|---|---|
| 高并发读取 | READ COMMITTED | 性能好,避免脏读 |
| 需要一致性读取 | REPEATABLE READ | 默认级别,适合大多数场景 |
| 极高数据一致性 | SERIALIZABLE | 性能差,谨慎使用 |
| 统计分析(允许脏读) | READ UNCOMMITTED | 性能最好 |
事务最佳实践
1. 保持事务简短
sql
-- 好:事务简短
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
-- 差:事务过长
BEGIN;
-- 大量操作...
SELECT ...;
UPDATE ...;
DELETE ...;
-- 业务逻辑处理...
COMMIT;2. 避免在事务中处理耗时操作
sql
-- 差:事务中包含网络请求
BEGIN;
UPDATE orders SET status = 'processing';
-- 发送邮件(耗时操作)
CALL send_email(...);
COMMIT;
-- 好:先完成数据库操作
BEGIN;
UPDATE orders SET status = 'processing';
COMMIT;
-- 事务外发送邮件
CALL send_email(...);3. 正确处理错误
sql
-- 使用存储过程处理错误
DELIMITER //
CREATE PROCEDURE transfer_funds(
IN from_id INT,
IN to_id INT,
IN amount DECIMAL(10,2)
)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SELECT '转账失败' AS result;
END;
START TRANSACTION;
UPDATE accounts SET balance = balance - amount WHERE id = from_id;
UPDATE accounts SET balance = balance + amount WHERE id = to_id;
COMMIT;
SELECT '转账成功' AS result;
END //
DELIMITER ;4. 使用合适的隔离级别
sql
-- 报表查询:使用较低隔离级别
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
SELECT * FROM large_table WHERE ...;
COMMIT;
-- 关键业务:使用默认或更高隔离级别
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
-- 关键操作
COMMIT;总结
事务控制语句
| 语句 | 说明 |
|---|---|
| BEGIN / START TRANSACTION | 开启事务 |
| COMMIT | 提交事务 |
| ROLLBACK | 回滚事务 |
| SAVEPOINT | 创建保存点 |
| ROLLBACK TO | 回滚到保存点 |
隔离级别对比
| 级别 | 脏读 | 不可重复读 | 幻读 | 性能 |
|---|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 | 最高 |
| READ COMMITTED | 不会 | 可能 | 可能 | 高 |
| REPEATABLE READ | 不会 | 不会 | 可能 | 中 |
| SERIALIZABLE | 不会 | 不会 | 不会 | 最低 |
最佳实践
- 保持事务简短:减少锁定时间
- 选择合适隔离级别:平衡一致性和性能
- 正确处理错误:使用异常处理机制
- 避免长事务:防止资源长时间锁定