SQL查询优化 sql-query-optimization

这个技能用于优化SQL查询性能,提升数据库效率,包括索引策略、查询计划分析、解决慢查询和N+1问题等。关键词:SQL优化、数据库性能、索引创建、EXPLAIN分析、慢查询解决、MySQL优化、PostgreSQL性能、查询调优。

后端开发 0 次安装 0 次浏览 更新于 3/7/2026

name: sql-query-optimization description: SQL查询优化,针对PostgreSQL/MySQL,包括索引、EXPLAIN分析。用于慢查询、N+1问题、缺失索引、或遇到顺序扫描、OFFSET分页、临时表溢出、低效JOINs。

关键词:sql优化、查询性能、数据库索引、explain analyze、慢查询、n+1问题、 查询计划、索引策略、复合索引、覆盖索引、postgresql性能、mysql优化、 查询重写、预处理语句、连接池、顺序扫描、缺失索引、SELECT *、 LIMIT优化、子查询性能、分页、基于游标的分页、批量操作、 pg_stat_statements、全文搜索、位图扫描、索引仅扫描、查询调优、数据库性能、 缓存命中率、work_mem、shared_buffers、数据库监控 license: MIT

SQL查询优化

状态: 生产就绪 ✅ 最后更新: 2025-12-15 最新版本: PostgreSQL 17, MySQL 8.4 依赖项: 无


快速开始 (10分钟)

1. 识别慢查询

-- PostgreSQL: 启用 pg_stat_statements
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

-- 查找最慢的查询
SELECT query, mean_exec_time, calls
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;

2. 使用 EXPLAIN 分析

EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
SELECT * FROM orders WHERE user_id = 123;

-- 注意:
-- - 大表上的顺序扫描 → 需要索引
-- - 高“过滤移除的行” → 选择性差
-- - 临时读写 → 增加 work_mem

3. 创建索引

-- 添加缺失索引
CREATE INDEX CONCURRENTLY idx_orders_user
ON orders(user_id);

-- 验证改进
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders WHERE user_id = 123;
-- 执行时间应下降 10-100 倍

关键规则

总是要做 ✓

规则 原因 示例
索引外键 JOIN 需要索引列 CREATE INDEX idx_orders_user ON orders(user_id)
在生产前使用 EXPLAIN ANALYZE 验证查询计划最优 EXPLAIN (ANALYZE, BUFFERS) <query>
选择特定列 减少数据传输 90% SELECT id, name FROM users 而不是 SELECT *
为无限查询添加 LIMIT 防止内存耗尽 SELECT * FROM logs ORDER BY id LIMIT 100
使用预处理语句 防止 SQL 注入 + 更快 db.query('SELECT * FROM users WHERE id = $1', [id])
批量操作后运行 ANALYZE 更新查询计划器统计 ANALYZE table_name
监控 pg_stat_statements 跟踪查询性能随时间变化 每日审查回归
使用连接池 减少连接开销 10 倍 new Pool({ max: 20 })

永远不要做 ✗

反模式 问题 修复
生产中使用 SELECT * 获取不必要列 仅选择特定列
前导通配符 LIKE ‘%term%’ 无法使用索引 改用全文搜索
字符串拼接构建 SQL SQL 注入漏洞 使用参数化查询
大结果无 LIMIT 内存耗尽 始终添加 LIMIT + 分页
循环中的 N+1 查询 网络延迟 × N 使用 JOIN 或批量加载
忽略 EXPLAIN 输出 部署慢查询到生产 部署前始终 EXPLAIN
循环中多次 INSERT 批量操作慢 使用带多个 VALUES 的批量 INSERT
分页使用 OFFSET O(n) 时间,扫描跳过行 使用基于游标的分页

前 7 个关键错误

1. 大表上的顺序扫描

症状: Seq Scan on orders (cost=0.00..150000.00) 在 1M+ 行上 原因: 过滤列无索引 修复: CREATE INDEX idx_orders_column ON orders(column) 影响: 10-100 倍更快

2. 外键缺失索引

症状: 慢 JOINs (5+ 秒) 原因: 外键列未索引 修复: CREATE INDEX idx_orders_user_id ON orders(user_id) 影响: 50-500 倍更快 JOINs

3. N+1 查询问题

症状: 1 + N 查询用于 N 条记录 原因: ORM 循环中的延迟加载 修复: 使用 JOIN 或急切加载: SELECT u.*, o.* FROM users u LEFT JOIN orders o ON u.id = o.user_id 影响: N 查询 → 1 查询

4. 前导通配符 LIKE

症状: WHERE name LIKE '%search%' 顺序扫描 原因: 索引无法匹配字符串中间 修复: 使用全文搜索 (GIN 索引) 或 trigrams 影响: 100-1000 倍更快

5. 生产中使用 SELECT *

症状: 高网络流量,慢响应 原因: 获取所有 50 列而不是所需 3 列 修复: SELECT id, name, email (显式列列表) 影响: 数据传输减少 90%

6. 大结果缺失 LIMIT

症状: 服务器内存不足,查询超时 原因: 尝试返回 5M 行 修复: SELECT * FROM logs WHERE ... LIMIT 100 + 分页 影响: 恒定内存使用

7. 批量加载后统计过时

症状: 尽管有索引但选择错误查询计划 原因: PostgreSQL 统计过时 修复: ANALYZE table_name 批量操作后 影响: 正确查询计划选择

参见 references/error-catalog.md 获取所有 12 个错误及详细解决方案。


常见模式总结

模式 使用场景 示例 性能
B-树索引 相等、范围、排序查询 CREATE INDEX idx ON t(col) 默认,最佳通用用途
复合索引 多列 WHERE 子句 CREATE INDEX idx ON t(c1, c2) 比单索引快 5-50 倍
覆盖索引 包含所有查询列 CREATE INDEX idx ON t(c1) INCLUDE (c2) 快 2-10 倍 (无堆获取)
部分索引 过滤行子集 CREATE INDEX idx ON t(c) WHERE status='active' 索引小 50-90%
JOIN 重写 替换 IN 子查询 INNER JOIN users u ON o.user_id = u.id 比子查询快 5-20 倍
批量 INSERT 批量数据加载 INSERT INTO t VALUES (..),(..) 比单个快 10-100 倍
游标分页 大偏移性能 WHERE id > last_id LIMIT 100 恒定时间 vs O(n)

配置总结

PostgreSQL 配置

-- 增加 work_mem 用于复杂查询 (可重载 - 无需重启)
SET work_mem = '256MB';

-- 增加 shared_buffers 用于更好缓存 (RAM 的 25%)
ALTER SYSTEM SET shared_buffers = '8GB';

-- 重要:shared_buffers 需要完整 PostgreSQL 服务器重启!
-- 此设置不可通过 pg_reload_conf() 重载!
--
-- 应用 shared_buffers 更改:
-- 1. 停止 PostgreSQL:   sudo systemctl stop postgresql
-- 2. 启动 PostgreSQL:  sudo systemctl start postgresql
-- 或使用:              sudo systemctl restart postgresql
--
-- 验证更改生效:
-- SHOW shared_buffers;

-- 启用自动清理 (可重载 - 可使用 pg_reload_conf)
ALTER SYSTEM SET autovacuum = on;

-- 重载配置 (仅适用于不需要重启的参数)
-- 这不会重载 shared_buffers - 需要重启!
SELECT pg_reload_conf();

MySQL 配置

# my.cnf
[mysqld]
innodb_buffer_pool_size = 8G  # RAM 的 70%
max_connections = 500
slow_query_log = 1
long_query_time = 1

何时加载参考

性能分析:

  • 加载 references/explain-analysis.md 当: 阅读 EXPLAIN 输出、理解查询计划、分析缓冲区统计、比较 PostgreSQL vs MySQL EXPLAIN
  • 加载 references/performance-monitoring.md 当: 设置监控、跟踪慢查询随时间变化、监控缓存命中率、识别膨胀表

索引优化:

  • 加载 references/index-strategies.md 当: 选择索引类型 (B-树、GIN、GiST、Hash)、创建复合索引、确定列顺序、使用覆盖索引、实现部分索引、监控索引使用

查询优化:

  • 加载 references/query-rewrites.md 当: 重写慢查询、转换子查询为 JOINs、消除 N+1 查询、实现分页、优化 LIKE 查询、批量操作

系统化流程:

  • 加载 references/optimization-workflow.md 当: 遵循逐步优化流程、创建优化假设、测量改进、监控长期性能

错误解决:

  • 加载 references/error-catalog.md 当: 调试特定错误 (顺序扫描、缺失索引、N+1 查询等)、理解根本原因、实施已验证解决方案

使用捆绑资源

模板 (复制粘贴 SQL)

# EXPLAIN 查询模板
templates/explain-query.sql

# 索引创建模式
templates/index-examples.sql

# 查询重写示例
templates/query-rewrites.sql

# 监控查询
templates/monitoring-queries.sql

参考 (深度探讨)

# 综合指南
references/error-catalog.md              # 所有 12 个错误 + 解决方案
references/explain-analysis.md           # 阅读查询计划
references/index-strategies.md           # 索引类型 & 选择
references/query-rewrites.md             # 优化前后
references/performance-monitoring.md     # 长期监控
references/optimization-workflow.md      # 系统化流程

依赖项

PostgreSQL 扩展:

  • pg_stat_statements - 查询性能跟踪 (内置)
  • pg_trgm - Trigram 相似性搜索 (可选,用于模糊匹配)

MySQL:

  • performance_schema - 性能监控 (8.0+ 默认启用)

无需额外依赖项。


已知问题预防

问题 症状 预防
顺序扫描 1M+ 行上的顺序扫描 生产前索引过滤列
缺失 FK 索引 慢 JOINs 始终索引外键
N+1 查询 1+N 数据库调用 使用 JOIN 或急切加载
前导通配符 LIKE ‘%x%’ 慢 使用全文搜索 (GIN)
SELECT * 膨胀 高网络流量 选择特定列
无 LIMIT 内存耗尽 始终 LIMIT 无限查询
过时统计 错误查询计划 批量操作后 ANALYZE
错误索引顺序 索引存在但未使用 匹配查询模式
缺失复合索引 多个 WHERE 慢 创建复合索引
无连接池 高延迟 实现池化 (20-50 连接)
SQL 注入 安全漏洞 仅使用预处理语句
临时溢出 排序的磁盘 I/O 增加 work_mem

完整设置清单

生产部署:

  • [ ] 启用 pg_stat_statements 或 performance_schema
  • [ ] 索引所有外键列
  • [ ] 索引 WHERE、JOIN、ORDER BY 子句中的列
  • [ ] 用特定列替换 SELECT *
  • [ ] 为所有无限查询添加 LIMIT
  • [ ] 使用预处理语句 (参数化查询)
  • [ ] 实现连接池 (20-50 连接)
  • [ ] 配置 work_mem (每连接 256MB-1GB)
  • [ ] 配置 shared_buffers (PostgreSQL RAM 的 25%)
  • [ ] 启用慢查询日志 (阈值: 100-1000ms)
  • [ ] 在所有关键查询上运行 EXPLAIN ANALYZE
  • [ ] 设置 pg_stat_statements 的每日监控
  • [ ] 夜间批量操作后调度 ANALYZE
  • [ ] 监控缓存命中率 (目标: >99%)
  • [ ] 每月审查并删除未使用索引

生产示例

优化前:

-- 查询: 获取用户订单
SELECT * FROM orders WHERE user_id = 123;

-- 性能:
-- 执行时间: 2500ms
-- 顺序扫描 orders (1M 行扫描)
-- 网络: 50MB 传输
-- user_id 无索引

优化后:

-- 添加索引
CREATE INDEX CONCURRENTLY idx_orders_user_id ON orders(user_id);

-- 优化查询
SELECT id, total, status, created_at
FROM orders
WHERE user_id = 123
LIMIT 100;

-- 性能:
-- 执行时间: 12ms (快 208 倍!)
-- 使用 idx_orders_user_id 的索引扫描 (100 行)
-- 网络: 50KB 传输 (少 1000 倍!)
-- 带 INCLUDE 的覆盖索引

结果: 快 208 倍的执行,少 1000 倍的数据传输


对于综合优化指导、错误解决和生产模式,加载上面“何时加载参考”中列出的适当参考文件。