在Oracle 18c中,为什么max()的效率比order by低得多,只获取前1行?

vuv7lop3  于 2023-06-29  发布在  Oracle
关注(0)|答案(1)|浏览(323)

我有一个大约有10亿行的表,它按date分区,并在someNr上有一个(本地)索引。此查询:

  1. select max("date") from T where someNr = 1;

如预期的那样在毫秒内执行。但是,如果没有带有someNr = 1的条目,则查询需要大约一分钟。然后我发现:

  1. select "date" from T where someNr = 1 order by "date" desc fetch first 1 rows only;

在毫秒内执行,无论是否存在带有someNr = 1的条目。
max("date")的生产数据库中的实际计划:

order by的生产数据库中的实际计划:

找了一个多小时,也没找到满意的解释。要么他们没有正确的索引,使用rownum < 2,要么答案没有真正回答问题。
以下最小重现器与真实的场景类似,并显示了解释计划中的差异:

  1. SELECT BANNER FROM v$version;
  2. create table T ("DATE" date not null, SOME_NR int not null);
  3. alter table T
  4. modify PARTITION BY RANGE ("DATE")
  5. INTERVAL (NUMTODSINTERVAL(1, 'DAY'))
  6. (partition "old" values less than (to_date('01-JAN-2023', 'DD-MON-YYYY') ));
  7. create index I_T_DATE ON T ("DATE", SOME_NR);
  8. explain plan for
  9. select max("DATE") from T where SOME_NR = 1;
  10. select plan_table_output
  11. from table (dbms_xplan.display('plan_table', null, 'basic'));
  12. explain plan for
  13. select "DATE" from T where SOME_NR = 1 order by "DATE" fetch first 1 rows only;
  14. select plan_table_output
  15. from table (dbms_xplan.display('plan_table', null, 'basic'));

输出:

  1. Oracle Database 18c Express Edition Release 18.0.0.0.0 - Production
  2. +-------------------------------------------------+
  3. |PLAN_TABLE_OUTPUT |
  4. +-------------------------------------------------+
  5. |Plan hash value: 1357496781 |
  6. | |
  7. |-------------------------------------------------|
  8. || Id | Operation | Name ||
  9. |-------------------------------------------------|
  10. || 0 | SELECT STATEMENT | ||
  11. || 1 | SORT AGGREGATE | ||
  12. || 2 | FIRST ROW | ||
  13. || 3 | PARTITION RANGE ALL | ||
  14. || 4 | INDEX FULL SCAN (MIN/MAX)| I_T_DATE ||
  15. +-------------------------------------------------+
  16. +-------------------------------------------+
  17. |PLAN_TABLE_OUTPUT |
  18. +-------------------------------------------+
  19. |Plan hash value: 3984456263 |
  20. | |
  21. |-------------------------------------------|
  22. || Id | Operation | Name ||
  23. |-------------------------------------------|
  24. || 0 | SELECT STATEMENT | ||
  25. || 1 | VIEW | ||
  26. || 2 | WINDOW NOSORT STOPKEY| ||
  27. || 3 | PARTITION RANGE ALL | ||
  28. || 4 | INDEX FULL SCAN | I_T_DATE ||
  29. +-------------------------------------------+

那么,为什么Oracle的max()不够智能,不能按照日期降序排序,并以order by + fetch first 1 rows only相同的方式使用第一个值呢?我知道max()可能会返回null,而第二个选项不会,但这真的不应该有什么关系。

oknwwptz

oknwwptz1#

你说得对,Oracle的MIN/MAX索引算法不是很聪明。它似乎只能遍历索引一次,以获得最大或最小的值。Oracle通常不会将查询转换为使用MIN/MAX,因此您需要重写查询以利用该算法。
我不能真正回答 * 为什么 * 算法不聪明。我猜这是一个恶性循环,Oracle最初并没有投入太多精力来使MIN/MAX变得智能,因此它不经常使用,因此不值得投入更多精力来改进。MIN/MAX选项甚至没有在文档中的其他全面的PLAN_TABLE参考中列出。
作为MIN/MAX的一个示例,查询只能使用MIN或MAX,而不能同时使用两者。像SELECT MIN(A),MAX(B) FROM SOME_TABLE这样的查询需要重写为SELECT (SELECT MIN(A) FROM SOME_TABLE), (SELECT MAX(B) FROM SOME_TALBE) FROM DUAL以利用MIN/MAX。有关此限制的更多详细信息,请参阅我的答案here
但是理解算法的局限性可以帮助我们设计一个更好的查询。我们可以使用一次MIN/MAX来快速获得最大值,然后将该值插入查询。下面的计划生成一个“INDEX FULL SCAN(MIN/MAX)”和一个“INDEX RANGE SCAN”,但是使用这些操作阅读索引两次可能比执行常规“INDEX FULL SCAN”的查询更快。

  1. explain plan for
  2. select "DATE"
  3. from T
  4. where SOME_NR = 1
  5. --This subquery improves performance by enabling the (MIN/MAX) option.
  6. and "DATE" = (select max("DATE") from T where SOME_NR = 1)
  7. order by "DATE" fetch first 1 rows only;
  8. select plan_table_output
  9. from table (dbms_xplan.display('plan_table', null, 'basic'));
  10. Plan hash value: 206361108
  11. ---------------------------------------------------
  12. | Id | Operation | Name |
  13. ---------------------------------------------------
  14. | 0 | SELECT STATEMENT | |
  15. | 1 | VIEW | |
  16. | 2 | WINDOW NOSORT STOPKEY | |
  17. | 3 | INDEX RANGE SCAN | I_T_DATE |
  18. | 4 | SORT AGGREGATE | |
  19. | 5 | FIRST ROW | |
  20. | 6 | INDEX FULL SCAN (MIN/MAX)| I_T_DATE |
  21. ---------------------------------------------------
展开查看全部

相关问题