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
共享锁、排他锁、行锁、表锁

最佳实践:

  1. 事务要简短
  2. 选择合适的隔离级别
  3. 使用索引减少锁范围
  4. 避免死锁
  5. 监控长事务

下一步,让我们学习用户权限管理!

最后更新:2026-03-26