

新闻资讯
技术学院订单创建时需在同一个数据库事务中同步写入积分变动日志,确保与订单表同库同InnoDB引擎,通过beginTransaction/commit包裹Order创建和PointLog插入,并校验before_point/after_point一致性。
订单完成才触发积分发放,但日志必须在订单状态变更的**同一事务内**落库,否则会出现积分已加、日志丢失的不一致问题。PHP 中常用 beginTransaction() + commit() / rollback() 包裹订单写入和日志插入逻辑。
关键点:
log_type 字段建议设为枚举值,如 'order_reward'(下单返积分)、'order_consume'(积分抵扣)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' 的记录。
防御措施:
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 查最新订单状态,而非直接更新直接 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, ord
er_id, created_at
JOIN orders
point_log:user_123:recent,TTL 设为 60 秒,避免缓存雪崩当 point_log 单表超过 200 万行,且 INSERT 和 SELECT 明显变慢(慢查询日志中频繁出现),就该考虑归档。
实操建议:
point_log_202508,用 INSERT INTO ... SELECT 搬迁旧数据,再 DELETE 原表对应月份数据COUNT(*) 和 SUM(point_change)
日志字段看似简单,但 before_point 和 after_point 这两个值一旦因并发漏算或事务回滚没同步更新,后续所有积分对账都不可信。宁可多一次 SELECT 读当前余额,也不要靠 PHP 层变量推算。