fix(integral): 防止个人奖金重复生成积分
将个人奖金转积分流程改为先写唯一流水再加积分,并用 wa_selfbonus_logid 唯一索引兜底多入口并发场景;同时补充历史重复数据修复与索引落地 SQL 脚本。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
23
backend/sql/add_unique_index_uk_integral_selfbonus_log.sql
Normal file
23
backend/sql/add_unique_index_uk_integral_selfbonus_log.sql
Normal file
@@ -0,0 +1,23 @@
|
||||
-- Add strong idempotency guard for selfbonus -> integral conversion.
|
||||
-- This index guarantees one wa_selfbonus_log can map to at most one integral record.
|
||||
-- Prerequisite: clear duplicate wa_selfbonus_logid rows first.
|
||||
|
||||
-- Pre-checks
|
||||
SELECT wa_selfbonus_logid, COUNT(*) AS cnt
|
||||
FROM eb_user_integral_record
|
||||
WHERE wa_selfbonus_logid IS NOT NULL
|
||||
GROUP BY wa_selfbonus_logid
|
||||
HAVING COUNT(*) > 1
|
||||
LIMIT 20;
|
||||
|
||||
SELECT COUNT(*) AS zero_cnt
|
||||
FROM eb_user_integral_record
|
||||
WHERE wa_selfbonus_logid = 0;
|
||||
|
||||
-- Apply unique index
|
||||
ALTER TABLE eb_user_integral_record
|
||||
ADD UNIQUE KEY uk_integral_selfbonus_log (wa_selfbonus_logid);
|
||||
|
||||
-- Verify index exists
|
||||
SHOW INDEX FROM eb_user_integral_record
|
||||
WHERE Key_name = 'uk_integral_selfbonus_log';
|
||||
130
backend/sql/fix_duplicate_selfbonus_integral_records.sql
Normal file
130
backend/sql/fix_duplicate_selfbonus_integral_records.sql
Normal file
@@ -0,0 +1,130 @@
|
||||
-- Purpose:
|
||||
-- 1) Find duplicate selfbonus integral rows generated from same wa_selfbonus_log
|
||||
-- 2) Backup affected data
|
||||
-- 3) Keep min(id), delete duplicate rows
|
||||
-- 4) Resync eb_user.integral from remaining ledger rows
|
||||
-- 5) Rebuild integer balance snapshot for affected users
|
||||
--
|
||||
-- Notes:
|
||||
-- - Designed for MySQL 5.7
|
||||
-- - Run during low traffic window
|
||||
-- - Review backup table names before execution
|
||||
|
||||
-- 0) Preview duplicate groups
|
||||
SELECT uid,
|
||||
wa_selfbonus_logid,
|
||||
link_id,
|
||||
COUNT(*) AS cnt,
|
||||
SUM(integral) AS total_integral,
|
||||
MIN(id) AS keep_id,
|
||||
GROUP_CONCAT(id ORDER BY id) AS record_ids
|
||||
FROM eb_user_integral_record
|
||||
WHERE link_type = 'selfbonus'
|
||||
AND type = 1
|
||||
AND wa_selfbonus_logid IS NOT NULL
|
||||
GROUP BY uid, wa_selfbonus_logid, link_id
|
||||
HAVING COUNT(*) > 1;
|
||||
|
||||
-- 1) Backup duplicate rows and affected users
|
||||
DROP TABLE IF EXISTS backup_euir_selfbonus_dups_20260511_0959;
|
||||
CREATE TABLE backup_euir_selfbonus_dups_20260511_0959 AS
|
||||
SELECT e.*
|
||||
FROM eb_user_integral_record e
|
||||
JOIN (
|
||||
SELECT uid, wa_selfbonus_logid, link_id, MIN(id) AS keep_id, COUNT(*) AS cnt
|
||||
FROM eb_user_integral_record
|
||||
WHERE link_type = 'selfbonus'
|
||||
AND type = 1
|
||||
AND wa_selfbonus_logid IS NOT NULL
|
||||
GROUP BY uid, wa_selfbonus_logid, link_id
|
||||
HAVING COUNT(*) > 1
|
||||
) d
|
||||
ON d.uid = e.uid
|
||||
AND d.wa_selfbonus_logid = e.wa_selfbonus_logid
|
||||
AND d.link_id = e.link_id;
|
||||
|
||||
DROP TABLE IF EXISTS backup_eb_user_integral_before_fix_20260511_0959;
|
||||
CREATE TABLE backup_eb_user_integral_before_fix_20260511_0959 AS
|
||||
SELECT u.*
|
||||
FROM eb_user u
|
||||
WHERE u.uid IN (
|
||||
SELECT DISTINCT uid FROM backup_euir_selfbonus_dups_20260511_0959
|
||||
);
|
||||
|
||||
-- 2) Deduplicate + resync in one transaction
|
||||
START TRANSACTION;
|
||||
|
||||
DROP TEMPORARY TABLE IF EXISTS tmp_dup_groups;
|
||||
CREATE TEMPORARY TABLE tmp_dup_groups AS
|
||||
SELECT uid, wa_selfbonus_logid, link_id, MIN(id) AS keep_id
|
||||
FROM eb_user_integral_record
|
||||
WHERE link_type = 'selfbonus'
|
||||
AND type = 1
|
||||
AND wa_selfbonus_logid IS NOT NULL
|
||||
GROUP BY uid, wa_selfbonus_logid, link_id
|
||||
HAVING COUNT(*) > 1;
|
||||
|
||||
DROP TEMPORARY TABLE IF EXISTS tmp_affected_uids;
|
||||
CREATE TEMPORARY TABLE tmp_affected_uids AS
|
||||
SELECT DISTINCT uid FROM tmp_dup_groups;
|
||||
|
||||
DELETE e
|
||||
FROM eb_user_integral_record e
|
||||
JOIN tmp_dup_groups d
|
||||
ON d.uid = e.uid
|
||||
AND d.wa_selfbonus_logid = e.wa_selfbonus_logid
|
||||
AND d.link_id = e.link_id
|
||||
WHERE e.id <> d.keep_id;
|
||||
|
||||
UPDATE eb_user u
|
||||
JOIN (
|
||||
SELECT r.uid, COALESCE(SUM(r.integral), 0) AS sum_integral
|
||||
FROM eb_user_integral_record r
|
||||
JOIN tmp_affected_uids t ON t.uid = r.uid
|
||||
GROUP BY r.uid
|
||||
) s ON s.uid = u.uid
|
||||
SET u.integral = s.sum_integral;
|
||||
|
||||
SET @run_uid := 0;
|
||||
SET @run_bal := 0;
|
||||
UPDATE eb_user_integral_record e
|
||||
JOIN (
|
||||
SELECT t.id, FLOOR(t.running) AS new_balance
|
||||
FROM (
|
||||
SELECT s.id,
|
||||
s.uid,
|
||||
(@run_bal := IF(@run_uid = s.uid, @run_bal + s.integral, s.integral)) AS running,
|
||||
(@run_uid := s.uid) AS uid_guard
|
||||
FROM (
|
||||
SELECT id, uid, integral
|
||||
FROM eb_user_integral_record
|
||||
WHERE uid IN (SELECT uid FROM tmp_affected_uids)
|
||||
ORDER BY uid, id
|
||||
) s
|
||||
) t
|
||||
) x ON x.id = e.id
|
||||
SET e.balance = x.new_balance;
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- 3) Post-checks
|
||||
SELECT COUNT(*) AS remaining_dup_groups
|
||||
FROM (
|
||||
SELECT 1
|
||||
FROM eb_user_integral_record
|
||||
WHERE link_type = 'selfbonus'
|
||||
AND type = 1
|
||||
AND wa_selfbonus_logid IS NOT NULL
|
||||
GROUP BY uid, wa_selfbonus_logid, link_id
|
||||
HAVING COUNT(*) > 1
|
||||
) a;
|
||||
|
||||
SELECT u.uid, u.integral, s.sum_integral
|
||||
FROM eb_user u
|
||||
JOIN (
|
||||
SELECT uid, SUM(integral) AS sum_integral
|
||||
FROM eb_user_integral_record
|
||||
GROUP BY uid
|
||||
) s ON s.uid = u.uid
|
||||
WHERE u.uid IN (SELECT DISTINCT uid FROM backup_euir_selfbonus_dups_20260511_0959)
|
||||
ORDER BY u.uid;
|
||||
Reference in New Issue
Block a user