MySQL事务 #
一、事务概述 #
1.1 什么是事务 #
事务是一组数据库操作的逻辑单元,要么全部成功,要么全部失败。
text
┌─────────────────────────────────────┐
│ 事务 │
├─────────────────────────────────────┤
│ BEGIN │
│ ├── 操作1:扣减账户A余额 │
│ ├── 操作2:增加账户B余额 │
│ └── 操作3:记录转账日志 │
│ COMMIT / ROLLBACK │
└─────────────────────────────────────┘
1.2 ACID特性 #
| 特性 | 说明 |
|---|---|
| Atomicity(原子性) | 事务是不可分割的工作单位 |
| Consistency(一致性) | 事务前后数据保持一致状态 |
| Isolation(隔离性) | 多个事务并发执行互不干扰 |
| Durability(持久性) | 事务提交后永久保存 |
1.3 事务控制语句 #
sql
-- 开始事务
START TRANSACTION;
-- 或
BEGIN;
-- 提交事务
COMMIT;
-- 回滚事务
ROLLBACK;
-- 设置保存点
SAVEPOINT savepoint_name;
-- 回滚到保存点
ROLLBACK TO savepoint_name;
-- 释放保存点
RELEASE SAVEPOINT savepoint_name;
二、基本事务操作 #
2.1 简单事务 #
sql
-- 开始事务
START TRANSACTION;
-- 执行操作
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- 提交事务
COMMIT;
-- 或回滚事务
-- ROLLBACK;
2.2 自动提交 #
sql
-- 查看自动提交状态
SELECT @@autocommit;
-- 1: 自动提交开启(默认)
-- 0: 自动提交关闭
-- 关闭自动提交
SET autocommit = 0;
-- 开启自动提交
SET autocommit = 1;
2.3 保存点 #
sql
START TRANSACTION;
INSERT INTO orders (user_id, amount) VALUES (1, 100);
SAVEPOINT after_order;
INSERT INTO order_items (order_id, product_id, quantity) VALUES (1, 1, 2);
-- 回滚到保存点
ROLLBACK TO after_order;
-- 提交
COMMIT;
三、事务隔离级别 #
3.1 隔离级别介绍 #
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | ✓ | ✓ | ✓ |
| READ COMMITTED | ✗ | ✓ | ✓ |
| REPEATABLE READ | ✗ | ✗ | ✓ |
| SERIALIZABLE | ✗ | ✗ | ✗ |
3.2 查看和设置隔离级别 #
sql
-- 查看当前隔离级别
SELECT @@transaction_isolation;
-- 查看全局隔离级别
SELECT @@global.transaction_isolation;
-- 设置会话隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 设置全局隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
3.3 READ UNCOMMITTED #
sql
-- 可以读取未提交的数据(脏读)
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
-- 会话A
START TRANSACTION;
UPDATE users SET age = 100 WHERE id = 1;
-- 未提交
-- 会话B
SELECT age FROM users WHERE id = 1; -- 读取到未提交的100
-- 会话A
ROLLBACK; -- 回滚
-- 会话B读取的数据是无效的
3.4 READ COMMITTED #
sql
-- 只能读取已提交的数据
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 会话A
START TRANSACTION;
SELECT age FROM users WHERE id = 1; -- 25
-- 会话B
START TRANSACTION;
UPDATE users SET age = 30 WHERE id = 1;
COMMIT;
-- 会话A
SELECT age FROM users WHERE id = 1; -- 30(不可重复读)
COMMIT;
3.5 REPEATABLE READ(MySQL默认) #
sql
-- 同一事务中多次读取结果相同
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- 会话A
START TRANSACTION;
SELECT age FROM users WHERE id = 1; -- 25
-- 会话B
START TRANSACTION;
UPDATE users SET age = 30 WHERE id = 1;
COMMIT;
-- 会话A
SELECT age FROM users WHERE id = 1; -- 仍然是25
COMMIT;
3.6 SERIALIZABLE #
sql
-- 最高隔离级别,完全串行化执行
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- 会话A
START TRANSACTION;
SELECT * FROM users WHERE id = 1;
-- 会话B
START TRANSACTION;
UPDATE users SET age = 30 WHERE id = 1; -- 阻塞等待
-- 会话A
COMMIT;
-- 会话B继续执行
COMMIT;
四、并发问题 #
4.1 脏读 #
sql
-- 脏读:读取到其他事务未提交的数据
-- 会话A
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 会话B(READ UNCOMMITTED)
SELECT balance FROM accounts WHERE id = 1; -- 读取到未提交的数据
-- 会话A
ROLLBACK; -- 数据回滚,会话B读取的数据无效
4.2 不可重复读 #
sql
-- 不可重复读:同一事务中两次读取结果不同
-- 会话A
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1; -- 1000
-- 会话B
UPDATE accounts SET balance = 900 WHERE id = 1;
COMMIT;
-- 会话A
SELECT balance FROM accounts WHERE id = 1; -- 900
COMMIT;
4.3 幻读 #
sql
-- 幻读:同一事务中两次查询记录数不同
-- 会话A
START TRANSACTION;
SELECT COUNT(*) FROM users WHERE age > 25; -- 5
-- 会话B
INSERT INTO users (name, age) VALUES ('New', 30);
COMMIT;
-- 会话A
SELECT COUNT(*) FROM users WHERE age > 25; -- 6
COMMIT;
五、锁机制 #
5.1 锁类型 #
| 锁类型 | 说明 |
|---|---|
| 共享锁(S锁) | 允许其他事务读,不允许写 |
| 排他锁(X锁) | 不允许其他事务读写 |
| 意向锁 | 表级锁,表示事务要获取行锁 |
| 行锁 | 锁定单行记录 |
| 表锁 | 锁定整个表 |
| 间隙锁 | 锁定一个范围 |
| 临键锁 | 行锁+间隙锁 |
5.2 共享锁和排他锁 #
sql
-- 共享锁(S锁)
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;
-- MySQL 8.0+
SELECT * FROM users WHERE id = 1 FOR SHARE;
-- 排他锁(X锁)
SELECT * FROM users WHERE id = 1 FOR UPDATE;
5.3 行锁示例 #
sql
-- 会话A
START TRANSACTION;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- 会话B(阻塞)
START TRANSACTION;
SELECT * FROM users WHERE id = 1 FOR UPDATE; -- 等待会话A释放锁
-- 会话A
COMMIT;
-- 会话B继续执行
COMMIT;
5.4 间隙锁示例 #
sql
-- 间隙锁防止幻读
-- 假设users表有id: 1, 5, 10
-- 会话A
START TRANSACTION;
SELECT * FROM users WHERE id > 3 AND id < 8 FOR UPDATE;
-- 会话B(阻塞)
INSERT INTO users (id, name) VALUES (4, 'Test'); -- 阻塞
INSERT INTO users (id, name) VALUES (6, 'Test'); -- 阻塞
-- 会话A锁定了(3, 8)这个间隙
5.5 查看锁信息 #
sql
-- 查看当前锁
SELECT * FROM performance_schema.data_locks;
-- 查看锁等待
SELECT * FROM performance_schema.data_lock_waits;
-- 查看InnoDB锁
SHOW ENGINE INNODB STATUS;
六、死锁 #
6.1 死锁示例 #
sql
-- 会话A
START TRANSACTION;
UPDATE users SET age = 30 WHERE id = 1;
-- 会话B
START TRANSACTION;
UPDATE users SET age = 40 WHERE id = 2;
-- 会话A
UPDATE users SET age = 50 WHERE id = 2; -- 等待会话B释放id=2的锁
-- 会话B
UPDATE users SET age = 60 WHERE id = 1; -- 死锁!
-- ERROR 1213 (40001): Deadlock found when trying to get lock
6.2 避免死锁 #
sql
-- 1. 按相同顺序访问表和行
-- 会话A和B都按id顺序更新
START TRANSACTION;
UPDATE users SET age = 30 WHERE id = 1;
UPDATE users SET age = 40 WHERE id = 2;
COMMIT;
-- 2. 使用较短的事务
-- 3. 避免长时间运行的事务
-- 4. 合理设计索引
6.3 查看死锁信息 #
sql
-- 查看最近的死锁
SHOW ENGINE INNODB STATUS;
-- 查看死锁历史
SELECT * FROM performance_schema.events_waits_history;
七、事务最佳实践 #
7.1 事务使用原则 #
sql
-- 1. 事务要简短
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
-- 2. 避免在事务中进行耗时操作
-- 错误示例
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 发送邮件(耗时操作)
CALL send_email(...);
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
-- 3. 合理设置隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
7.2 锁优化 #
sql
-- 1. 使用索引避免表锁
-- 有索引:行锁
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- 无索引:表锁
SELECT * FROM users WHERE name = 'John' FOR UPDATE;
-- 2. 减少锁范围
-- 只查询需要的列
SELECT id, name FROM users WHERE id = 1 FOR UPDATE;
-- 3. 避免长事务
7.3 事务监控 #
sql
-- 查看长事务
SELECT
trx_id,
trx_state,
trx_started,
TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS duration
FROM information_schema.innodb_trx
ORDER BY duration DESC;
-- 查看锁等待超时设置
SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';
-- 设置锁等待超时
SET innodb_lock_wait_timeout = 50;
八、总结 #
事务要点:
| 要点 | 说明 |
|---|---|
| ACID | 原子性、一致性、隔离性、持久性 |
| 控制语句 | BEGIN, COMMIT, ROLLBACK |
| 隔离级别 | READ UNCOMMITTED ~ SERIALIZABLE |
| 锁 | 共享锁、排他锁、行锁、表锁 |
最佳实践:
- 事务要简短
- 选择合适的隔离级别
- 使用索引减少锁范围
- 避免死锁
- 监控长事务
下一步,让我们学习用户权限管理!
最后更新:2026-03-26