目录
- 一、执行计划是啥?为啥要控制它?🤔
- 1.1 执行计划就像导航路线
- 1.2 不管控执行计划的后果
- 二、操控执行计划的「三板斧」🪓
- 2.1 第一招:HINT大法(直接下指令)
- 2.2 第二招:暗度陈仓(间接引导)
- 方法1:统计信息大法
- 方法2:虚拟列黑科技
- 方法3:分区表妙用
- 2.3 第三招:计划冻结术(一劳永逸)
- SQL Profile vs Baseline 对比表
- 三、实战演练:从青铜到王者👑
- 案例:分页查询优化(5秒 → 0.1秒)
- 四、课后加油站⛽
- 练习题(答案见文末)
- 学习建议
- 资源推荐
- 五、文末福利 🎁
摘要:你是不是经常遇到SQL跑得比蜗牛还慢?改SQL改到怀疑人生?别慌!今天带你用“上帝视角”操控执行计划,让SQL性能起飞!就算你是SQL新人,看完也能秒变调优达人!
一、执行计划是啥?为啥要控制它?🤔
1.1 执行计划就像导航路线
想象一下:你要从北京去上海,导航给你规划了三条路线:
- 路线A:高速公路,2小时直达(最优)
- 路线B:乡间小路,绕路5小时(坑爹)
- 路线C:飞机,但票价贵10倍(土豪专属)
执行计划就是数据库的“导航路线”,优化器(导航APP)会根据“路况”(统计信息)自动选路线。但有时候它会抽风选错,这时候就需要我们手动干预!
1.2 不管控执行计划的后果
举个真实案例🌰:
-- 某电商平台订单查询SQL
SELECT * FROM orders WHERE create_time > '2023-01-01';
- 优化器抽风:没走时间索引,全表扫描1000万条数据!
- 结果:查询耗时30秒,数据库CPU飙升到90%,用户疯狂投诉!
结论:控制执行计划就像握住方向盘,关键时刻能救命!
二、操控执行计划的「三板斧」🪓
2.1 第一招:HINT大法(直接下指令)
适用场景:紧急止血!比如优化器死活不用索引。
极简步骤:
- 找到需要优化的SQL
- 在SELECT后插入
/*+ HINT内容 */
- 验证执行计划是否生效
代码示例:
-- 强制使用索引(INDEX提示)
SELECT /*+ INDEX(orders idx_create_time) */ *
FROM orders
WHERE create_time > '2023-01-01';
-- 强制嵌套循环连接(USE_NL提示)
SELECT /*+ USE_NL(a b) */ *
FROM table_a a, table_b b
WHERE a.id = b.id;
HINT类型速查表:
HINT类型 | 作用 | 使用场景 |
---|---|---|
INDEX | 强制使用索引 | 避免全表扫描 |
USE_NL | 强制嵌套循环连接 | 小表驱动大表时 |
LEADING | 指定表连接顺序 | 多表关联顺序敏感时 |
PARALLEL | 开启并行查询 | 大数据量计算 |
⚠️注意:HINT是猛药,用多了会有抗药性!比如:
- 不同数据库版本可能不兼容
- 数据量变化后可能失效
2.2 第二招:暗度陈仓(间接引导)
适用场景:不想改SQL代码,偷偷影响优化器判断。
方法1:统计信息大法
-- 手动收集统计信息(Oracle示例)
BEGIN
DBMS_STATS.GATHER_TABLE_STATS(
ownname => 'SCOTT',
tabname => 'ORDERS',
estimate_percent => 30
);
END;
方法2:虚拟列黑科技
-- 创建函数索引(针对复杂条件)
CREATE INDEX idx_func ON orders (TO_CHAR(create_time, 'YYYY-MM-DD'));
-- 查询时自动命中索引
SELECT * FROM orders
WHERE TO_CHAR(create_time, 'YYYY-MM-DD') = '2023-01-01';
方法3:分区表妙用
-- 按时间范围分区
CREATE TABLE orders (
id NUMBER,
create_time DATE
) PARTITION BY RANGE (create_time) (
PARTITION p2023 VALUES LESS THAN (TO_DATE('2024-01-01', 'YYYY-MM-DD'))
);
-- 查询时自动裁剪分区
SELECT * FROM orders
WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31';
2.3 第三招:计划冻结术(一劳永逸)
适用场景:重要SQL必须稳定,禁止优化器作妖!
SQL Profile vs Baseline 对比表
特性 | SQL Profile | SQL Baseline |
---|---|---|
绑定方式 | 自动建议优化方案 | 手动选择历史计划 |
适用场景 | 单次优化 | 长期固化 |
维护难度 | 低 | 中 |
版本兼容性 | 一般 | 高 |
Oracle绑定Baseline步骤:
- 从AWR抓取历史SQL_ID
- 加载到Baseline
DECLARE
my_plans PLS_INTEGER;
BEGIN
my_plans := DBMS_SPM.LOAD_PLANS_FROM_AWR(
begin_snap => 1234,
end_snap => 5678
);
END;
三、实战演练:从青铜到王者👑
案例:分页查询优化(5秒 → 0.1秒)
原始写法(性能差):
SELECT *
FROM (
SELECT t.*, ROWNUM rn
FROM big_table t
)
WHERE rn BETWEEN 1000001 AND 1000010;
问题:先全表扫描100万行,再取最后10行!
优化写法(性能起飞):
SELECT *
FROM (
SELECT /*+ INDEX(t idx_id) */
t.*, ROWNUM rn
FROM big_table t
WHERE id > 1000000 -- 利用索引快速定位
ORDER BY id
)
WHERE rn <= 10;
原理:通过COUNT STOPKEY
机制,扫描到第10行立即停止!
四、课后加油站⛽
练习题(答案见文末)
-
现有一个查询:
SELECT * FROM user_logs WHERE log_time > SYSDATE - 7;
执行计划显示全表扫描,如何用HINT强制使用索引
idx_log_time
? -
如何通过统计信息调整,让优化器认为某表只有100行数据?
学习建议
- 新手必做:每天看1个真实SQL的执行计划(用
EXPLAIN PLAN
) - 进阶路线:
资源推荐
- 书籍:《Oracle SQL高级编程》
- 工具:SQLT (SQLTXPLAIN) —— Oracle官方调优神器
五、文末福利 🎁
练习题答案:
- 答案:
SELECT /*+ INDEX(user_logs idx_log_time) */ * FROM user_logs WHERE log_time > SYSDATE - 7;
- 答案:
BEGIN DBMS_STATS.SET_TABLE_STATS( ownname => 'SCOTT', tabname => 'YOUR_TABLE', numrows => 100 ); END;
🎯下期预告:《SQL优化之体系结构》
💬互动话题:你在学习SQL时遇到过哪些坑?欢迎评论区留言讨论!
🏷️温馨提示:我是[随缘而动,随遇而安], 一个喜欢用生活案例讲技术的开发者。如果觉得有帮助,点赞关注不迷路🌟