最近,我在客户站点的各处遇到了这种查询:
DECLARE
v_var NUMBER(10);
BEGIN
SELECT COUNT(*)
INTO v_var
FROM table1
JOIN table2 ON table1.t1_id = table2.t1_id
JOIN table3 ON table2.t2_id = table3.t2_id
...
WHERE some_predicate;
IF (v_var = 1) THEN
do_something
ELSE
do_something_else
END IF;
END;
不幸的是,当我们要检查某些谓词的关系时, COUNT(*)
通常是想到的第一个解决方案。 但是COUNT()
非常昂贵,特别是如果我们要做的只是检查我们的关系是否存在 。 这个词会响吗? 是的,我们应该使用EXISTS
谓词,因为如果我们不关心为给定谓词返回true的确切记录数,则我们不应该遍历完整的数据集来实际计算确切数 。 上面的PL / SQL块可以简单地重写为这一块:
DECLARE
v_var NUMBER(10);
BEGIN
SELECT CASE WHEN EXISTS (
SELECT 1
FROM table1
JOIN table2 ON table1.t1_id = table2.t1_id
JOIN table3 ON table2.t2_id = table3.t2_id
...
WHERE some_predicate
) THEN 1 ELSE 0 END
INTO v_var
FROM dual;
IF (v_var = 1) THEN
do_something
ELSE
do_something_else
END IF;
END;
让我们测量一下!
查询1产生以下执行计划:
-----------------------------------------------
| Id | Operation | E-Rows | A-Rows |
-----------------------------------------------
| 0 | SELECT STATEMENT | | 1 |
| 1 | SORT AGGREGATE | 1 | 1 |
|* 2 | HASH JOIN | 4 | 4 |
|* 3 | TABLE ACCESS FULL| 2 | 2 |
|* 4 | TABLE ACCESS FULL| 6 | 6 |
-----------------------------------------------
查询2产生以下执行计划:
----------------------------------------------
| Id | Operation | E-Rows | A-Rows |
----------------------------------------------
| 0 | SELECT STATEMENT | | 1 |
| 1 | NESTED LOOPS | 4 | 1 |
|* 2 | TABLE ACCESS FULL| 2 | 1 |
|* 3 | TABLE ACCESS FULL| 2 | 1 |
| 4 | FAST DUAL | 1 | 1 |
----------------------------------------------
您可以忽略TABLE ACCESS FULL
操作,实际查询是在没有索引的普通数据库上执行的。
但是,最重要的是大大提高了E-Rows
值(E =估计值),更重要的是最优A-Rows
值(A =实际值)。 如您所见,一旦遇到与谓词匹配的第一个记录,则EXISTS
谓词可能会提前中止–在这种情况下,立即终止。
有关如何收集Oracle执行计划的更多详细信息,请参见这篇文章。
结论
每当遇到COUNT(*)
操作时,都应该问自己是否真的需要它。 您是否真的需要知道与谓词匹配的记录的确切数量? 还是您已经很高兴知道任何记录与谓词匹配?
答:可能是后者。
翻译自: https://www.javacodegeeks.com/2014/08/sql-tip-of-the-day-be-wary-of-select-count.html