SuperAPI AI 工具集

query-review

数据与分析

查看项目

更新于 10-16 10:19

技能介绍

SQL 审查与查询优化(Query Review)

参考来源:Markus Winand《SQL Performance Explained》、Bruce Momjian《Mastering PostgreSQL》、High Performance MySQL(O'Reilly)、PostgreSQL 优化器文档、MySQL 8.0 优化器文档。

适用场景

审查后端提交的 SQL 或 ORM 查询;分析慢查询、超时、CPU/IO 异常;识别 N+1、全表扫描、低效 join、低效分页;优化聚合、排序、过滤、模糊搜索;为新增索引或 schema 调整提供证据。

核心原则

  1. 先定位真实瓶颈,再改 SQL 或索引。不看执行计划就加索引等于瞎猜。
  2. EXPLAIN ANALYZE 是真相,优化器估算可能错,实际执行才算。
  3. 看调用频率 × 单次成本:100 次/天 × 10 秒 vs 100000 次/天 × 100 毫秒,后者总成本是前者的 30 倍。
  4. 区分查询重写 vs 索引变更:能改 SQL 不动索引优先;只能加索引时评估写入成本。
  5. 关注 actual rows vs estimated rows,差异 10x+ 说明统计信息可能过期,需 ANALYZE。
  6. ORM 是黑盒,要看真 SQL,开 SQL 日志或用 sqlcommenter。

审查输入

SQL 或 ORM 查询、表结构和索引、参数样例(实际生产值)、数据量级、调用路径和频率、执行计划(EXPLAIN ANALYZE)、慢日志样本、性能目标(P99)。

EXPLAIN 解读速查

PostgreSQL EXPLAIN ANALYZE 关键节点(从下往上读):Seq Scan(全表扫描,小表 OK,大表是问题)、Index Scan(用上索引)、Filter(过滤但未用索引)、Rows Removed by Filter、Bitmap Heap Scan、Sort(内存排序,大数据是问题)、Sort Method: external merge(磁盘排序更慢)、Hash Join、Nested Loop。

MySQL EXPLAIN 关键字段:type=ALL(全表扫描最差)、index、range、ref、eq_ref、const(最好);key 表示用了哪个索引;rows 为估算扫描行数;filtered 为过滤剩余比例;Extra=Using temporary(临时表)、Using filesort(文件排序)、Using index(覆盖索引,好)、Using where(索引取出后还要过滤)。

常见问题诊断库

  1. 全表扫描:症状为 Seq Scan / type=ALL,actual rows 接近表总行数。原因包括缺索引、索引被函数包裹(WHERE LOWER(email) = ?)、类型隐式转换(WHERE varchar_col = 123)、优化器选错(统计信息过期)。解决方式包括表达式索引、类型对齐加引号、ANALYZE 更新统计信息。

  2. N+1 查询:症状为 1 个主查询 + N 个子查询(ORM 懒加载)。解决方式包括 Django 的 select_related / prefetch_related、SQLAlchemy 的 joinedload、Prisma 的 include,或直接写 JOIN SQL。

  3. OFFSET 深分页:症状为 OFFSET 100000 LIMIT 20 越来越慢,原因是 OFFSET 仍要扫前 100000 行。解决方式为 cursor 分页,用上次最后一条的 cursor 条件。

  4. ORDER BY 文件排序:症状为 Sort Method: external merge / Using filesort,原因是排序字段无索引或不在索引末尾。解决方式为建立排序友好索引,如 (user_id, created_at DESC)。

  5. LIKE '%xxx%' 全表扫:PostgreSQL 用 GIN trigram(pg_trgm 扩展),MySQL 用 FULLTEXT。

  6. COUNT() 慢:大表 SELECT COUNT() 数秒,原因是 MVCC 模型必须扫表。解决方式为估算(PostgreSQL 从 pg_class 读 reltuples)或物化(维护单独计数表)。

  7. JSON 字段查询慢:metadata->>'status' = 'paid' 全表扫,解决方式为表达式索引或 GIN 索引。

  8. 大事务锁等待:单条 SQL 不慢但等待时间长,诊断用 pg_locks / pg_stat_activity 或 performance_schema,解决方式为缩小事务范围、避免长事务。

标准流程

  1. 明确业务场景和性能目标(SLA:P99 < ? ms;频率:QPS = ?)。
  2. 收集 SQL、schema、索引、数据量、执行计划(真实参数、EXPLAIN ANALYZE 输出、调用频率)。
  3. 判断瓶颈(扫描、排序、join、锁、N+1、网络往返)。
  4. 给出查询重写或索引建议(改 SQL 优先,加索引评估写入成本,改 schema 极少)。
  5. 验证(重跑 EXPLAIN ANALYZE 对比、监控生产指标改善、评估写入退化)。

输出格式

包含问题、证据(EXPLAIN)、频率、单次耗时、建议、验证方式、风险。

ORM 陷阱速查

Django:反模式为 lazy load 导致 N+1,解决用 select_related;count() 慢可用 explain() 看真实查询。SQLAlchemy:反模式为 lazy load,解决用 joinedload。Prisma:反模式为 findMany 不 include,解决用 include。

配套模板

templates/sql-review-template.md — SQL 审查报告(场景 / 当前 SQL / 执行计划 / 问题 / 建议 / 验证 / 风险)。

质量自检

有真实 SQL 或 ORM 查询;说明数据量和调用频率;基于 EXPLAIN ANALYZE 而非猜测;区分查询重写 vs 索引变更;评估分页、排序、join、聚合成本;看 actual rows vs estimated rows;检查 N+1 模式;OFFSET 深分页改 cursor;文件排序加排序友好索引;JSON 字段查询用表达式索引或 GIN;模糊匹配用 GIN trigram / FULLTEXT;给出可验证指标;评估写入退化。

常见坑

没 EXPLAIN 就加索引;只优化单次耗时忽略高频调用总成本;OFFSET 深分页;ORM 懒加载 N+1;聚合查询和在线业务混链路;统计信息过期;隐式类型转换;函数包裹索引字段;OR 多条件可能退化为全表扫;NOT IN 大子查询改 NOT EXISTS;COUNT(*) 大表;不限制 LIMIT;SELECT *;DISTINCT 大数据。

与其他 skill 的协作

上游:schema-design(表结构和约束)、index-access-pattern(索引设计)、api-designer(查询模式 pagination / filter)。下游:index-access-pattern(根据证据设计索引)、schema-design(必要时调整字段或表结构)、migration-rollout(涉及结构或索引变更时设计上线方案)、backend-engineer(改 ORM 写法)。

相关参考

references/query-optimization-guide.md — 优化器内部、统计信息、Join 算法(NL/Hash/Merge)、ORM 陷阱完整库、慢日志分析。