一、什么是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'
七、执行计划可视化工具
- pgAdmin / DBeaver:内置可视化执行计划
- Explain.dalibo.com:PostgreSQL执行计划可视化
- MySQL Workbench:Visual Explain功能
- Oracle SQL Developer:执行计划图形化
最佳实践总结
-
先看执行计划再优化:不要盲目添加索引
-
关注高成本操作:全表扫描、排序、临时表
-
索引设计原则 :
- 为WHERE、JOIN、ORDER BY列创建索引
- 使用复合索引减少索引数量
- 避免过度索引影响写入性能
-
定期分析 :
sql-- 找出最慢的查询 SELECT query, total_time FROM pg_stat_statements ORDER BY total_time DESC LIMIT 10; -
测试验证:在生产环境优化前,先在测试环境验证