题目
高并发转账场景下的事务与锁设计
信息
- 类型:问答
- 难度:⭐⭐
考点
事务ACID特性,悲观锁与乐观锁应用,死锁预防,隔离级别选择
快速回答
在高并发转账场景中,需通过事务和锁机制保证数据一致性:
- 使用数据库事务确保操作的原子性和一致性
- 采用SELECT FOR UPDATE实现悲观锁,或版本号机制实现乐观锁
- 设置合理的隔离级别(推荐Read Committed或Repeatable Read)
- 添加死锁检测和重试机制
- 遵循固定顺序的锁获取原则预防死锁
场景说明
设计一个银行转账系统,账户表结构如下:
CREATE TABLE accounts (
id INT PRIMARY KEY,
name VARCHAR(50),
balance DECIMAL(10,2),
version INT DEFAULT 0 -- 乐观锁版本号字段
);在高并发场景下,从账户A向账户B转账100元,需保证:
1. 余额不能为负
2. 并发操作不出现超扣
3. 避免死锁
解决方案对比
方案1:悲观锁实现(SELECT FOR UPDATE)
BEGIN TRANSACTION;
-- 锁定账户A和B(按固定ID顺序避免死锁)
SELECT * FROM accounts WHERE id IN (A,B) ORDER BY id ASC FOR UPDATE;
-- 检查A账户余额
SELECT balance FROM accounts WHERE id = A;
-- 转账操作
UPDATE accounts SET balance = balance - 100 WHERE id = A;
UPDATE accounts SET balance = balance + 100 WHERE id = B;
COMMIT;原理说明:
- 通过
FOR UPDATE在事务开始时锁定相关行 - 按固定ID顺序获取锁可预防死锁(如总是先锁小ID)
- 事务提交时自动释放锁
方案2:乐观锁实现(版本号控制)
-- 伪代码流程
DO $$
DECLARE
retry_count INT := 0;
max_retries INT := 3;
BEGIN
WHILE retry_count < max_retries LOOP
-- 获取当前版本号和余额
SELECT balance, version INTO old_balance_A, old_version_A
FROM accounts WHERE id = A;
SELECT balance, version INTO old_balance_B, old_version_B
FROM accounts WHERE id = B;
-- 业务校验
IF old_balance_A < 100 THEN
RAISE EXCEPTION 'Insufficient balance';
END IF;
-- 尝试更新(带版本校验)
UPDATE accounts
SET balance = old_balance_A - 100, version = version + 1
WHERE id = A AND version = old_version_A;
IF row_count = 0 THEN
retry_count := retry_count + 1;
CONTINUE;
END IF;
UPDATE accounts
SET balance = old_balance_B + 100, version = version + 1
WHERE id = B AND version = old_version_B;
IF row_count = 0 THEN
ROLLBACK;
retry_count := retry_count + 1;
CONTINUE;
END IF;
COMMIT;
RETURN;
END LOOP;
RAISE EXCEPTION 'Transfer failed after retries';
END$$;原理说明:
- 通过version字段检测数据是否被修改
- 更新失败时自动重试(需设置重试上限)
- 不长期占用锁,适合低冲突场景
最佳实践
- 隔离级别选择:推荐Read Committed(平衡性能与一致性)
- 锁策略选择:
- 高冲突场景 → 悲观锁
- 低冲突场景 → 乐观锁(减少锁开销)
- 死锁预防:
- 统一锁获取顺序(如按ID排序)
- 设置锁等待超时(
SET lock_timeout = '500ms')
- 索引优化:确保WHERE条件使用索引,减少锁范围
常见错误
- 锁缺失:未加锁直接查询后更新,导致超扣
- 锁顺序不一致:不同事务按不同顺序加锁引发死锁
- 长事务:事务中包含耗时操作,增大锁竞争概率
- 过度锁定:使用表锁导致性能下降
扩展知识
- MVCC机制:PostgreSQL/MySQL通过多版本控制实现非阻塞读
- 锁升级:SQL Server在锁过多时自动升级为表锁
- 分布式事务:跨数据库转账需考虑XA协议或Saga模式
- 监控工具:
- MySQL:
SHOW ENGINE INNODB STATUS查看死锁 - PostgreSQL:
pg_stat_activity监控锁等待
- MySQL: