From 959ce547501d1c136dded18275c9ce3f87ff1579 Mon Sep 17 00:00:00 2001 From: zhx Date: Fri, 10 Jul 2026 16:10:57 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=B2=BB=E7=90=86=E7=BA=BF=E4=B8=8A=20M?= =?UTF-8?q?ySQL=20CPU=20=E6=B3=A2=E5=8A=A8=E7=9A=84=E4=B8=A4=E4=B8=AA?= =?UTF-8?q?=E7=83=AD=E7=82=B9=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - backfill-last7 的报表用户集合改从 wallet_accounts(7千行)取, 替代每10分钟对千万级 wallet_entries 做资产全量 DISTINCT 扫描 (线上等价性已核验:旧集合 4907 人 ⊆ 新集合 4908 人) - lucky_gift_outbox 补两个无 app_code 前缀的 claim 索引, 消除 worker 每秒空轮询的全索引扫描+filesort(线上已手工应用) Co-Authored-By: Claude Fable 5 --- .../mysql/initdb/001_lucky_gift_service.sql | 25 ++++++++++++++++++- .../cmd/backfill-last7/main.go | 11 +++++--- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/services/lucky-gift-service/deploy/mysql/initdb/001_lucky_gift_service.sql b/services/lucky-gift-service/deploy/mysql/initdb/001_lucky_gift_service.sql index 3b8fe370..1967f15c 100644 --- a/services/lucky-gift-service/deploy/mysql/initdb/001_lucky_gift_service.sql +++ b/services/lucky-gift-service/deploy/mysql/initdb/001_lucky_gift_service.sql @@ -47,9 +47,32 @@ CREATE TABLE IF NOT EXISTS lucky_gift_outbox ( KEY idx_lucky_gift_outbox_status_retry (app_code, status, next_retry_at_ms), KEY idx_lucky_gift_outbox_lucky_retry (app_code, event_type, status, next_retry_at_ms, created_at_ms, outbox_id), KEY idx_lucky_gift_outbox_lucky_lock (app_code, event_type, status, lock_until_ms, created_at_ms, outbox_id), - KEY idx_lucky_gift_outbox_retention (app_code, status, updated_at_ms, outbox_id) + KEY idx_lucky_gift_outbox_retention (app_code, status, updated_at_ms, outbox_id), + KEY idx_lucky_gift_outbox_claim_retry (event_type, status, next_retry_at_ms, created_at_ms, outbox_id), + KEY idx_lucky_gift_outbox_claim_lock (event_type, status, lock_until_ms, created_at_ms, outbox_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='幸运礼物 owner outbox 表'; +-- Worker 的 outbox 抢占 SQL 不带 app_code(跨 App 统一补偿),老索引都以 app_code 开头无法命中, +-- 每次轮询(默认 1 秒 × 空轮询最多 6 条查询)都会全索引扫描并 filesort 整表。 +-- 这两个无 app_code 前缀的索引让空轮询变成零行索引定位,是线上 MySQL 基线 CPU 的主要治理点。 +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'lucky_gift_outbox' AND INDEX_NAME = 'idx_lucky_gift_outbox_claim_retry') = 0, + 'ALTER TABLE lucky_gift_outbox ADD INDEX idx_lucky_gift_outbox_claim_retry (event_type, status, next_retry_at_ms, created_at_ms, outbox_id), ALGORITHM=INPLACE, LOCK=NONE', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'lucky_gift_outbox' AND INDEX_NAME = 'idx_lucky_gift_outbox_claim_lock') = 0, + 'ALTER TABLE lucky_gift_outbox ADD INDEX idx_lucky_gift_outbox_claim_lock (event_type, status, lock_until_ms, created_at_ms, outbox_id), ALGORITHM=INPLACE, LOCK=NONE', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + CREATE TABLE IF NOT EXISTS lucky_gift_rule_versions ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码', pool_id VARCHAR(96) NOT NULL COMMENT '幸运礼物奖池 ID', diff --git a/services/statistics-service/cmd/backfill-last7/main.go b/services/statistics-service/cmd/backfill-last7/main.go index 3dbad996..2f28583e 100644 --- a/services/statistics-service/cmd/backfill-last7/main.go +++ b/services/statistics-service/cmd/backfill-last7/main.go @@ -352,12 +352,15 @@ func collectReportMetricUserIDs(ctx context.Context, walletDB *sql.DB, userDB *s }{ { db: walletDB, + // 历史持有人集合从 wallet_accounts 取:账户行在首笔流水时创建且从不删除, + // 与「wallet_entries 中出现过该资产」等价(提前开户的账户是无害超集,只影响维度预取)。 + // 千万级 wallet_entries 上按资产全量 DISTINCT 扫描曾是线上 MySQL 每 10 分钟的 CPU 尖峰主因。 query: ` - SELECT DISTINCT e.user_id - FROM wallet_entries e - WHERE e.app_code = ? AND e.asset_type IN ('COIN', 'COIN_SELLER_COIN', 'HOST_SALARY_USD', 'AGENCY_SALARY_USD') AND e.created_at_ms < ? + SELECT user_id + FROM wallet_accounts + WHERE app_code = ? AND asset_type IN ('COIN', 'COIN_SELLER_COIN', 'HOST_SALARY_USD', 'AGENCY_SALARY_USD') AND created_at_ms < ? UNION - SELECT DISTINCT e.counterparty_user_id + SELECT e.counterparty_user_id FROM wallet_entries e JOIN wallet_transactions wt ON wt.app_code = e.app_code AND wt.transaction_id = e.transaction_id WHERE e.app_code = ? AND wt.biz_type = 'salary_transfer_to_coin_seller'