审核状态宜用语义化TINYINT(0待审/1通过/2拒绝/3撤回),复杂流程需增reviewed_at等字段;并发审核须SELECT...FOR UPDATE加锁;日志应独立建表并建联合索引;COUNT(*)慢时可用游标分页替代OFFSET。

审核状态字段怎么设计才合理
直接用 TINYINT 或 ENUM 存审核状态最常见,但别用 0/1 这种模糊值。推荐用明确语义的整数:0(待审核)、1(已通过)、2(已拒绝)、3(已撤回)。这样查起来不用翻注释,也方便加新状态。
如果状态流转复杂(比如“驳回后可重提”),建议额外加 reviewed_at、reviewer_id、review_comment 字段,避免后期补字段导致历史数据逻辑断裂。
别把审核逻辑全压在应用层——比如靠代码判断“当前用户能否审核这条记录”,而应在数据库加约束或视图控制可见范围。
如何用 SELECT + FOR UPDATE 实现审核时的并发安全
多人同时点“通过”同一条记录,不加锁会导致覆盖写。必须在事务中先查再更新,并显式加锁:
START TRANSACTION; SELECT * FROM article WHERE id = 123 AND status = 0 FOR UPDATE; -- 应用层校验权限、业务规则 UPDATE article SET status = 1, reviewed_at = NOW(), reviewer_id = 456 WHERE id = 123; COMMIT;
注意三点:
-
FOR UPDATE只对索引列生效,id必须是主键或有唯一索引,否则会锁全表 - WHERE 条件里一定要包含
status = 0,否则可能锁住不该审的行 - 事务不能太长,审核页停留太久再提交,容易触发锁等待超时(
Lock wait timeout exceeded)
审核日志要不要单独建表
要。别把操作人、时间、前/后状态、备注全塞进主表。单独建 review_log 表更清晰:
CREATE TABLE review_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, target_table VARCHAR(32) NOT NULL, -- 'article', 'user' target_id BIGINT NOT NULL, operator_id INT NOT NULL, old_status TINYINT, new_status TINYINT NOT NULL, comment TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_target (target_table, target_id), INDEX idx_op (operator_id, created_at) );
好处很明显:
- 主表体积小,查询快
- 日志可审计、可追溯,不影响主流程性能
- 删旧日志(比如保留180天)不会影响主业务数据
别忘了给 target_table 和 target_id 加联合索引,不然按单条记录查日志会变慢。
审核列表分页为什么 COUNT(*) 会越来越慢
当待审数据从几百条涨到几十万条,SELECT COUNT(*) FROM article WHERE status = 0 会成为瓶颈。MySQL 8.0+ 可以考虑用物化视图替代(需手动维护),但更实际的做法是:
- 用近似值:查
information_schema.TABLES的TABLE_ROWS(仅 MyISAM 精确,InnoDB 是估算) - 在应用层缓存总数,每次审核成功后用
UPDATE counter SET count = count - 1 WHERE type = 'pending'更新 - 放弃精确总数,改用“下一页是否有数据”判断(即查
LIMIT 21,只显示前20条,有第21条就说明还有下一页)
真正卡住的往往不是 COUNT,而是分页偏移量大了以后的 OFFSET 100000。用游标分页(WHERE id > ? ORDER BY id LIMIT 20)才是长期解法。










