多角色多端状态控制与锁控制

目录

抽象场景描述

在实际业务系统中,我们经常遇到同一条数据记录被多个角色、多个客户端并发操作的情况。典型如“内容审核”、“任务状态更新”、“订单流转”等场景。

本案例抽象为以下数据模型:

id | user_id | word | review_status | review_opinion | review_user_id

这张表用于记录用户提交的内容(word),由后台审核人员进行审核处理,审核状态存储在 review_status 字段,审核意见写入 review_opinion,而 review_user_id 表示执行审核操作的管理员 ID。

很多时候可能都没有 review_user_id 这个内容,但是为了严谨和安全这个审核人也是需要加上的。

在实际业务中,存在以下典型角色:

  • 普通用户:提交或修改 word 内容,触发审核流程。
  • 审核人员(运营):基于 review_status 审核用户提交内容,可能打回或通过。
  • 系统服务或定时任务:自动更改 review_status,例如长时间未审核的内容自动打回。

🎯 具体场景举例

  1. 用户 A 提交文案 word,此时 review_status = 0(待审核)。
  2. 同时运营 B 正在审核该内容,正准备将状态置为 2(已通过)。
  3. 此时,用户 A 发现错误,在运营未审核前修改了内容,review_status 被用户接口自动回退成 0(待审核),运营端页面未刷新,仍提交 2(已通过)。
  4. 结果:状态冲突,最终保存结果不一致,导致运营看到通过,用户看到未通过,系统状态混乱。

状态流转:

[至少有 5 个内容,但是 0和 1 可以在某些场景 共用】

| review_status | 状态含义 | 备注说明 | | — | — | — | | -1 | 草稿 | 用户刚填写内容,尚未提交审核 | | 0 | 待审核 | 用户点击“提交审核”按钮后进入审核流程 | | 1 | 审核中(可选) | 审核人员点进详情页或锁定审核任务 | | 2 | 审核通过 | 内容通过 | | 3 | 审核不通过 / 打回 | 审核失败,需要用户修改后重新提交 |

  1. 用户 A 编辑文案 word,初始为 review_status = -1(草稿)
  2. 用户点击“提交审核”,状态改为 0(待审核)
  3. 运营 B 开始审核,准备提交为 2(通过)
  4. 用户又修改文案内容,系统逻辑应自动将状态重置为 -1(草稿),并中断审核流程或提示运营重新刷新内容
  5. 否则就可能出现状态混乱问题

加乐观锁

  • 数据量不大,不适合加悲观锁(行锁或表锁开销太高)
  • 用户 A 和运营 B 可能通过不同接口/前端并发操作一行数据
  • 如果没有控制机制,会导致运营审核通过的记录被用户刷新覆盖

核心思想:在写入前先检查版本号 / 修改时间 /状态是否被其他人改动过

加一个字段:

version INT DEFAULT 1

✅ 如果版本号一致,说明没人改过,更新成功;

❌ 如果版本号不一致,说明被别人改过了,更新失败 → 返回冲突提示。


UPDATE xxx
SET word = '新的内容', review_status = 0, version = version + 1
WHERE id = 123 AND version = 当前版本号;

但是“前端传来的 version 不可信,如何在多角色多端并发场景下保证状态一致性?”

1、服务端查询 version,不信任前端传参

用户提交内容时:

  • 不接受用户传来的 version
  • 服务端查询当前 version 并进行判断逻辑:
# 查询数据库中最新 version
row = db.query("SELECT version, review_status FROM your_table WHERE id = 123")

# 如果 review_status != -1草稿),说明不允许用户编辑
# 如果 review_status == -1则执行 UPDATE ... WHERE version = row.version

然后执行:

UPDATE your_table
SET word = '新内容', review_status = 0, version = version + 1
WHERE id = 123 AND version = 数据库中读出的 version;

2、UUID版乐观锁

比较项 整型 version UUID version
可预测性 可预测(+1) 不可预测,难以伪造
冲突检测 依赖数据库 current version 更安全,防止前端伪造
安全性 可能被篡改 前端无法猜测
多端并发控制 依赖统一接口流程 可天然支持多端(用户、运营、系统)
可扩展性 固定递增 可嵌入修改来源、时间戳等元信息

✅ UUID 版本号方案设计(建议用字段version_uuid)

用户提交时(草稿 → 待审核):

  • 服务端查询当前版本 UUID 是否匹配
  • 若匹配,生成新的 UUID 作为新版本
current = db.query("SELECT version_uuid FROM your_table WHERE id = 123")
if client_version_uuid != current.version_uuid:
    raise Exception("内容已变更,请刷新后重试")

new_uuid = str(uuid4())
UPDATE your_table
SET word = ..., review_status = 0, version_uuid = :new_uuid
WHERE id = 123 AND version_uuid = :client_version_uuid

运营审核时:

  • 运营提交时也要附带 version_uuid
  • 服务端判断 version_uuid 是否匹配
  • 否则提示“用户已修改内容,请刷新页面”
风险场景 是否规避 说明
用户刷旧页面覆盖运营审核 UUID 不匹配无法更新
多人同时编辑,最后一人覆盖前者结果 UUID 控制写入成功与否
恶意构造 version 参数尝试绕过审核 UUID 无法预测或伪造
多端(Web/APP/运营后台)并发操作冲突 UUID 自然解耦所有端

“用 UUID 来代替整型 version,作为乐观锁的唯一标识”,既解决了并发一致性,又规避了版本信息泄露问题。

审核状态限制

只允许在草稿状态(review_status = -1)下编辑,审核中/待审核/已通过等状态下禁止修改。

  1. 天然避免并发写冲突

    如果一旦用户提交审核(状态为 0 或 1),就锁定该条数据的编辑权限,用户端无法再提交修改,自然避免了和运营端审核产生的数据冲突。

  2. 业务语义清晰

    审核中、待审核、已通过的内容,按理都应视为“已提交作品”,不应再被用户随意改动,符合大多数业务流程对稳定性的要求。

  3. 简化乐观锁处理逻辑

    只需要处理少数“草稿”状态下的编辑,不再需要对每次修改都进行复杂的版本校验或冲突回滚。

if review_status != -1:
    raise Exception("内容已提交,无法修改")

✅ 最终好处

  • 提高稳定性:避免运营、用户之间“互相覆盖”问题。
  • 减少冲突处理成本:不用频繁处理版本对比、状态回滚。
  • 提升用户体验:流程清晰,操作有反馈,错误可避免。

使用哈希防止误触

通过对 user_id + word 做 Hash 判断是否内容变化

✅ 目标问题

  • 审核失败(review_status = 3)后允许用户再次修改并提交。
  • 但用户未做任何修改,仅误点击“提交”按钮,不应触发审核流程
  • 或者内容重复,系统也不应发起无意义的审核请求。
content_hash VARCHAR(64) COMMENT '内容哈希值,用于识别是否变更'

提交时:

  1. 服务端生成新的 content_hash = sha256(user_id + word)。
  2. 查询数据库中现有的 content_hash。
  3. 对比:
    • 一致:说明没有修改 → 拒绝发起审核。
    • 不一致:说明内容变化 → 执行提交审核流程。
优点 说明
✅ 防止误提交 用户内容没变,误点击也不会发起审核流程
✅ 降低系统负担 重复审核流程被拦截,减少系统流量
✅ 语义清晰 用内容 Hash 判断是否“确实修改”
✅ 安全性强 不依赖前端判断是否变更
✅ 扩展性强 后续可加入内容版本控制(记录历史 hash)
   

按职责拆表设计

❗问题本质

  • 用户和运营在同一张表修改同一行数据
  • 容易产生并发冲突、双写问题、状态不一致
  • 如果用乐观锁,还得追踪 version / update_time,逻辑复杂。

表 A:用户提交表(user_word_submit)

字段名 说明
id 主键
user_id 用户 ID
word 用户输入的内容
submit_time 用户提交时间
content_hash 内容 hash,避免重复提交
status 草稿 / 已提交

表 B:审核状态表(word_review_status)

字段名 说明
id 主键
submit_id 对应 user_word_submit.id
review_status 审核状态(0-3等)
review_user_id 审核人员 ID
review_opinion 审核意见
version_uuid 乐观锁控制字段(UUID)
update_time 最后审核修改时间
优点 说明
✅ 物理隔离 用户只能写 submit 表,运营只能写审核表,不会互相覆盖
✅ 写入更安全 业务逻辑天然分离,审计也更清晰
✅ 并发更友好 多端操作无双写,update_time 不冲突
✅ 便于流程解耦 审核逻辑变更不会影响用户内容逻辑
方案 优点 缺点
不拆表(合一) 结构简单、字段集中 双写问题严重,乐观锁逻辑复杂
拆表(推荐) 多角色写分离,逻辑更清晰稳定 表设计略复杂,需要 JOIN 等操作

• 多角色多端同时写入同一行数据的并发问题,最合适的办法是按职责拆表

点赞一下

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦