为什么 3 个相同值上的 DISTINCT 这么慢?

Why is DISTINCT on 3 identical values so slow?

我有一个我无法理解的性能问题。我正在执行的查询如下:

SELECT UNIQUE GROUP_ID
  FROM pos, TABLE (t_type(12984918, 12984919, 12984917))
  WHERE pos.pos_id = COLUMN_VALUE AND GROUP_ID <> 0;

1 row returned in 99 ms 

您可能认为 83 毫秒很好,但真正让我困惑的是,即使只有 3 个重复值,删除 UNIQUE 也会使查询更快:

SELECT GROUP_ID
  FROM pos, TABLE (t_type(12984918, 12984919, 12984917))
  WHERE pos.pos_id = COLUMN_VALUE AND GROUP_ID <> 0;

3 rows returned in 0.048 ms 

oracle 真的只需要将近 100 毫秒就可以将一组 3 个元素缩减为一个值吗?这对我来说似乎很疯狂,所以我开始做一些调查并使用 TKPROF.

获取执行计划
SELECT UNIQUE GROUP_ID
  FROM pos, TABLE (t_type(12984918, 12984919, 12984917))
 WHERE pos.pos_id = COLUMN_VALUE AND GROUP_ID <> 0

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          2          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.09          0          8          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.10          0         10          0           1

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 58  
Number of plan statistics captured: 1

Rows (1st) Rows (avg) Rows (max)  Row Source Operation
---------- ---------- ----------  ---------------------------------------------------
         1          1          1  HASH UNIQUE (cr=8 pr=0 pw=0 time=99258 us cost=31 size=3892 card=139)
         3          3          3   NESTED LOOPS  (cr=8 pr=0 pw=0 time=84 us)
         3          3          3    NESTED LOOPS  (cr=5 pr=0 pw=0 time=59 us cost=30 size=3892 card=139)
         3          3          3     COLLECTION ITERATOR CONSTRUCTOR FETCH (cr=0 pr=0 pw=0 time=7 us cost=29 size=16336 card=8168)
         3          3          3     INDEX UNIQUE SCAN IU_POS_POS_ID (cr=5 pr=0 pw=0 time=31 us cost=0 size=0 card=1)(object id 20684)
         3          3          3    TABLE ACCESS BY INDEX ROWID POS (cr=3 pr=0 pw=0 time=12 us cost=0 size=26 card=1)

好吧,看起来 Oracle 使用 HASH UNIQUE 操作确实花费了 99 毫秒将 3 个相同的值减少为一个值。这实际上仍然看起来很疯狂。那应该几乎不会引起注意。所以我开始寻找替代方案。原来Oracle也可以用SORT UNIQUE操作来实现DISTINCT。我还发现我可以通过以下方式强制 oracle 不使用 HASH UNIQUE

ALTER SESSION SET "_gby_hash_aggregation_enabled" = true;

现在,我的查询运行时间为 0.48 毫秒!这是 2000 倍的改进!卧槽!这是显示它正在使用 SORT UNIQUE:

的执行计划
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          4          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0         16          0           2
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        6      0.00       0.00          0         20          0           2

Misses in library cache during parse: 2
Optimizer mode: ALL_ROWS
Parsing user id: 58  
Number of plan statistics captured: 2

Rows (1st) Rows (avg) Rows (max)  Row Source Operation
---------- ---------- ----------  ---------------------------------------------------
         1          1          1  SORT UNIQUE (cr=8 pr=0 pw=0 time=48 us cost=30 size=28 card=1)
         3          3          3   NESTED LOOPS  (cr=8 pr=0 pw=0 time=35 us)
         3          3          3    NESTED LOOPS  (cr=5 pr=0 pw=0 time=26 us cost=29 size=28 card=1)
         3          3          3     COLLECTION ITERATOR CONSTRUCTOR FETCH (cr=0 pr=0 pw=0 time=2 us cost=29 size=6 card=3)
         3          3          3     INDEX UNIQUE SCAN IU_POS_POS_ID (cr=5 pr=0 pw=0 time=17 us cost=0 size=0 card=1)(object id 20684)
         3          3          3    TABLE ACCESS BY INDEX ROWID POS (cr=3 pr=0 pw=0 time=7 us cost=0 size=26 card=1)

********************************************************************************

好的,现在我在想如何在不触及会话参数的情况下强制 oracle 使用 SORT UNIQUE。原来我可以只添加一个 ORDER BY 子句,并加速我的查询。

SELECT UNIQUE GROUP_ID
  FROM pos, TABLE (t_type(12984918, 12984919, 12984917))
 WHERE pos.pos_id = COLUMN_VALUE AND GROUP_ID <> 0
 ORDER BY GROUP_ID;

所以基本上我已经解决了我的问题(如果你可以称之为 hack "solving a problem"),但我仍然感到困惑。

问题来了:

Why is Oracle choosing HASH UNIQUE over SORT UNIQUE?

您的第一个 tkprof 结果显示 TABLE 迭代器的基数更高(预期?)。 Oracle 正在优化多于三行。

Why is HASH UNIQUE so slow when there are only 3 records to hash?

Hash unique 将需要构建一个散列table,无论有多少元素被散列。据推测,对于较少的元素,散列的大小 table 会更小,但基数估计再次表明 Oracle 将构建更大的散列 table。

Is there a better way to hint oracle to use SORT UNIQUE over HASH UNIQUE?

我会尝试在 table 运算符上暗示基数。

Is there a better way to hint oracle to use SORT UNIQUE over HASH UNIQUE?

我会检查 table 和索引统计数据是如何收集的。 oracle 优化器使用它们来构建有效的访问计划。