医数智联·第 13 期:SQL 性能优化 —— 让首页查询从 30 秒到 0.5 秒,EXPLAIN + 索引 + 重写
医数智联·第 13 期:SQL 性能优化 —— 让首页查询从 30 秒到 0.5 秒,EXPLAIN + 索引 + 重写
[!ABSTRACT] 核心摘要
- 本期主题:SQL 性能优化——同样的查询,从 30 秒降到 0.5 秒
- 核心结论:90% 的慢 SQL 都是「全表扫描」,只要找到「扫描原因」就能提速
- 3 大核心工具:EXPLAIN(看执行计划)+ 索引(加速查询)+ 查询重写(避免反模式)
- 5 大实战:单列索引、联合索引、覆盖索引、避免
SELECT *、EXPLAIN 解读- 典型效果:从「30 秒超时」到「0.5 秒返回」
- 阅读时长:约 10 分钟
一、场景引入:30 秒的 SQL 是怎样的体验?
[!example] 一个真实的周三上午
院长:「上月骨科主诊错误率 Top 10 医生,马上给我。」质管办小张跑 SQL:
1
2
3
4
5
6
7
8 SELECT a.责任医生, COUNT(*) AS 错误数
FROM 首页主表 a
LEFT JOIN 医生表 b ON a.责任医生工号 = b.医生工号
WHERE a.主诊错误标记 = 1
AND a.出院日期 >= '2026-06-01'
GROUP BY a.责任医生
ORDER BY 错误数 DESC
LIMIT 10;跑了 30 秒没结果,Navicat 提示「查询超时」。
小张心急如焚,又不敢重启。
信息科同事路过,扫了一眼他的 SQL:
「你这表没建索引,where 里的字段全表扫描,500 万行当然慢。」
加了一个索引:
1 CREATE INDEX idx_出院日期_错误 ON 首页主表 (出院日期, 主诊错误标记);0.5 秒返回结果。
这是医院慢 SQL 的典型故事。
[!quote] 狼叔的判断
“慢 SQL 不是程序员的锅,是’不会看 EXPLAIN’的代价。” 这一期,带你掌握 3 个核心技能——EXPLAIN、索引、查询重写,让 30 秒变 0.5 秒。
二、工具原理:慢 SQL 的 3 大「罪魁祸首」
2.1 罪魁祸首 ①:全表扫描(Full Table Scan)
[!info] 全表扫描是什么?
数据库在执行查询时,逐行扫描整张表,每行都判断是否符合 WHERE 条件。对于 500 万行的表,全表扫描要 30 秒+。
判断方法:EXPLAIN 结果里看到
type = ALL(MySQL) 或Seq Scan(PostgreSQL)。
2.2 罪魁祸首 ②:索引失效
[!info] 索引失效是什么?
表上有索引,但查询写法让数据库用不上索引,退化成全表扫描。常见原因:
- 对索引列做函数运算:
WHERE YEAR(出院日期) = 2026- 对索引列做类型转换:
WHERE 病案号 = 123(病案号是 VARCHAR)- 用
!=或NOT IN:WHERE 状态 != '已处理'- 用
LIKE '%xxx%'(前导模糊):WHERE 主诉 LIKE '%糖尿病%'- 复合索引不遵守最左前缀(详见 3.2)
2.3 罪魁祸首 ③:数据量过大
[!info] 数据量过大的表现
- 单表超过 1000 万行(即使有索引,也会变慢)
- JOIN 超过 3 张表(笛卡尔积爆炸)
SELECT *(返回不必要的列)- 排序字段无索引(ORDER BY 全表排序)
三、医院实战:5 大优化技巧
3.1 技巧一:EXPLAIN 看执行计划
[!example] 实战 ①:EXPLAIN 分析
EXPLAIN 是 SQL 优化的「听诊器」,告诉你数据库是怎么执行这条 SQL 的。
1 | -- MySQL 写法 |
MySQL EXPLAIN 输出关键字段:
| 字段 | 含义 | 优化目标 |
|---|---|---|
type |
连接类型 | const > eq_ref > ref > range > index > ALL(全表扫描,要避免) |
key |
实际使用的索引 | NULL = 没用到索引 |
rows |
扫描行数(估算) | 越小越好 |
Extra |
额外信息 | 看到 Using filesort / Using temporary 通常要优化 |
PostgreSQL EXPLAIN 输出关键字段:
| 字段 | 含义 | 优化目标 |
|---|---|---|
Seq Scan |
全表扫描 | 改为 Index Scan |
Index Scan |
索引扫描 | 理想 |
Bitmap Index Scan |
位图索引扫描 | 多条件时常用 |
Sort |
排序 | 尽量用索引排序 |
Hash Join / Nested Loop |
连接方式 | 小表用 Nested Loop |
3.2 技巧二:单列索引 vs 联合索引
[!example] 实战 ②:索引类型选择
3.2.1 单列索引
1 | -- 适合:查询只用一个条件 |
3.2.2 联合索引(复合索引)
1 | -- 适合:查询同时用多个条件 |
3.2.3 覆盖索引(包含 SELECT 字段)
1 | -- 如果查询只需要「责任医生」和「错误数」,索引直接包含这两个字段,不用回表 |
[!TIP] 索引选择 3 原则
- 高频查询的 WHERE 字段建索引
- 高频 JOIN 的关联字段建索引
- 不要建太多索引(每多一个索引,INSERT/UPDATE 变慢 5-10%)
3.3 技巧三:避免 SELECT *
[!example] 实战 ③:避免
SELECT *
1 | -- ❌ 错误:SELECT * 返回所有列,可能几 MB,慢 |
3.4 技巧四:重写慢 SQL
[!example] 实战 ④:重写慢 SQL 的 5 种模式
模式 ①:对索引列避免函数
1 | -- ❌ 慢:对索引列用函数,索引失效 |
模式 ②:对索引列避免类型转换
1 | -- ❌ 慢:病案号是 VARCHAR,传入数字,索引失效 |
模式 ③:用 EXISTS 替代 IN
1 | -- 慢:大数据量 IN 慢 |
模式 ④:用 JOIN 替代子查询
1 | -- 慢:相关子查询 |
模式 ⑤:小表驱动大表(Nested Loop)
1 | -- ✅ 正确:小表(医生表)驱动大表(首页主表) |
3.5 技巧五:分页查询优化
[!example] 实战 ⑤:深分页优化
场景:LIMIT 100000, 20—— 跳过 10 万行取 20 行,慢到爆。
1 | -- ❌ 慢:LIMIT 100000, 20 仍然要扫描前 10 万行 |
四、避坑指南:5 个让索引「失效」的常见错误
[!warning] 雷区 ①:用
!=/<>/NOT IN
症状:WHERE 状态 != '已处理'全表扫描。
解决:用=或IN;或者考虑WHERE 状态 IN ('待处理', '处理中')。
[!warning] 雷区 ②:
LIKE '%关键词%'(前导模糊)
症状:WHERE 主诉 LIKE '%糖尿病%'全表扫描。
解决:用后置模糊:WHERE 主诉 LIKE '糖尿病%'(走索引)。
[!warning] 雷区 ③:对索引列做运算
症状:WHERE DATE(出院日期) = '2026-06-15'索引失效。
解决:用范围查询:WHERE 出院日期 >= '2026-06-15' AND 出院日期 < '2026-06-16'。
[!warning] 雷区 ④:联合索引不遵守最左前缀
症状:联合索引(科室, 出院日期),查询WHERE 出院日期 = '2026-06-15'不走索引。
解决:查询条件顺序要和索引一致,或者调整索引顺序。
[!warning] 雷区 ⑤:
OR让索引失效
症状:WHERE 科室 = '骨科' OR 主诊ICD = 'I10'全表扫描。
解决:用UNION ALL:
1
2
3 SELECT * FROM 首页主表 WHERE 科室 = '骨科'
UNION ALL
SELECT * FROM 首页主表 WHERE 主诊ICD = 'I10';
五、SQL 性能优化清单(实战模板)
[!TIP] 上线前的 5 步自查
Step 1:用 EXPLAIN 看执行计划
- 是否有
ALL/Seq Scan?rows估算是否过大?Step 2:检查 WHERE 字段是否建索引
- 高频查询字段有没有索引?
- 联合索引顺序是否正确?
Step 3:检查是否有
SELECT */ 函数 / 类型转换Step 4:检查表数据量
- 超过 1000 万行?考虑分区表或归档
Step 5:跑一遍实际耗时
- 5 秒内:可接受
- 5-30 秒:需优化
30 秒:必须优化
六、模块总结:数据库 7 期学完了,你获得了什么?
[!ABSTRACT] 数据库模块 7 期回顾
期号 主题 核心技能 07 MySQL 基础 10 个 SQL:SELECT/WHERE/GROUP BY/HAVING/LIMIT/INSERT/UPDATE/DELETE 08 MySQL 进阶 JOIN/子查询/窗口函数,5 大常用查询 09 首页主诊错误率 完整分析 5 步流程 10 DRG 入组异常 5 类异常筛查 SQL 11 PostgreSQL 入门 JSONB + 全文检索 + GIS 12 PostgreSQL 进阶 GIN 索引 + zhparser + PostGIS 13 SQL 性能优化 EXPLAIN + 索引 + 查询重写 学完这 7 期,你能独立完成:
- 80% 的医院日常数据查询
- 首页质控、DRG 入组的完整 SQL 报告
- 用 EXPLAIN 排查慢 SQL
- PostgreSQL 科研病历库搭建
下个模块预告:Python 自动化(共 8 期,见开篇规划)
[!TIP] 互动
如果你觉得这篇「医数智联·第 13 期」对你有用,请点赞、在看、转发给科里的同事。文中 EXPLAIN 解读手册 + 索引设计清单 + SQL 重写模板,我已经整理成「SQL 性能优化工具包」。点击「阅读原文」可直接下载。
我是白衣狼,一个用数字化工具把医院管理做「轻」的实战派。咱们下期见。
[!quote] 数据库模块结语
“SQL 是医院数据的’普通话’——会 SQL,你就能和任何医院系统’对话’。”MySQL + PostgreSQL 双栈并存,既能跑业务(HIS 用 MySQL),又能做科研(PostgreSQL),这就是 2026 年医院数据人的标配。
下一模块 Python,我们把 SQL 的结果「自动化」起来——自动报表、自动校验、自动发邮件。敬请期待。





