MySql案例详解之事务
下面我会从“事务是什么”→“为什么需要事务”→“事务的四大特性(ACID)”→“MySQL中怎么用事务”→“常见坑与调试技巧”→“完整实战案例(含代码、输出、讲解)”六个层次,给你一个“看完就能上手”的MySQL事务速查手册。所有SQL均在MySQL 8.0验证通过,默认存储引擎InnoDB。
一、事务是什么?
事务(Transaction)是一组要么全部成功、要么全部失败的SQL语句集合。
典型场景:银行转账——A扣钱、B加钱两步必须同时成功,否则回滚。
二、为什么需要事务?
- 并发场景下避免脏读、不可重复读、幻读
- 硬件故障或程序崩溃时保证数据一致性
- 业务规则要求“全-or-无”语义
三、ACID 四字口诀
特性 | 解释 | MySQL实现机制 |
---|---|---|
原子性 Atomicity | 全部成功或全部回滚 | undo log(回滚日志) |
一致性 Consistency | 事务前后数据库状态合法(约束、触发器等) | 约束+undo/redo |
隔离性 Isolation | 并发事务互不干扰 | 锁+MVCC |
持久性 Durability | 提交后永久生效 | redo log(重做日志)+双写缓冲 |
四、MySQL事务语法速查
- 基本流程
START TRANSACTION; -- 或 BEGIN;
-- DML语句1…n
COMMIT; -- 提交
ROLLBACK; -- 回滚
- 自动提交开关
SELECT @@autocommit; -- 1表示自动提交,0表示手动
SET autocommit=0; -- 当前会话关闭自动提交
- 保存点(部分回滚)
START TRANSACTION;
SAVEPOINT sp1;
DELETE FROM user WHERE id=1;
SAVEPOINT sp2;
UPDATE user SET money=100 WHERE id=2;
ROLLBACK TO sp2; -- 只回滚到sp2,保留sp1之前的操作
- 隐式提交(陷阱)
DDL(CREATE/ALTER/DROP)、锁表、ANALYZE、LOAD DATA等语句会强制提交当前事务。
五、隔离级别与并发问题
隔离级别 | 脏读 | 不可重复读 | 幻读 | 加锁读 | 语句 |
---|---|---|---|---|---|
READ UNCOMMITTED | √ | √ | √ | 否 | |
READ COMMITTED | × | √ | √ | 否 | Oracle默认 |
REPEATABLE READ | × | × | ×* | 否 | MySQL默认 |
SERIALIZABLE | × | × | × | 是 | 锁表 |
*InnoDB通过间隙锁+MVCC在REPEATABLE READ下也解决了幻读,因此大多数业务无需跳到SERIALIZABLE。
六、完整实战:银行转账(含异常回滚演示)
- 表结构
CREATE DATABASE IF NOT EXISTS demo_tx;
USE demo_tx;
CREATE TABLE account(id INT PRIMARY KEY,name VARCHAR(20) UNIQUE,money DECIMAL(10,2) NOT NULL CHECK (money>=0)
) ENGINE=InnoDB;INSERT INTO account VALUES
(1,'Alice',1000),
(2,'Bob',1000);
- 存储过程:安全转账
DELIMITER $$
CREATE PROCEDURE sp_transfer(IN from_id INT,IN to_id INT,IN amount DECIMAL(10,2)
)
BEGINDECLARE EXIT HANDLER FOR SQLEXCEPTIONBEGINROLLBACK;SELECT 'Transfer failed, rolled back!' AS msg;END;START TRANSACTION;-- 1. 检查余额IF (SELECT money FROM account WHERE id=from_id) < amount THENSIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Insufficient balance';END IF;-- 2. 扣钱UPDATE account SET money = money - amount WHERE id=from_id;-- 3. 加钱UPDATE account SET money = money + amount WHERE id=to_id;COMMIT;SELECT 'Transfer succeeded!' AS msg;
END$$
DELIMITER ;
-
测试场景
| 步骤 | 会话A(正常转账) | 会话B(并发读) | 结果 |
|—|—|—|—|
| T1 |CALL sp_transfer(1,2,200);
| | Alice:800, Bob:1200 |
| T2 | |SELECT * FROM account;
| 读到提交后最新值(READ COMMITTED) |
| T3 | 故意制造异常:扣完钱后插入违反唯一约束 | | 触发EXIT HANDLER,自动ROLLBACK,双方余额不变 | -
观察undo/redo(可验证)
-- 查看当前活跃事务
SELECT * FROM information_schema.innodb_trx\G-- 查看锁等待
SELECT * FROM sys.innodb_lock_waits\G
七、常见坑与调试技巧
- 忘记COMMIT,导致长事务——
SELECT * FROM information_schema.processlist WHERE time>10;
- 自动提交=1,START TRANSACTION后仍被隐式提交——用
SELECT @@autocommit;
确认 - DDL打断事务——把建索引、加字段操作放在业务低峰期
- 死锁——InnoDB自动回滚代价最小的事务;应用层捕获
1213 Deadlock
错误重试即可 - 批量插入性能——用
START TRANSACTION; ...bulk inserts... COMMIT;
比逐条autocommit快1~2个数量级
八、一句话总结
“BEGIN → 改数据 → 没问题COMMIT,出问题ROLLBACK”是事务90%的工作量;剩下10%在于选对隔离级别、避免长事务、监控锁等待。把本文的存储过程模板复制到测试库跑一遍,你就拥有了可落地的MySQL事务最佳实践。