liusheng
5 天以前 fb89df539c6293a41933e445df25fa9b8047ba18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
-- ============================================================================
-- /smartor/serviceSubtask/getSfStatisticsCountDetails 慢查询优化(南华附一 nhfy)
--                                                          2026-11-02
--
-- 【现象】满意度统计页点“查看详情”要等十几秒到二十几秒。
--   请求:{ "configKey":"joyCount", "leavehospitaldistrictcodes":["40003514"],
--           "serviceType":[6], "pageNum":1, "pageSize":20 }
--
-- 【这条接口实际发了 5 条 SQL】
--   ServiceSubtaskServiceImpl#getSfStatisticsScriptDetails 在 type 为空时会跑两趟:
--     type=1(语音)  → getSfStatisticsScriptDetails + getSfStatisticsScriptItemDetails
--     type=2(问卷)  → getSfStatisticsScriptDetails + getSfStatisticsScriptItemDetails
--   controller 上还有 PageUtils.startPageByPost(...),PageHelper 会对第一条再补一次 COUNT。
--   每一条都在 service_subtask_detail(38 万行)上做「子任务 JOIN 题目明细 GROUP BY」。
--
-- 【根因:谓词把列包在函数里,索引全废】
--   为了「出院病区为空回退到在院病区」,条件写成了
--       AND IFNULL(ss.leavehospitaldistrictcode, ss.hospitaldistrictcode) IN ('40003514')
--   列被 IFNULL 包住 → 优化器用不了任何索引,只能 orgid='20001001' 的 27864 条子任务全扫,
--   每条再去 detail 表按 sub_id 捞 11 行(≈30 万行随机读)。
--   EXPLAIN 实测:ss 走 idx_service_subtask_orgid_visit_time,rows=27864。
--
-- 【改法 1:谓词展开成等价的 OR,让索引可用】
--       AND (ss.leavehospitaldistrictcode IN ('40003514')
--            OR (ss.leavehospitaldistrictcode IS NULL AND ss.hospitaldistrictcode IN ('40003514')))
--   语义与 IFNULL 完全等价(4 个病区逐一比对明细行数:8873/95/4307/8550 全部一致)。
--   只做这一步(不加索引):5.1s → 1.9s,执行计划变成 ref_or_null,rows 27864 → 3644。
--
-- 【改法 2:补两个索引,把范围再收窄】
--   明细页固定按 (病区, service_type, type) 过滤,原来没有任何索引能覆盖这个组合
--   (service_subtask 上只有单列 index,或 visit_time 打头的复合索引)。
--
--   离院病区(含空值回退)→ idx_ss_district_pair
--   在院病区             → idx_ss_hosp_dist_svc_type
--
-- 【实测(192.168.99.192 / smartor_nhfy,同一条件下的单条 SQL)】
--   离院病区 40003514:5120ms → 1593ms(冷) / 728ms(热)
--   在院病区 40003514:4750ms → 1854ms
--   明细选项统计      :6183ms → 629~1104ms
--   一次调用 5 条 SQL 合计:≈23s → ≈2s
-- ============================================================================
 
 
-- ----------------------------------------------------------------------------
-- 【一】DDL(每个环境都要单独执行;MySQL 8 支持 Online DDL,不会锁表)
-- ----------------------------------------------------------------------------
-- ALTER TABLE service_subtask
--     ADD INDEX idx_ss_district_pair
--         (leavehospitaldistrictcode, hospitaldistrictcode, service_type, type, del_flag),
--     ADD INDEX idx_ss_hosp_dist_svc_type
--         (hospitaldistrictcode, service_type, type, del_flag);
 
-- 单独执行写法(已在 192.168.99.192 执行过,两条各 5~8 秒):
-- CREATE INDEX idx_ss_district_pair      ON service_subtask (leavehospitaldistrictcode, hospitaldistrictcode, service_type, type, del_flag);
-- CREATE INDEX idx_ss_hosp_dist_svc_type ON service_subtask (hospitaldistrictcode, service_type, type, del_flag);
 
-- 回滚:
-- DROP INDEX idx_ss_district_pair      ON service_subtask;
-- DROP INDEX idx_ss_hosp_dist_svc_type ON service_subtask;
 
 
-- ----------------------------------------------------------------------------
-- 【二】验证执行计划(执行 DDL 后应看到 key = idx_ss_district_pair / idx_ss_hosp_dist_svc_type)
-- ----------------------------------------------------------------------------
-- EXPLAIN
-- SELECT ssd.scriptid, COUNT(*) AS all_quantity
-- FROM service_subtask_detail ssd
-- INNER JOIN service_subtask ss ON ssd.sub_id = ss.id
-- WHERE ss.del_flag = 0 AND ssd.del_flag = 0
--   AND ss.orgid = '20001001' AND ss.campusid = '30001002'
--   AND (ss.leavehospitaldistrictcode IN ('40003514')
--        OR (ss.leavehospitaldistrictcode IS NULL AND ss.hospitaldistrictcode IN ('40003514')))
--   AND ss.service_type IN (6) AND ss.type = '2'
--   AND ssd.categoryid IN (62,1000000012,5,1000000015,465)
-- GROUP BY ssd.scriptid;
 
 
-- ----------------------------------------------------------------------------
-- 【三】等价性自检:换写法前后每个病区的明细行数必须一模一样
-- ----------------------------------------------------------------------------
-- SELECT w.code,
--        (SELECT COUNT(*) FROM service_subtask ss JOIN service_subtask_detail ssd ON ssd.sub_id = ss.id
--         WHERE ss.del_flag=0 AND ssd.del_flag=0
--           AND ss.orgid='20001001' AND ss.campusid='30001002'
--           AND ss.service_type IN (6) AND ss.type='2'
--           AND IFNULL(ss.leavehospitaldistrictcode, ss.hospitaldistrictcode) IN (w.code))     AS ifnull_rows,
--        (SELECT COUNT(*) FROM service_subtask ss JOIN service_subtask_detail ssd ON ssd.sub_id = ss.id
--         WHERE ss.del_flag=0 AND ssd.del_flag=0
--           AND ss.orgid='20001001' AND ss.campusid='30001002'
--           AND ss.service_type IN (6) AND ss.type='2'
--           AND (ss.leavehospitaldistrictcode IN (w.code)
--                OR (ss.leavehospitaldistrictcode IS NULL AND ss.hospitaldistrictcode IN (w.code)))) AS or_rows
-- FROM (SELECT '40003514' AS code UNION ALL SELECT '40003309'
--       UNION ALL SELECT '40011024' UNION ALL SELECT '40003317') w;
-- 实测:8873/8873、95/95、4307/4307、8550/8550 —— 完全一致。
 
 
-- ----------------------------------------------------------------------------
-- 【四】还没解决的两点(需要业务/前端配合,本次未动)
-- ----------------------------------------------------------------------------
-- 1) type 为空时固定跑两趟(语音 + 问卷)。南华满意度只可能是问卷(type=2),
--    语音那一趟恒为 0 行。现在有索引后它只要 ~120ms,可以接受;
--    若要彻底省掉,让前端明确传 type,或后端按 serviceType 推断。
-- 2) PageHelper 的 COUNT 会对同一条重查询再跑一遍。这个结果集只有 19 行,
--    其实不需要分页。要省就得去掉 startPageByPost,接口返回结构会变,先不动。