别再死记硬背了!用这5个真实SQL场景,帮你彻底搞懂数据库事务和并发控制
数据库事务和并发控制是每个开发者必须掌握的核心技能,但教科书上的理论往往让人望而生畏。本文将带你通过5个电商系统典型场景,用实战代码演示事务隔离级别和锁机制如何解决并发问题。
1. 库存超卖问题:理解事务原子性
电商大促时,我们常遇到"库存显示有余量但下单失败"的情况。以下是一个典型场景:
-- 错误示范:非原子操作导致超卖 UPDATE products SET stock = stock - 1 WHERE id = 100; INSERT INTO orders(user_id, product_id) VALUES (123, 100);当两个用户同时执行这段代码时,可能出现库存减为负数的情况。解决方案是使用事务包裹整个操作:
BEGIN TRANSACTION; -- 添加行级锁防止并发修改 SELECT stock FROM products WHERE id = 100 FOR UPDATE; UPDATE products SET stock = stock - 1 WHERE id = 100; INSERT INTO orders(user_id, product_id) VALUES (123, 100); COMMIT;关键点:
FOR UPDATE子句对查询行加排他锁- 事务确保库存检查和扣减是原子操作
- 其他事务必须等待当前事务完成才能操作同一行
2. 账户余额不一致:隔离级别的实战选择
银行转账需要同时修改两个账户余额,不同隔离级别的表现:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 适用场景 |
|---|---|---|---|---|
| READ UNCOMMITTED | ✓ | ✓ | ✓ | 几乎不用 |
| READ COMMITTED | × | ✓ | ✓ | 默认级别 |
| REPEATABLE READ | × | × | ✓ | MySQL默认 |
| SERIALIZABLE | × | × | × | 严格场景 |
-- 设置隔离级别为REPEATABLE READ SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; BEGIN; -- 转账操作 UPDATE accounts SET balance = balance - 100 WHERE user_id = 1; UPDATE accounts SET balance = balance + 100 WHERE user_id = 2; COMMIT;提示:REPEATABLE READ能防止转账过程中其他事务修改数据,但可能产生死锁
3. 订单状态同步:避免脏读的三种方案
物流系统需要读取订单状态进行发货,但可能读到未提交的中间状态:
方案对比表:
| 方案 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
| 提高隔离级别 | SET ISOLATION LEVEL READ COMMITTED | 简单 | 性能影响 |
| 乐观锁 | 添加version字段 | 高并发 | 需重试机制 |
| 悲观锁 | SELECT FOR UPDATE | 强一致 | 可能死锁 |
悲观锁实现示例:
BEGIN; -- 锁定待发货订单 SELECT * FROM orders WHERE status = 'paid' FOR UPDATE; -- 发货操作... UPDATE orders SET status = 'shipped' WHERE id = 100; COMMIT;4. 数据统计报表:快照读与当前读
生成年度报表时需要一致的数据视图:
-- 使用READ ONLY事务保证数据一致性 START TRANSACTION READ ONLY; -- 快照读(MySQL) SELECT SUM(amount) FROM orders WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31'; -- 其他统计查询... COMMIT;MVCC机制下:
- 普通SELECT使用快照读(一致性非锁定读)
- SELECT FOR UPDATE使用当前读(锁定读)
- READ ONLY事务使用启动时的数据版本
5. 死锁分析与解决:电商购物车案例
典型死锁场景:
- 用户A先锁商品X,再请求锁商品Y
- 用户B先锁商品Y,再请求锁商品X
解决方案:
-- 统一获取锁的顺序 BEGIN; -- 按商品ID排序获取锁 SELECT * FROM products WHERE id IN (X, Y) ORDER BY id FOR UPDATE; -- 购物车操作... COMMIT;死锁排查技巧:
-- MySQL查看最近死锁 SHOW ENGINE INNODB STATUS; -- 关键信息包含: -- LATEST DETECTED DEADLOCK -- WAITING FOR THIS LOCK -- HOLDS THE LOCK实际项目中,我们通过监控发现80%的死锁源于不规范的锁获取顺序。建立编码规范后,死锁率下降了90%。