Статьи

Совет дня SQL: будьте осторожны с SELECT COUNT (*)

Недавно я встречал такой запрос повсюду на сайте клиента:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
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 можно переписать тривиально в этот:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
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 дает этот план выполнения:

1
2
3
4
5
6
7
8
9
-----------------------------------------------
| 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 дает этот план выполнения:

1
2
3
4
5
6
7
8
9
----------------------------------------------
| 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 = Estimated) и, что еще важнее, оптимальные значения A-Rows (A = Actual). Как вы можете видеть, предикат EXISTS может быть прерван рано, как только будет обнаружена первая запись, соответствующая предикату — в этом случае немедленно.

Смотрите этот пост о более подробной информации о том, как собирать планы выполнения Oracle.

Вывод

Всякий раз, когда вы сталкиваетесь с операцией COUNT(*) , вы должны спросить себя, действительно ли она нужна. Вы действительно должны знать точное количество записей, которые соответствуют предикату? Или вы уже счастливы, зная, что любая запись соответствует предикату?

Ответ: это, вероятно, последний.

Ссылка: Совет дня по SQL: Остерегайтесь SELECT COUNT (*) от нашего партнера по JCG Лукаса Эдера в блоге JAVA, SQL и AND JOOQ .