并发编程与数据隔离

核心问题

并发场景下的典型数据问题:

问题 描述 例子
竞态条件 多个线程同时读写同一数据,结果依赖执行顺序 两个请求同时修改用户余额
脏读 读到未提交的事务数据 A 修改未提交,B 读到了
丢失更新 后提交的覆盖先提交的 两人同时编辑同一文档
幻读 同一查询在不同时间返回不同行数 统计用户数时有人新注册

数据库层面解决方案

事务隔离级别

隔离级别 脏读 丢失更新 幻读 默认数据库
Read Uncommitted -
Read Committed Oracle, PostgreSQL
Repeatable Read MySQL
Serializable -

乐观锁 vs 悲观锁

1
2
3
4
5
6
7
8
-- 悲观锁(先锁再改)
SELECT * FROM users WHERE id = 1 FOR UPDATE;
UPDATE users SET balance = balance - 100 WHERE id = 1;

-- 乐观锁(先改再检查版本号)
UPDATE users
SET balance = balance - 100, version = version + 1
WHERE id = 1 AND version = 5;

应用层面解决方案

分布式锁(Redis):

1
2
3
4
async function acquireLock(key, ttl) {
const result = await redis.set(key, 'locked', 'EX', ttl, 'NX');
return result === 'OK';
}

队列****串行化

  • 用户级队列,保证同一用户操作串行执行
  • 适用于:订单处理、账户操作

架构层面解决方案

方案 说明 适用场景
数据分片 按用户/租户分片,天然隔离 多租户 SaaS
读写分离 写主库、读从库 读多写少
CQRS 命令查询职责分离 复杂业务系统

常见场景推荐方案

场景 推荐方案 理由
库存扣减 乐观锁 + 重试 高并发,冲突率低
账户转账 悲观锁 / 分布式锁 强一致性要求
秒杀抢购 队列串行化 + 预扣减 抗高并发
配置更新 版本号 + CAS 简单有效

实践项目:gf_v3 进销存系统

可应用点

  1. 库存扣减(高并发场景)
1
2
3
UPDATE products 
SET stock = stock - 1, version = version + 1
WHERE id = ? AND stock > 0;
  1. 多租户数据隔离
    1. 每行数据加 tenant_id 字段
    2. 查询强制带上 WHERE tenant_id = ?
  2. 采购单/销售单并发编辑
    1. 使用乐观锁版本号
    2. 冲突时提示用户刷新重试
  3. 用户会话隔离
    1. 不同租户的数据完全隔离
    2. 数据库层面 + 应用层双重校验

推荐学习资源

书籍

  • 《数据库系统概念》- 事务隔离章节
  • 《高性能 MySQL》- 锁与并发
  • 《数据密集型应用系统设计》(DDIA) - 第 7、8 章 ⭐⭐⭐

DDIA 阅读链接

在线文档