Oracle не может сделать вывод, что эти запросы идентичны - сгенерирован очень другой план выполнения - PullRequest
3 голосов
/ 28 марта 2012

У меня есть два очень похожих утверждения с подзапросом. Я выделил разницу с **.

1

SELECT DISTINCT name
FROM person, nameindex n
WHERE person.id1    ='0812'
AND person.id2    =n.id2
AND person.id1    =n.id1
AND n.phonetic IN
  (SELECT n2.phonetic
  FROM nameindex n2
  WHERE n2.id1=person.id1 **
  GROUP BY n2.phonetic
 HAVING COUNT(*) BETWEEN 4 AND 500)

2

SELECT DISTINCT name
FROM person, nameindex n
WHERE person.id1    ='0812'
AND person.id2    =n.id2
AND person.id1    =n.id1
AND n.phonetic IN
  (SELECT n2.phonetic
  FROM nameindex n2
  WHERE n2.id1='0812'  **
  GROUP BY n2.phonetic
 HAVING COUNT(*) BETWEEN 4 AND 500)

Я думаю, что оракул может сделать вывод, что person.id1 должно быть постоянным 0812 в подзапросе. Однако оба запроса приводят к совершенно разным планам выполнения и затратам (1: стоимость 4404211855, а 2: стоимость: 36237). Почему это?

Это скорее аналитический запрос, а не OLTP, поэтому для этого конкретного запроса не определены индексы.

(История вопроса: получить имена людей в id1 = '0812', у которых есть фонетическая запись в таблице nameindex, для которой существует от 4 до 500 случаев).

1 Ответ

4 голосов
/ 28 марта 2012

Я запустил аналогичный запрос со следующей настройкой:

CREATE TABLE person (id1, id2, NAME) AS 
   SELECT to_char(mod(ROWNUM, 1000), 'fm0000'), ROWNUM,
          dbms_random.string('A',10)
     FROM dual 
   CONNECT BY LEVEL <= 1e6;
CREATE TABLE nameindex (id1, id2, phonetic) AS
   SELECT id1, id2, to_char(dbms_random.value(1, 200), 'fm000')
     FROM person;

Я обнаружил, что ваш первый запрос дает следующий план:

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 291343677
--------------------------------------------------------------------------------
| Id  | Operation             | Name      | Rows  | Bytes | Cost (%CPU)| Time
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |           |     1 |  2040 |   331K  (2)| 01:06:2
|   1 |  HASH UNIQUE          |           |     1 |  2040 |   331K  (2)| 01:06:2
|*  2 |   FILTER              |           |       |       |            |
|*  3 |    HASH JOIN          |           |   891 |  1775K|  1750   (2)| 00:00:2
|*  4 |     TABLE ACCESS FULL | NAMEINDEX |   892 | 18732 |   739   (2)| 00:00:0
|*  5 |     TABLE ACCESS FULL | PERSON    |  1395 |  2750K|  1010   (2)| 00:00:1
|*  6 |    FILTER             |           |       |       |            |
|   7 |     HASH GROUP BY     |           |  9550 | 76400 |   740   (2)| 00:00:0
|*  8 |      TABLE ACCESS FULL| NAMEINDEX |  9550 | 76400 |   739   (2)| 00:00:0
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter( EXISTS (SELECT 0 FROM "NAMEINDEX" "N2" WHERE "N2"."ID1"=:B1
              GROUP BY "N2"."PHONETIC" HAVING "N2"."PHONETIC"=:B2 AND COUNT(*)>=
              COUNT(*)<=500))
   3 - access("PERSON"."ID2"="N"."ID2" AND "PERSON"."ID1"="N"."ID1")
   4 - filter("N"."ID1"='0812')
   5 - filter("PERSON"."ID1"='0812')
   6 - filter("N2"."PHONETIC"=:B1 AND COUNT(*)>=4 AND COUNT(*)<=500)
   8 - filter("N2"."ID1"=:B1)

Как видите, полусоединение IN переписано как EXISTS, который создает тот же план, что и этот запрос:

SELECT DISTINCT NAME
  FROM person, nameindex n
 WHERE person.id1 = '0812'
   AND person.id2 = n.id2
   AND person.id1 = n.id1
   AND EXISTS (SELECT NULL
                 FROM nameindex n2
                WHERE n2.id1 = person.id1
                  AND n2.phonetic = n.phonetic
                GROUP BY n2.phonetic
               HAVING COUNT(*) BETWEEN 4 AND 500);

Здесь вы видите, что подзапрос НЕ является константой и поэтому вычисляется для каждой строки основного запроса, что приводит к неоптимальному плану выполнения.

Я предлагаю вам использовать все столбцы соединения в GROUP BY при использовании агрегированного полусоединения. Следующий запрос дает оптимальный план:

SELECT DISTINCT NAME
  FROM person, nameindex n
 WHERE person.id1 = '0812'
   AND person.id2 = n.id2
   AND person.id1 = n.id1
   AND (n.id1, n.phonetic) IN (SELECT n2.id1, n2.phonetic
                                 FROM nameindex n2
                                GROUP BY n2.id1, n2.phonetic
                               HAVING COUNT(*) BETWEEN 4 AND 500);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...