SQL执行计划与优化详解

一、什么是SQL执行计划

执行计划是数据库优化器生成的、用于执行SQL查询的详细步骤说明。它描述了:

  • 如何访问数据(全表扫描、索引扫描等)
  • 表的连接顺序和方式
  • 操作执行的顺序

二、如何查看执行计划

1. MySQL

sql 复制代码
-- 基本执行计划
EXPLAIN SELECT * FROM users WHERE age > 25;

-- 详细执行计划(MySQL 8.0+)
EXPLAIN ANALYZE SELECT * FROM users WHERE age > 25;

-- JSON格式查看
EXPLAIN FORMAT=JSON SELECT * FROM users;

2. PostgreSQL

sql 复制代码
-- 基本计划
EXPLAIN SELECT * FROM users WHERE age > 25;

-- 带实际执行时间
EXPLAIN ANALYZE SELECT * FROM users WHERE age > 25;

-- 带统计信息
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM users;

3. Oracle

sql 复制代码
-- 生成执行计划
EXPLAIN PLAN FOR 
SELECT * FROM users WHERE age > 25;

-- 查看执行计划
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

4. SQL Server

sql 复制代码
-- 文本格式
SET SHOWPLAN_TEXT ON;
GO
SELECT * FROM users WHERE age > 25;
GO
SET SHOWPLAN_TEXT OFF;

-- 图形化(SSMS中)
-- 点击"显示估计的执行计划"按钮或Ctrl+L

三、执行计划关键元素解读

1. 访问方法(Access Methods)

复制代码
- Seq Scan/Full Table Scan: 全表扫描
- Index Scan: 索引扫描
- Index Only Scan: 仅索引扫描
- Bitmap Index Scan: 位图索引扫描
- Hash Scan: 哈希扫描

2. 连接类型(Join Types)

复制代码
- Nested Loop Join: 嵌套循环连接
- Hash Join: 哈希连接
- Merge Join: 合并连接

3. 关键指标

复制代码
- cost: 预估成本(越低越好)
- rows: 预估返回行数
- width: 预估行宽度
- actual time: 实际执行时间
- loops: 循环次数

四、执行计划优化实战

示例分析:识别问题

sql 复制代码
-- 问题查询
EXPLAIN ANALYZE
SELECT u.name, o.order_date, p.product_name
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN products p ON o.product_id = p.id
WHERE u.city = '北京'
AND o.order_date >= '2024-01-01'
ORDER BY o.order_date DESC
LIMIT 100;

优化步骤:

1. 识别全表扫描
sql 复制代码
-- 检查是否缺少索引
EXPLAIN SELECT * FROM users WHERE city = '北京';
-- 如果显示"Seq Scan",考虑创建索引
CREATE INDEX idx_users_city ON users(city);
2. 优化连接顺序
sql 复制代码
-- 查看连接顺序是否合理
-- 小表驱动大表更高效
-- 使用子查询优化
SELECT * FROM (
  SELECT * FROM orders 
  WHERE order_date >= '2024-01-01'
) o
JOIN users u ON o.user_id = u.id
WHERE u.city = '北京';
3. 索引优化组合
sql 复制代码
-- 复合索引(最左前缀原则)
CREATE INDEX idx_orders_user_date ON orders(user_id, order_date DESC);

-- 覆盖索引(包含查询所有列)
CREATE INDEX idx_users_city_name ON users(city) INCLUDE (name);

-- 函数索引
CREATE INDEX idx_lower_email ON users(LOWER(email));
4. 统计信息更新
sql 复制代码
-- 确保统计信息准确
ANALYZE users;  -- PostgreSQL
ANALYZE TABLE users;  -- MySQL
EXEC sp_updatestats;  -- SQL Server

五、常见优化模式

模式1:N+1查询问题

sql 复制代码
-- 反模式:多次查询
SELECT * FROM users WHERE city = '北京';
-- 对每个user执行:
SELECT * FROM orders WHERE user_id = ?;

-- 优化:使用JOIN
SELECT u.*, o.* 
FROM users u 
LEFT JOIN orders o ON u.id = o.user_id 
WHERE u.city = '北京';

模式2:分页优化

sql 复制代码
-- 低效的分页(OFFSET越大越慢)
SELECT * FROM orders ORDER BY id LIMIT 10 OFFSET 1000000;

-- 优化:使用seek方法
SELECT * FROM orders 
WHERE id > last_seen_id 
ORDER BY id 
LIMIT 10;

模式3:避免SELECT *

sql 复制代码
-- 不推荐
SELECT * FROM users WHERE age > 25;

-- 推荐:只选择需要的列
SELECT id, name, email FROM users WHERE age > 25;

六、优化工具和技巧

1. 数据库内置工具

sql 复制代码
-- PostgreSQL: 检查索引使用情况
SELECT * FROM pg_stat_user_indexes;

-- MySQL: 查看索引统计
SHOW INDEX FROM users;

-- MySQL: 性能分析
SET profiling = 1;
SELECT ...;
SHOW PROFILES;
SHOW PROFILE FOR QUERY 1;

2. 查询重写技巧

sql 复制代码
-- OR优化
SELECT * FROM users WHERE age < 20 OR age > 60;
-- 重写为
SELECT * FROM users WHERE age < 20
UNION
SELECT * FROM users WHERE age > 60;

-- IN优化
SELECT * FROM users WHERE id IN (SELECT user_id FROM orders);
-- 重写为
SELECT u.* FROM users u 
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);

3. 配置优化

sql 复制代码
-- 调整内存设置
-- PostgreSQL: work_mem, shared_buffers
-- MySQL: innodb_buffer_pool_size, sort_buffer_size

-- 启用并行查询
-- PostgreSQL: max_parallel_workers_per_gather
-- MySQL 8.0+: optimizer_switch='parallel_query=on'

七、执行计划可视化工具

  1. pgAdmin / DBeaver:内置可视化执行计划
  2. Explain.dalibo.com:PostgreSQL执行计划可视化
  3. MySQL Workbench:Visual Explain功能
  4. Oracle SQL Developer:执行计划图形化

最佳实践总结

  1. 先看执行计划再优化:不要盲目添加索引

  2. 关注高成本操作:全表扫描、排序、临时表

  3. 索引设计原则

    • 为WHERE、JOIN、ORDER BY列创建索引
    • 使用复合索引减少索引数量
    • 避免过度索引影响写入性能
  4. 定期分析

    sql 复制代码
    -- 找出最慢的查询
    SELECT query, total_time 
    FROM pg_stat_statements 
    ORDER BY total_time DESC 
    LIMIT 10;
  5. 测试验证:在生产环境优化前,先在测试环境验证

相关推荐
乌鸦乌鸦你的小虎牙1 小时前
qt 5.12.8 配置报错(交叉编译环境)
开发语言·数据库·qt
一只大袋鼠2 小时前
Redis 安装+基于短信验证码登录功能的完整实现
java·开发语言·数据库·redis·缓存·学习笔记
Anastasiozzzz2 小时前
深入研究Redis的ZSet底层数据结构:从 Ziplist 的级联更新到 Listpack 的完美救场
数据结构·数据库·redis
菠萝蚊鸭2 小时前
x86 平台使用 buildx 基于源码构建 MySQL Wsrep 5.7.44 镜像
数据库·mysql·galera·wsrep
沙漏无语4 小时前
(二)TIDB搭建正式集群
linux·数据库·tidb
姚不倒4 小时前
三节点 TiDB 集群部署与负载均衡搭建实战
运维·数据库·分布式·负载均衡·tidb
隔壁小邓4 小时前
批量更新方式与对比
数据库
数据知道4 小时前
MongoDB复制集架构原理:Primary、Secondary 与 Arbiter 的角色分工
数据库·mongodb·架构
人道领域4 小时前
苍穹外卖:菜品新增功能全流程解析
数据库·后端·状态模式
修行者Java4 小时前
(七)从 “非结构化数据难存储” 到 “MongoDB 灵活赋能”——MongoDB 实战进阶指南
数据库·mongodb