Доброго времени суток!

Немножко погонял 2.1.0.17735 RC1 и обнаружил
следующие интересные (для меня) вещи.
Возможно, они заинтересуют кого-нибудь еще.
Насколько я понимаю, все это верно и для 2.0.x

Итак, начнем

CREATE TABLE TEST_TABLE3 (
    ID1  INTEGER,
    ID2  INTEGER
);

COMMIT;


INSERT INTO TEST_TABLE3 (ID1, ID2) VALUES (3, 7);
INSERT INTO TEST_TABLE3 (ID1, ID2) VALUES (9, 8);
INSERT INTO TEST_TABLE3 (ID1, ID2) VALUES (6, 3);
INSERT INTO TEST_TABLE3 (ID1, ID2) VALUES (7, 7);
INSERT INTO TEST_TABLE3 (ID1, ID2) VALUES (4, 9);
INSERT INTO TEST_TABLE3 (ID1, ID2) VALUES (3, 5);
INSERT INTO TEST_TABLE3 (ID1, ID2) VALUES (9, 2);
INSERT INTO TEST_TABLE3 (ID1, ID2) VALUES (10, 8);
INSERT INTO TEST_TABLE3 (ID1, ID2) VALUES (7, 2);
INSERT INTO TEST_TABLE3 (ID1, ID2) VALUES (4, 5);

COMMIT;

CREATE INDEX TEST_TABLE3_ID1 ON TEST_TABLE3 (ID1);
CREATE INDEX TEST_TABLE3_ID2 ON TEST_TABLE3 (ID2);

COMMIT;

Пример 1.

select Count(*) from test_table3 t1 inner join test_table3 t2
     on t1.ID1 = t2.ID2
where t2.id2 = 3

PLAN JOIN (T1 INDEX (TEST_TABLE3_ID1), T2 INDEX (TEST_TABLE3_ID2))

Честно скажу, не сразу сообразил, что оптимизатор превращает
условие t1.ID1 = t2.ID2 and t2.id2 = 3 в t1.ID1 = t2.ID2 and t1.id1 = 3

В данном случае, это немного хуже варианта с
select Count(*) from test_table3 t2 inner join test_table3 t1
     on t1.ID1 = t2.ID2
where t2.id2 = 3
с планом
PLAN JOIN (T2 INDEX (TEST_TABLE3_ID2), T1 INDEX (TEST_TABLE3_ID1))

- 4 и 3 индексных чтения соответственно.

Впрочем, в реальных условиях для разных таблиц, нужно сильно постараться, чтобы потерять в производительности - например, если селективности индексов совпадают, но по ID1 идет перекос в сторону 3,
а в ID2 - в сторону 5.

Это, естественно, не баг, а особенность реализации.

Пример 2.

Оптимизатор хорошо обрабатывает условия с равенствами и неравенствами
в where

select Count(*) from test_table3 t1 inner join test_table3 t2
     on t1.ID1 = t2.ID2
where (t2.id1 = t1.id2)
план
PLAN JOIN (T1 NATURAL, T2 INDEX (TEST_TABLE3_ID2, TEST_TABLE3_ID1))

select Count(*) from test_table3 t1 inner join test_table3 t2
     on t1.ID1 = t2.ID2
where (t2.id1 >= t1.id2)
план
PLAN JOIN (T1 NATURAL, T2 INDEX (TEST_TABLE3_ID2, TEST_TABLE3_ID1))

Но стоит немного усложнить выражение:

select Count(*) from test_table3 t1 inner join test_table3 t2
     on t1.ID1 = t2.ID2
where (t2.id1 = t1.id2-1)

или

select Count(*) from test_table3 t1 inner join test_table3 t2
     on t1.ID1 = t2.ID2
where (t2.id1 > t1.id2-1)

как получаем

PLAN JOIN (T2 NATURAL, T1 INDEX (TEST_TABLE3_ID1))

хотя в этих случаях, на первый взгляд, ничто не мешает
задействовать индекс по t2.id1

Пример 3.

Итак, условия в where при inner-связке оптимизатор более-менее обрабатывает.

Как насчет left?

Поскольку таблица всего одна, далее будем использовать сокращение
U / I - U безиндексных и I индексных чтения из таблицы TEST_TABLE3


Оказывается, условие в where не всегда учитывается должным образом

Например,
select * from test_table3 t1 left join test_table3 t2
     on t1.ID1 = t2.ID2
where t2.id2 = 0

с планом
PLAN JOIN (T1 NATURAL, T2 INDEX (TEST_TABLE3_ID2))

требует всего 10 / 0 чтений - т.е. условие
в where было принято во внимание, и не потребовалось 10 раз читать по индексу T2.

Но если взять

select * from test_table3 t1 left join test_table3 t2
     on t1.ID1 = t2.ID2
where t2.id2 = 3

с планом
PLAN JOIN (T1 NATURAL, T2 INDEX (TEST_TABLE3_ID2)),

то получим 10 /10 чтений - т.е. оптимизатор условие в Where
проигнорировал.
Чтобы этого не случилось, нужно включить t2.id2 = 3 в условие связки

select * from test_table3 t1 left join test_table3 t2
     on t1.ID1 = t2.ID2 and t2.id2 = 3
where t2.id2 is not null

Получаем 10 / 2 чтения.

Что ж, это еще одна причина не писать кривых запросов (а запрос, и в самом деле, кривой), используя left там, где требуется inner.

Пример 4

Так же хорошо оптимизируется нововведение is not distinct from,
как и обычный предикат равенства?

И

select * from test_table3 t1 left join test_table3 t2
     on t1.ID1 = t2.ID2,

и

select * from test_table3 t1 inner join test_table3 t2
     on t1.ID1 is not distinct from t2.ID2

выполняются с одинаковым планом:
PLAN JOIN (T1 NATURAL, T2 INDEX (TEST_TABLE3_ID2))

требуя 10 / 8  чтений.

Но для

select * from test_table3 t1 left join test_table3 t2
     on t1.ID1 = t2.ID2  and t2.id2 = 3

получаем
PLAN JOIN (T1 NATURAL, T2 INDEX (TEST_TABLE3_ID2, TEST_TABLE3_ID1))
и 10/2 чтений

а для

select * from test_table3 t1 left join test_table3 t2
     on t1.ID1 is not distinct from t2.ID2 and t2.id2 = 3

PLAN JOIN (T1 NATURAL, T2 INDEX (TEST_TABLE3_ID2))
и 10/10 чтений.

NULL-ов в тестовой таблице нет, поэтому по идее разницы быть не должно.
Скорее всего, внутри сервера t1.ID1 is not distinct from t2.ID2
раскладывается в (t1.ID1 = t2.id2) OR (t1.ID1 is null and t2.ID2 is null). Попробуем выполнить эту замену в самом запросе

Оказывается,
select * from test_table3 t1 left join test_table3 t2
on ((t1.ID1 = t2.id2) OR (t1.ID1 is null and t2.ID2 is null)) and (t2.id2 = 3)

при плане PLAN JOIN (T1 NATURAL, T2 INDEX (TEST_TABLE4_ID2, TEST_TABLE4_ID2))
требует аж 10/16 чтений

Вручную приводим ее к более оптимальному виду

select * from test_table3 t1 left join test_table3 t2
on ((t1.ID1 = t2.id2) AND (t2.id2 = 3)) OR ((t1.ID1 is null and t2.ID2 is null) and (t2.id2 = 3))

и получаем те же 10/10 чтений.

Поскольку проверка первой части условия связки требует всего 2 индексных чтения, а проверка второй - 0, то можно выдвинуть предположение о проблемах с обработкой OR в данном случае. Тем паче, что похожий запрос

select * from test_table3 t1 inner join test_table3 t2
     on t1.ID1 is not distinct from t2.ID2 and t2.id1 =3

оптимизируется нормально - 10/2.

Пример 5

Сортировка и join

Для запроса

select first 1 *
from test_table3 t1 left join test_table3 t2
     on 1=1
order by t1.id1

получаем план PLAN JOIN (T1 ORDER TEST_TABLE3_ID1, T2 NATURAL)

и 1/1 чтение

Но для эквивалентного ему

select first 1 *
from test_table3 t1 cross join test_table3 t2
order by t1.id1

сортировка по индексу не прокатывает

PLAN SORT (JOIN (T2 NATURAL, T1 NATURAL))
и 110/0 чтений

и смена порядка таблиц в запросе не поможет.

Те же проблемы у inner

select first 1 *
from test_table3 t1 inner join test_table3 t2
     on t2.id2 is null
order by t1.id1

PLAN SORT (JOIN (T1 NATURAL, T2 INDEX (TEST_TABLE3_ID2)))
10/0

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

К счастью, это можно обойти с помощью derived tables.
Например,

select * from
 (select first 1 *
  from test_table3 t1
  order by t1.id1) dt
inner join
  test_table3 t2
     on t2.id2 is null

PLAN JOIN (DT T1 ORDER TEST_TABLE3_ID1, T2 INDEX (TEST_TABLE3_ID2))
1/0

Вот, собственно, и все.
Буду рад замечаниям и поправкам.

С уважением, Евгений

Reply via email to