🏺 第15章 优化 SQL 语句
📄 一句话概括

顾问是一套「分析、建议、依据」的框架:你给它一个任务,它跑完给你建议, 每条建议后面附一份「为什么这么建议」。看不懂依据,就等于没用它。 两个最常用的:SQL 调优顾问管「这条 SQL 怎么写」,SQL 访问顾问管「该建什么索引」。 它们都会错,因为只看成本模型,不懂你的业务。

📝 本章和前面几章的关系

15.5 是我们自己写脚本比耗时。这一篇是让数据库自己分析,直接给出建议清单。 但两者的关系不是替代,是接力:顾问帮你把范围从「几万条 SQL」缩到「五个候选索引」, 验证还得靠 15.5 那套办法。

一、顾问是什么:一套统一的框架

Oracle 里带「Advisor」的东西有十几个,但结构是一样的: 建任务(task)、执行、出建议(recommendation)、附依据(finding)。 所以学会一个,其余的看文档就会用。 它们的数据都落在同一组视图里,四个后缀分别对应四件事: _TASKS 是任务、_RECOMMENDATIONS 是建议、_FINDINGS 是依据、 _ACTIONS 是「具体该执行什么命令」。 前缀都是 DBA_ADVISOR。 DBA_ADVISOR_ACTIONS 这一列最实用:它直接把「该敲的 SQL」写成文本给你,复制就能跑。 但正因为太方便,很多人在这一步犯错——看都不看就整段执行。 常见的几个顾问,各管一段:

顾问 管什么 什么时候用
ADDM 整体诊断:这一段最耗什么 每天看 AWR 时顺手看
SQL 调优顾问 单条 SQL 怎么变快 找到 Top SQL 之后
SQL 访问顾问 该建哪些索引和物化视图 一批 SQL 都慢的时候
Segment Advisor 哪些段有空间可回收 表空间吃紧时
Undo Advisor 回滚段该留多大 长事务报快照过旧时

二、SQL 调优顾问

它的输入可以是单条 SQL(给 SQL_ID 或 SQL 文本),也可以是一个 SQL 集。 三个包函数走完:DBMS_SQLTUNE.CREATE_TUNING_TASK 建任务、EXECUTE_TUNING_TASK 执行、 REPORT_TUNING_TASK 出报告。 它给的建议分四类,风险从低到高:

建议类型 它想做什么 风险
统计信息 这张表的统计信息过时了,重收一遍 最低
索引 建一个索引,能让这条 SQL 少扫很多块 中,写会变慢
SQL 改写 这条 SQL 有个等价但更快的写法 中,要回归测试
SQL Profile 给优化器补一组修正系数 高,升级后可能失效

四类里最值得先看的是第一类:统计信息。 因为它什么都不改,只是让优化器知道真实情况——这一条能解决的比例,比很多人想的高得多。 最玄的是 SQL Profile:它不改 SQL、不建索引、不改数据, 只往优化器里塞一份「这个条件下实际有多少行」的修正。 效果是优化器突然「算对了」,于是选对了路。 代价是这份修正绑在 SQL 的文本特征上,SQL 一改、版本一升,可能就废了。 所以 Profile 的定位是「救火」,不是「治本」。 治本还是收统计信息,或者干脆把 SQL 改成优化器不用猜的写法。

三、SQL 访问顾问

它管的不是一条 SQL,而是一批 SQL:给定工作负载,它算出「该建哪些索引、该建哪些物化视图、 该按什么分区」。 这是它跟调优顾问最大的区别——调优顾问一次只看一条,访问顾问一次看一整批。 用法比调优顾问啰嗦,要五步: ① DBMS_ADVISOR.CREATE_TASK('SQL Access Advisor')——建任务。 ② CREATE_SQLWKLD_REFERENCE——把负载挂在任务上。 ③ ADD_STS_REF——关联一个 SQL 集。 ④ SET_TASK_PARAMETER——设「只考虑索引」还是「也考虑物化视图」。 ⑤ EXECUTE_TASK 再 GET_TASK_REPORT——执行并取报告。 第 ④ 步那个参数最关键,因为它决定了搜索空间: 只考虑索引,它就在「哪些列、什么顺序」里搜; 允许物化视图,它还会考虑「要不要预先算好一张汇总表」。 后者收益大得多,代价也大得多——物化视图要占空间、要刷新,刷新本身又是一笔开销。 它还有个参数叫「空间限制」。 设了之后,它会在「这么多空间里怎么建最划算」的约束下给方案。 这个参数很实用:不加限制,它会建议你建八个索引; 加了限制,它会告诉你「前三个收益占八成,后五个只占两成」。

四、建议怎么读:按风险排序

一条建议值不值得做,看三件事:改了什么、收益多大、代价是什么。 顾问只保证第二件,前两件得你自己判断。 收益这一栏它给的是「估算的改善百分比」,来源是成本模型。 成本模型准不准,取决于统计信息准不准——所以统计信息过时的时候,它连收益都算错。 代价这一栏它基本不给。 一条「建索引」的建议,报告里不会写「你的插入会慢多少」。 这不是它藏私,是它压根不算这个——它只看查询侧的成本。 所以读报告的顺序是: 先看「统计信息」类建议(零风险,直接做)→ 再看「SQL 改写」(要测试, 收益通常大)→ 再看「索引」(要算写代价)→ 最后看「Profile」和「固定基线」(救火用)。 顺序反了会怎样:先做索引那几条,写入变慢,然后把问题归到「顾问不靠谱」上。 其实顾问没说错,是你漏算了写代价。

五、顾问用错了会怎样

第一种错:喂假负载。 「我随便挑了几条慢 SQL 喂进去」——顾问给你的就是「只优化这几条」的方案, 别的 SQL 可能因此变慢。 它不懂你的业务,只知道你喂了什么。 第二种错:照单全收。 一次建八个索引,读可能快了三成,写慢了一倍。 这笔账顾问不替你算。 第三种错:不看依据。 报告里 DBA_ADVISOR_FINDINGS 那几行才是「为什么」,跳过它就等于没看报告。 比如它说「建索引能省 60% 的块访问」,依据是「这条 SQL 当前扫了 20000 块」—— 这个数字你可以自己去 V$SQL 核一遍,核不上就说明它估错了。 所以顾问的正确角色是「帮你缩小范围」,不是「替你做决定」。 它把几万条 SQL 缩成五个候选索引,剩下的判断——该建哪个、写代价能不能承受、 业务上能不能接受——还得人来做。 最后拍板的人,得看得懂计划,也看得懂业务。

六、动手 Demo:简化版访问顾问:从负载算出该建什么索引,再真建出来验证

这一篇的 Demo 做三件事:算「哪些列被最多 SQL 引用」、照建议建索引、 再把整批负载重跑一遍看总收益。 它比真顾问简陋得多,但三个环节一个不少:输入负载、输出建议、验证收益。 而这三个环节,正是「顾问能不能信」的全部依据。

# 简化版「访问顾问」:按被引用的列排序,再照建议建索引验证
import collections

cnt = collections.Counter(tag for sql, tag in load)   # load 同上
for col, n in cnt.most_common():
    print("  %-12s %d" % (col, n))

# 顾问的输出大致长这样:
#   建议建索引 big(deptno)        被 40 条 SQL 引用
#   建议建索引 big(amt)           被 20 条 SQL 引用
#   建议建索引 big(deptno, flag)  被 10 条 SQL 引用

# 照第一条建出来,整批负载重跑一遍
t0 = sum(best(lambda s=s[0]: d.execute(s).fetchone()) for s in load)
d.execute("create index i_a_deptno on big(deptno)")
d.execute("create index i_a_amt on big(amt)")
t1 = sum(best(lambda s=s[0]: d.execute(s).fetchone()) for s in load)
print("建索引前 %.4f 秒,建索引后 %.4f 秒,快了 %.2f 倍"
      % (t0, t1, t0 / t1))

实跑输出:

--------------------------------------------------------------------
第六十九课:性能顾问——它给建议,你负责验证
--------------------------------------------------------------------

  **Oracle 有两个常用的顾问:SQL 调优顾问管「这条 SQL 怎么写」,SQL 访问顾问管「该建什么索引」。**
  **这一课跑一个简化版的访问顾问:从负载里算出该建哪些索引,再真建出来验证。**

  一、顾问的输入:工作负载
  --------------------------------------------------------------------
  **真实用法是把一个 SQL 集(SQL Tuning Set)喂给它。**
  **这里直接用一个列表:80 条 SQL,各自带了「访问了哪些列」。**

  列组合      被引用次数
  ----------------------
  deptno      40
  amt         20
  name        10
  flag+deptno 10
  已选择 4 行

  二、顾问的输出:建议 + 理由 + 预估收益
  --------------------------------------------------------------------
  **真实的 `DBMS_ADVISOR` 输出是一张表:建议什么、为什么、预估能省多少成本。**
  **这里的「预估」是用「这条 SQL 现在的耗时 × 出现次数」算的:**

  建议                  影响条数 当前总耗时
  -----------------------------------------
  给 deptno 建索引      40       1.0937 秒
  给 amt 建索引         20       0.5391 秒
  给 name 建索引        10       0.2983 秒
  给 flag+deptno 建索引 10       0.3497 秒
  已选择 4 行
  **「影响条数」和「当前总耗时」两列一起看才有意义。**
  **一条 SQL 慢但只跑一次,和一条 SQL 快但跑一万次,该优化的是后者。**
  **顾问的排序依据是「总成本」,不是「单次成本」。**

  三、照建议建出来,验证
  --------------------------------------------------------------------

             整批负载总耗时
  -------------------------
  建索引之前 2.2885 秒
  建索引之后 0.5189 秒
  已选择 2 行
  **快了 4.41 倍。**
  **但这不是「照单全收」的理由——**
  **索引有写代价:每次 `INSERT`、`UPDATE`、`DELETE` 都要维护它。**
  **代价实测:带两个索引插 5000 行用了 0.0670 秒。**
  **可以把这两个索引删掉再跑一次对比——你会看到写变快了、读变慢了。**

  四、顾问的三条使用规矩
  --------------------------------------------------------------------
    1. 一定用真实负载          别拿自己编的几条 SQL 当样本
    2. 建议要逐条验证          顾问也会错,它只看成本模型
    3. 建完再看写性能          读快了、写慢了,要算总账
  **第一条最容易违反。**
  **「我随便挑了几条慢 SQL 喂进去」——顾问给你的就是「优化这几条」的方案,别的 SQL 可能因此变慢。**
  **它不懂你的业务,只知道你喂了什么。**
  **所以顾问的角色是「帮你缩小范围」,不是「替你做决定」。**
  **最后拍板的人还是得看懂计划、看得懂业务。**

第一段是顾问的输入:80 条 SQL,各自带了「访问了哪些列」。 真顾问喂的是 SQL 集,这里用列表代替,省掉抓负载那一步—— 但「按列统计引用次数」这个逻辑是一样的。 第二段是顾问的输出:建议、理由、预估收益。 注意 deptno 被 40 条引用,是 amt 的两倍——这就是它排在第一位的依据:收益面最广。 第三段照建议建两个索引,整批负载从 2.2885 秒掉到 0.5189 秒,快 4.41 倍。 这个数字比 15.5 那个只加一个索引的 2.17 倍更好看,但代价也更大:多了两个索引要维护。 第四段是这一篇最要紧的一句:这不是「照单全收」的理由。 实测带两个索引插 5000 行用 0.0670 秒——写是有代价的,只是顾问不算这笔账。 报告里永远不会出现「你的插入会慢多少」这一栏。 最后那段三条规矩,其实是在说一件事:顾问的输出是【候选】,不是【结论】。 候选要变成结论,中间必须过一遍验证——也就是 15.5 那套重演。 所以第 15 章这六篇是一个闭环:写 SQL、选索引、看计划、量改前量改后、让顾问缩范围, 最后回到量一遍。

30 秒实验

  1. 把 load 里 deptno 那 40 条删到只剩 5 条,再跑一次顾问统计。 看建议的顺序会不会变、amt 会不会升到第一。
  2. 只建 amt 那个索引,不建 deptno 的,跑一遍总耗时。 和建两个的 4.41 倍比一下,看「少建一个」亏多少——这就是边际收益。
  3. 在 big 上加一个 (deptno, amt) 复合索引,把 = deptno 和 = amt 两类查询都覆盖住。 看能不能用一个索引干两个索引的活、写代价是不是少了一半。

常见误区

  1. 「顾问说建就建」。 顾问只算读的收益,不算写的代价。建完要看插入、更新、删除有没有变慢。
  2. 「顾问给了八个索引,全建上」。 索引之间会互相干扰, 而且每个都要维护。边际收益很快就会掉到零以下。
  3. 「SQL Profile 是万能药」。 它是把修正系数挂在 SQL 特征上。 SQL 一改、版本一升就可能失效,还可能把错误的计划锁得更死。
  4. 「顾问说收益 60%,就真能快 60%」。 那是成本模型算出来的估算值。 统计信息不准的时候,连这个数都是错的。
自测三问(点开对答案) 1. 所有顾问共用的四步结构是什么? —— **建任务、执行、出建议、附依据。** 数据分别落在 `DBA_ADVISOR_TASKS`、`_RECOMMENDATIONS`、 `_FINDINGS`、`_ACTIONS` 里。 2. SQL 调优顾问的四类建议里,哪一类风险最低? —— **收集统计信息。** 它什么都不改,只是让优化器知道真实情况,**所以应该永远先做这一条。** 3. SQL Profile 和索引,最根本的区别是什么? —— **Profile 不改数据结构、不改 SQL,只改优化器的估算;** **索引是实打实加了一个对象,读变快、写变慢。** 4. 为什么说顾问的输出是「候选」不是「结论」? —— **因为它只按成本模型算读的收益,不算写的代价,也不懂业务。** **所以每条建议都得自己验证一遍。**

第 15 章到这里结束。下一篇进第 16 章:备份与恢复。 调优是「让它跑得快」,备份是「让它丢了能回来」—— 后者在生产上优先级更高,因为快的系统丢了数据也等于零。

相关笔记