fix: 治理线上 MySQL CPU 波动的两个热点查询

- 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 <noreply@anthropic.com>
This commit is contained in:
zhx 2026-07-10 16:10:57 +08:00
parent 68fc43d4b2
commit 959ce54750
2 changed files with 31 additions and 5 deletions

View File

@ -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',

View File

@ -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'