Bootstrap

第十六篇 SQL优化之计划控制:进阶之路带上这篇文章,快到起飞

目录

    • 一、执行计划是啥?为啥要控制它?🤔
      • 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大法(直接下指令)

适用场景:紧急止血!比如优化器死活不用索引。

极简步骤

  1. 找到需要优化的SQL
  2. 在SELECT后插入/*+ HINT内容 */
  3. 验证执行计划是否生效

代码示例

-- 强制使用索引(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 ProfileSQL Baseline
绑定方式自动建议优化方案手动选择历史计划
适用场景单次优化长期固化
维护难度
版本兼容性一般

Oracle绑定Baseline步骤

  1. 从AWR抓取历史SQL_ID
  2. 加载到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行立即停止!


四、课后加油站⛽

练习题(答案见文末)

  1. 现有一个查询:

    SELECT * FROM user_logs WHERE log_time > SYSDATE - 7;
    

    执行计划显示全表扫描,如何用HINT强制使用索引idx_log_time

  2. 如何通过统计信息调整,让优化器认为某表只有100行数据?

学习建议

  • 新手必做:每天看1个真实SQL的执行计划(用EXPLAIN PLAN
  • 进阶路线
    看懂执行计划
    掌握HINT用法
    学习统计信息管理
    实战分区表优化

资源推荐

  • 书籍:《Oracle SQL高级编程》
  • 工具:SQLT (SQLTXPLAIN) —— Oracle官方调优神器

五、文末福利 🎁

练习题答案

  1. 答案:
    SELECT /*+ INDEX(user_logs idx_log_time) */ *
    FROM user_logs
    WHERE log_time > SYSDATE - 7;
    
  2. 答案:
    BEGIN
      DBMS_STATS.SET_TABLE_STATS(
        ownname => 'SCOTT',
        tabname => 'YOUR_TABLE',
        numrows => 100
      );
    END;
    

🎯下期预告:《SQL优化之体系结构》
💬互动话题:你在学习SQL时遇到过哪些坑?欢迎评论区留言讨论!
🏷️温馨提示:我是[随缘而动,随遇而安], 一个喜欢用生活案例讲技术的开发者。如果觉得有帮助,点赞关注不迷路🌟

;