增减积分必须用事务包裹,否则并发时数据错乱;mysql表引擎须为innodb;日志需存明细并同事务操作;外部输入须过滤校验;批量操作应分批处理并后台执行。

增减积分必须用事务包裹,否则并发时数据错乱
用户同时完成两个任务(比如签到+评论),两次 UPDATE 如果没加事务,极可能只执行其中一条,导致积分少加或多加。MySQL 的 MyISAM 引擎不支持事务,务必确认表引擎是 InnoDB。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 用
$pdo->beginTransaction()开启事务,成功后commit(),失败则rollback() - 避免在事务里做 HTTP 请求、文件读写等耗时操作
- 不要用
SELECT + UPDATE两步更新(先查再改),直接用UPDATE user SET score = score + ? WHERE id = ? - 如果涉及多表(如用户表 + 积分日志表),两表都得是
InnoDB,且在同一个事务里操作
积分日志要存明细,不能只记最终值
只存当前总分,等于放弃追溯能力——用户问“我昨天怎么少了50分?”,你没法查。日志必须记录每次变更的完整上下文。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 日志表至少包含:
user_id、change(正负整数)、reason(如"sign_in"、"comment_post")、ref_id(关联业务ID,如评论ID)、created_at -
reason用预定义字符串,别存自由文本,方便后续统计和筛选 - 写日志和改积分必须在同一个事务里,否则日志写了但积分没更新,或反之
- 避免用
INSERT ... SELECT或触发器自动记日志——逻辑分散、难调试、事务控制易出错
PHP 更新积分时,别用 $_POST 原样拼 SQL
常见错误是直接把 $_POST['score'] 插进 SQL,结果被注入篡改,用户自己 POST -999999 就能清空积分。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 所有外部输入必须过滤:用
filter_var($score, FILTER_VALIDATE_INT)校验是否为整数 - 业务层校验合理范围,比如签到最多+10分,就该限制
$score >= 1 && $score - 一律用 PDO 预处理语句:
$stmt = $pdo->prepare("UPDATE user SET score = score + ? WHERE id = ?") - 别信前端 JS 校验,后端必须重验;也别依赖 session 中缓存的“当前积分”来计算新值——以数据库为准
批量发放积分时,注意 MySQL 的 max_allowed_packet 和超时
给 10 万人发 5 分,如果循环 10 万次单条 UPDATE,会卡死、超时、占满连接池。但一次性 INSERT INTO log ... VALUES (...), (...), ... 又可能因数据包太大被截断。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 拆成每 500–1000 条一批,用
INSERT INTO log (...) VALUES (...),(...),...批量插入日志 - 检查 MySQL 配置:
max_allowed_packet至少设为64M,wait_timeout加大到 300 秒以上 - 用
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false避免内存爆掉(尤其查大量用户ID时) - 别在 Web 请求里干这事——改用 CLI 脚本 +
nohup php give_score.php &后台跑,配合pcntl_fork或队列更稳妥
真正麻烦的不是加减法本身,而是每次变更都要对得上日志、扛得住并发、经得起回溯。漏掉事务、跳过校验、绕过日志,短期看不出问题,一到活动高峰或用户投诉就全暴露。











