92 lines
6.4 KiB
SQL
92 lines
6.4 KiB
SQL
-- 20261004 钱包流水表排序索引优化(可重复执行)
|
||
-- ⚠️ 本脚本第 4、5 步(前缀索引)已被 20261005_wallet_transaction_biz_type_varchar.sql 接替:
|
||
-- biz_type 改为 varchar 后重建为全列索引。线上已执行过 20261005 的环境勿再执行本脚本。
|
||
-- 背景:/api/wallet/wallet-transaction/page-all 分页查询很慢;索引生效后发现 page-cz/page-jz 仍慢(3~6s)。
|
||
-- 原因:查询固定排序 create_time DESC, user_id DESC, id DESC,
|
||
-- 旧索引 (tenant_id, del_flag, create_time DESC, user_id DESC) 末尾隐式追加的主键 id 为 ASC,
|
||
-- 与 ORDER BY 最后一段 id DESC 方向不匹配,索引无法提供有序结果,
|
||
-- 优化器对租户内全部流水做 filesort(执行计划 Using filesort),数据量越大越慢。
|
||
-- 方案:建显式包含 id DESC 的 5 列索引,排序完全走索引,LIMIT 只需读取前 N 条;
|
||
-- 旧 4 列索引被新索引前缀完全覆盖,一并删除以降低流水表写入放大。
|
||
-- 已在测试库(123.157.250.162:28816/pjcloudx_wallet)验证:四种查询形态(首页/部门权限包裹/业务类型+时间范围/冷门部门)
|
||
-- filesort 全部消除;删除旧索引后计划不变。
|
||
-- 幂等方式:本环境(OceanBase MySQL 模式)不支持 CREATE INDEX IF NOT EXISTS / DROP INDEX IF EXISTS,
|
||
-- 改用 information_schema.statistics 判断索引是否存在后经 PREPARE 动态执行;
|
||
-- 已存在/已删除时输出 skip 提示,重复执行无副作用。
|
||
-- 执行顺序:在线 DDL 不锁表,可独立执行;若担心流水表写入放大,可只执行第 1、2 步。
|
||
|
||
USE pjcloudx_wallet;
|
||
|
||
-- 1. 新建 5 列排序索引(与 Mapper commonWrapperQuery 的 ORDER BY 完全同序)
|
||
SET @ddl = IF(
|
||
(SELECT COUNT(*) FROM information_schema.statistics
|
||
WHERE table_schema = DATABASE() AND table_name = 'wallet_transaction'
|
||
AND index_name = 'idx_wallet_tx_tenant_del_ct_uid_id') = 0,
|
||
'CREATE INDEX idx_wallet_tx_tenant_del_ct_uid_id ON wallet_transaction (tenant_id, del_flag, create_time DESC, user_id DESC, id DESC)',
|
||
'SELECT ''skip: idx_wallet_tx_tenant_del_ct_uid_id 已存在'' AS msg');
|
||
PREPARE stmt FROM @ddl;
|
||
EXECUTE stmt;
|
||
DEALLOCATE PREPARE stmt;
|
||
|
||
-- 2. 删除被完全覆盖的旧 4 列索引(第 1 步成功后执行)
|
||
SET @ddl = IF(
|
||
(SELECT COUNT(*) FROM information_schema.statistics
|
||
WHERE table_schema = DATABASE() AND table_name = 'wallet_transaction'
|
||
AND index_name = 'idx_wallet_tx_tenant_del_create_user') > 0,
|
||
'DROP INDEX idx_wallet_tx_tenant_del_create_user ON wallet_transaction',
|
||
'SELECT ''skip: idx_wallet_tx_tenant_del_create_user 不存在'' AS msg');
|
||
PREPARE stmt FROM @ddl;
|
||
EXECUTE stmt;
|
||
DEALLOCATE PREPARE stmt;
|
||
|
||
-- 3. 部门前置索引:数据权限为"本部门/自定义部门"的操作者,最终 SQL 是外层 dept_id IN (...) 过滤。
|
||
-- 部门占比小时按 create_time 顺序扫会翻大量无关行;该索引让优化器按部门范围直取,
|
||
-- 部门内排序与索引后缀同序同样免 filesort(已验证:冷门部门走此索引,大部门自动走 5 列索引)。
|
||
-- 代价是流水表(高写入)多维护一个二级索引,如写入压力敏感可只执行 1、2 步。
|
||
SET @ddl = IF(
|
||
(SELECT COUNT(*) FROM information_schema.statistics
|
||
WHERE table_schema = DATABASE() AND table_name = 'wallet_transaction'
|
||
AND index_name = 'idx_wallet_tx_tenant_del_dept_ct') = 0,
|
||
'CREATE INDEX idx_wallet_tx_tenant_del_dept_ct ON wallet_transaction (tenant_id, del_flag, dept_id, create_time DESC, user_id DESC, id DESC)',
|
||
'SELECT ''skip: idx_wallet_tx_tenant_del_dept_ct 已存在'' AS msg');
|
||
PREPARE stmt FROM @ddl;
|
||
EXECUTE stmt;
|
||
DEALLOCATE PREPARE stmt;
|
||
|
||
-- 4. 业务类型+金额前缀索引:page-cz / page-jz 固定带 biz_type IN ('1','2','3') + price>0 / price<0。
|
||
-- biz_type 为 tinytext 无法整列建 B-tree(只能前缀),price 也不在任何索引中,
|
||
-- 此前 COUNT 与翻页需对租户内全部行回表校验这两个字段;记账扣款(price<0)仅约 0.5% 行且偏旧,
|
||
-- page-jz 首页还要按时间倒序翻过大量支付流水。
|
||
-- 本索引把 jz 的 COUNT/翻页访问行数从全租户降到 ~0.5%(测试库 40195→443),cz 同理;
|
||
-- 已验证 page-all 仍走 5 列排序索引、page-xf(biz_type='4') 计划不变,无回归。
|
||
-- 注:前缀索引仍需回表核对 biz_type 完整值,线上行数放大后 COUNT 预计降至 1s 内。
|
||
-- ⚠️ 已知代价(2026-10-04 线上实测):本索引会使优化器把 page-jz 的翻页查询切换为
|
||
-- "全量命中集回表+filesort"(无提前终止),jz 从 6s 恶化到 10s;cz/xf 等不受影响。
|
||
-- 根因:biz_type 为 tinytext 只能建前缀索引,前缀列的条件不会下推到索引层(ICP),
|
||
-- 也永远无法构成覆盖索引,多值 IN(1,2,3) 又无法沿用 biz 前导索引的顺序。
|
||
-- 纯索引手段无法同时优化 jz 的 COUNT 与翻页;根治需改代码(UNION 逐 biz_type 等值分支
|
||
-- + FORCE INDEX(idx_wallet_tx_biz_ct))或授权将 biz_type 改为 varchar 后建覆盖索引。
|
||
SET @ddl = IF(
|
||
(SELECT COUNT(*) FROM information_schema.statistics
|
||
WHERE table_schema = DATABASE() AND table_name = 'wallet_transaction'
|
||
AND index_name = 'idx_wallet_tx_tenant_del_biz_price') = 0,
|
||
'CREATE INDEX idx_wallet_tx_tenant_del_biz_price ON wallet_transaction (tenant_id, del_flag, biz_type(2), price)',
|
||
'SELECT ''skip: idx_wallet_tx_tenant_del_biz_price 已存在'' AS msg');
|
||
PREPARE stmt FROM @ddl;
|
||
EXECUTE stmt;
|
||
DEALLOCATE PREPARE stmt;
|
||
|
||
-- 5. biz 前导有序索引:让单值 biz_type 等值/单元素 IN 的翻页(page-tk 退款/page-th 退还等)
|
||
-- 自动走"biz 前缀定位 + 索引序 + price ICP 过滤",跳过无关业务类型的回表;
|
||
-- 同时为 page-jz/page-cz 的 UNION 改写(代码修复)预置索引。
|
||
-- 注意:本索引对 jz/cz(多值 IN)的翻页无直接改善,仅建议需要提速 tk/th 或配合代码修复时执行。
|
||
SET @ddl = IF(
|
||
(SELECT COUNT(*) FROM information_schema.statistics
|
||
WHERE table_schema = DATABASE() AND table_name = 'wallet_transaction'
|
||
AND index_name = 'idx_wallet_tx_biz_ct') = 0,
|
||
'CREATE INDEX idx_wallet_tx_biz_ct ON wallet_transaction (tenant_id, del_flag, biz_type(2), create_time DESC, user_id DESC, id DESC, price)',
|
||
'SELECT ''skip: idx_wallet_tx_biz_ct 已存在'' AS msg');
|
||
PREPARE stmt FROM @ddl;
|
||
EXECUTE stmt;
|
||
DEALLOCATE PREPARE stmt;
|