Странное поведение плана запроса postgresql

Я должен выбрать строки в одной таблице (таблица_A довольно маленькая, ‹ 10 тыс. строк), которым нет соответствующих строк в другой таблице (таблица_B более 500 тыс. строк) на основе столбца_X (таблица_B имеет индекс btree для этого столбца). Если я использую следующий запрос:

select a.column1,
    a.column2,
    a.column3,
    a.column_X,
    b.column_X
from table_A a
left outer join table_B b on a.column_X = b.column_X
where a.column_X <> 0
    and b.column_X is null

запрос (168 результирующих строк) выполняется примерно за 600 мс. Если, с другой стороны, я попробую другой запрос:

select column1,
    column2,
    column3,
    column_X
from table_A
where column_X not in (
        select column_X
        from table_B
        where column_X is not null
        )
    and column_X <> 0

для извлечения тех же 168 строк требуется около 8 минут. column_X имеет тип bigint, и приведение, похоже, не имеет значения (во втором запросе индекс никогда не используется). Есть идеи?


person Andrea Dalseno    schedule 14.02.2017    source источник
comment
пожалуйста, поделитесь EXPLAIN ANALYZE результатом — посетите веб-сайт explain.depesz.com, чтобы поделиться им.   -  person Pavel Stehule    schedule 14.02.2017
comment
Примечание: where column_X is not null в подзапросе не нужен.   -  person wildplasser    schedule 15.02.2017
comment
@wildplasser, если column_X допускает значение NULL, то оно действительно необходимо. Без него, если столбец фактически содержит значение NULL, выражение NOT IN (и любое выражение IN) приведет к NULL (это требуется стандартом ANSI SQL). И именно поэтому PostgreSQL не превратит этот запрос в антисоединение.   -  person pozs    schedule 15.02.2017
comment
... напр. column_X NOT IN (1, NULL) эквивалентен column_X <> 1 AND column_X <> NULL, который эквивалентен column_X <> 1 AND NULL, который всегда будет NULL.   -  person pozs    schedule 15.02.2017
comment
ИМХО column_x NOT IN(1, NULL) эквивалентен NOT ( column_x = 1 OR column_x = NULL), который стал бы NOT (columns_x = 1 OR NULL) ergo: даже если подзапрос возвращал нули, они никогда не могли сравниваться равными какому-либо значению (нулевому или ненулевому)   -  person wildplasser    schedule 15.02.2017


Ответы (1)


Подвыборка NOT IN оптимизирована намного хуже, чем любая другая. Из-за разной семантики PostgreSQL не может использовать антисоединение. Если можете, не используйте этот шаблон. Вместо этого используйте NOT EXISTS или внешнее соединение.

person Pavel Stehule    schedule 14.02.2017