评估 SQL 查询性能时要比较哪些指标?

Which metrics to compare when evaluating SQL query performance?

我最近看了一个关于 oracle SQL 性能调优的在线课程。在视频中,讲师在比较两个查询的性能时,不断地从Autotrace中比较COST值。

但我也从其他论坛和网站上了解到,COST 是特定于该查询的相对值,不应用作评估性能的绝对指标。他们建议改为查看一致获取、物理读取等内容。

所以我的解释是,比较用于不同目的的完全不同的查询的 COST 值是没有意义的,因为 COST 值是相对的。但是当比较相同的 2 个查询时,其中一个为了“更好的性能”而被稍作修改,比较 COST 值是可以的。我的解释准确吗?

什么时候可以将 COST 值与其他指标进行比较?

当 evaluating/comparing 查询性能时,我们还应该关注哪些其他指标?

与工程学中的大多数事情一样,这实际上归结为您要比较和评估的原因/内容。

COST 是对 Oracle 的一般 time-based 估计,在其内部优化器中用作排名指标。 This answer explains that selection process pretty well.

一般来说,COST 作为一种度量标准是比较两个不同查询的预期计算时间的好方法,因为它衡量查询的估计时间成本,表示为# of block reads。因此,如果您比较同一个查询的性能,一个针对时间进行了优化,那么 COST 是一个很好的指标。

但是,如果您的查询或系统 bottle-necked 或对时间以外的其他因素(例如内存效率)有限制,那么 COST 将不是一个可优化的指标。在这些情况下,您应该选择一个与您的最终目标相关的指标。

一般来说,我会非常谨慎地比较两个查询之间的 cost,除非您有非常具体的理由相信这是有道理的。

一般来说,人们不会查看优化器为其生成(几乎)最优计划的 99.9% 的查询。人们查看优化器已明确生成 sub-optimal 计划的查询。优化器将出于两个基本原因之一生成一个 sub-optimal 计划——它无法将查询转换为它可以优化的形式(在这种情况下,人类可能需要重写查询)或它的统计信息用于使其估计不正确,因此它认为的最佳计划并非如此。 (当然,查询速度可能还有其他原因——也许优化器生成了一个最优计划,但最优计划正在进行 table 扫描,例如因为缺少索引。)

如果我正在查看一个缓慢的查询并且该查询似乎是合理的 well-written 并且有一组合理的索引可用,那么统计数据很可能是问题的根源。然而,由于 cost 完全基于统计数据,这意味着优化器的 cost 估计是不正确的。如果它们不正确,则 cost 错误高或错误低的可能性大致相同。如果我查看查询的查询计划,我知道该查询需要聚合数十万行以生成报告,并且我看到优化器已为其分配 single-digit cost,我知道某处沿着这条线,它估计一个步骤将 return 行太少。为了调整该查询,我将需要 cost 上升,以便优化器的估计准确反映现实。如果我查看查询的查询计划,我知道应该只需要扫描少数几行,并且我看到数万行中的 cost,我知道优化器正在估计某个步骤将 return 行太多了。为了调整该查询,我将需要 cost 下降,以便优化器的估计反映现实。

如果您使用 gather_plan_statistics hint,您将在查询计划中看到估计的和实际的行数。如果优化器的估计接近实际情况,则计划可能非常好并且 cost 可能相当准确。如果优化器的估计是错误的,那么计划可能很糟糕并且 cost 很可能是错误的。尝试使用 cost 指标来调整查询而不首先确认 cost 相当接近实际情况很少有成效。

就我个人而言,我会忽略 cost 并关注随着时间的推移可能达到 stable 并且实际上与性能相关的指标。我的偏见是关注逻辑读取,因为大多数系统都是 I/O 绑定的,但您也可以使用 CPU 时间或经过时间(不过,经过时间往往不是特别 stable 因为这取决于查询时缓存中的内容 运行)。如果您正在查看计划,请关注估计行数与实际行数,而不是 cost.

查询的 实际 运行 时间 是迄今为止优化查询的最重要指标。我们可以在 99.9% 的时间里忽略成本和其他指标。

如果查询相对较小且速度较快,我们可以很容易地 re-run 它并使用 GATHER_PLAN_STATISTICS 提示找到实际的 运行 次:

-- Add a hint to the query and re-run it.
select /*+ gather_plan_statistics */ count(*) from all_objects;

-- Find the SQL_ID of your query.
select sql_id, sql_fulltext from gv$sql where lower(sql_text) like '%gather_plan_statistics%';

-- Plus in the SQL_ID to find an execution plan with actual numbers.
select * from table(dbms_xplan.display_cursor(sql_id => 'bbqup7krbyf61', format => 'ALLSTATS LAST'));

如果查询速度很慢,而且我们不能轻易地 re-run 它,生成一个 SQL 监控报告。该数据通常在最后一次执行后的几个小时内可用。

-- Generate a SQL Monitor report.
select dbms_sqltune.report_sql_monitor(sql_id => 'bbqup7krbyf61') from dual;

关于解释结果的书籍很多。基础知识是您要首先检查执行计划并关注具有最大“A-Time”的操作。如果您想了解查询或优化器在哪里出错,请将“E-Rows”与“A-Rows”进行比较,因为估计的基数会驱动大​​部分优化器决策。

示例输出:

SQL_ID  bbqup7krbyf61, child number 0
-------------------------------------
select /*+ gather_plan_statistics */ count(*) from all_objects
 
Plan hash value: 3058112905
 
--------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                 | Name               | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |  OMem |  1Mem | Used-Mem |
--------------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                          |                    |      1 |        |      1 |00:00:03.58 |     121K|    622 |       |       |          |
|   1 |  SORT AGGREGATE                           |                    |      1 |      1 |      1 |00:00:03.58 |     121K|    622 |       |       |          |
|*  2 |   FILTER                                  |                    |      1 |        |  79451 |00:00:02.10 |     121K|    622 |       |       |          |
|*  3 |    HASH JOIN                              |                    |      1 |  85666 |  85668 |00:00:00.12 |    1479 |      2 |  2402K|  2402K| 1639K (0)|
|   4 |     INDEX FULL SCAN                       | I_USER2            |      1 |    148 |    148 |00:00:00.01 |       1 |      0 |       |       |          |
...