订单创建时需在同一个数据库事务中同步写入积分变动日志,确保与订单表同库同InnoDB引擎,通过beginTransaction/commit包裹Order创建和PointLog插入,并校验before_point/after_point一致性。

订单创建时怎么同步写入积分变动日志
订单完成才触发积分发放,但日志必须在订单状态变更的**同一事务内**落库,否则会出现积分已加、日志丢失的不一致问题。PHP 中常用 beginTransaction() + commit() / rollback() 包裹订单写入和日志插入逻辑。
关键点:
- 日志表必须与订单表同库同引擎(推荐 InnoDB),否则无法保证事务原子性
- 不要用异步队列或定时任务补日志——延迟或失败都会导致对账困难
-
log_type字段建议设为枚举值,如'order_reward'(下单返积分)、'order_consume'(积分抵扣) - 记录原始订单 ID(
order_id)、用户 ID(user_id)、变动积分值(point_change,正为增加、负为扣除)、操作前/后余额(before_point/after_point)
DB::beginTransaction();
try {
$order = Order::create($orderData);
PointLog::create([
'user_id' => $order->user_id,
'order_id' => $order->id,
'log_type' => 'order_reward',
'point_change' => $rewardPoints,
'before_point' => $user->point,
'after_point' => $user->point + $rewardPoints,
'created_at' => now(),
]);
$user->increment('point', $rewardPoints);
DB::commit();
} catch (\Exception $e) {
DB::rollback();
throw $e;
}积分抵扣订单怎么避免重复记日志
用户用积分支付时,常见错误是:前端提交两次、接口被重放、或支付回调多次通知,导致 PointLog 表出现多条相同 order_id + log_type='order_consume' 的记录。
防御措施:
立即学习“PHP免费学习笔记(深入)”;
- 在
point_log表上建唯一索引:UNIQUE KEY `uk_order_type` (`order_id`, `log_type`) - 插入前先
SELECT COUNT(*) WHERE order_id = ? AND log_type = 'order_consume',存在则跳过 - 不要依赖前端传来的积分值做计算,应以订单表中
used_point字段为准 - 支付回调处理必须幂等:用
ORDER BY created_at LIMIT 1查最新订单状态,而非直接更新
怎么查某用户最近 30 天的积分变动明细
直接 SELECT * FROM point_log WHERE user_id = ? ORDER BY created_at DESC LIMIT 50 很容易慢,尤其当表数据超百万行时。
优化要点:
- 确保有复合索引:
INDEX idx_user_time (user_id, created_at) - 避免
SELECT *,只查必要字段,如id,log_type,point_change,order_id,created_at - 如果需关联订单号展示商品信息,用应用层分批查(N+1 改成 IN 查询),别在 SQL 里
JOIN orders - 高频查询可加缓存,键名如
point_log:user_123:recent,TTL 设为 60 秒,避免缓存雪崩
日志表什么时候需要分表或归档
当 point_log 单表超过 200 万行,且 INSERT 和 SELECT 明显变慢(慢查询日志中频繁出现),就该考虑归档。
实操建议:
- 按月归档最稳妥:建表
point_log_202408,用INSERT INTO ... SELECT搬迁旧数据,再DELETE原表对应月份数据 - 不要用分区表(MySQL Partitioning),维护成本高,且 Laravel/Eloquent 对分区支持弱
- 归档后,查询“历史积分”走归档表,日常“最近变动”仍查主表,应用层路由判断
- 归档脚本必须带事务和校验:搬完后比对
COUNT(*)和SUM(point_change)
日志字段看似简单,但 before_point 和 after_point 这两个值一旦因并发漏算或事务回滚没同步更新,后续所有积分对账都不可信。宁可多一次 SELECT 读当前余额,也不要靠 PHP 层变量推算。











