Как индексировать SQL с несколькими условиями AND, вложенными в OR - PullRequest
0 голосов
/ 02 ноября 2018

Я хочу ускорить следующий sql (стоимость 19685.75). Могу ли я индексировать этот sql, который имеет несколько сложных вложенных условий И, объединяющихся с ИЛИ в операторе WHERE?

SELECT DISTINCT
    ON ("crawler_url"."url") U0."id"
FROM "characteristics_text" U0 LEFT OUTER
JOIN "characteristics_text_urls"
    ON (U0."id" = "characteristics_text_urls"."text_id") LEFT OUTER
JOIN "crawler_url"
    ON ("characteristics_text_urls"."url_id" = "crawler_url"."id")
WHERE ( 
    (
        U0."publication_date" BETWEEN '2018-01-01' AND '2018-12-31'
        AND EXTRACT('month' FROM U0."publication_date") = 10
    )
        OR 
    (
        U0."publication_date" IS NULL
        AND U0."lastmod" BETWEEN '2018-01-01' AND '2018-12-31'
        AND EXTRACT('month' FROM U0."lastmod") = 10
    )
        OR 
    (
        U0."publication_date" IS NULL
        AND U0."lastmod" IS NULL
        AND U0."created_at" BETWEEN '2018-01-01 00:00:00+08:00' AND '2018-12-31 23:59:59.999999+08:00'
        AND EXTRACT('month' FROM U0."created_at" AT TIME ZONE 'Asia/Hong_Kong') = 10
    )
        OR 
    (
        U0."publication_date" >= '2018-08-01'
        AND U0."publication_date" < '2018-10-31'
    )
        OR 
    (
        U0."publication_date" IS NULL
        AND U0."lastmod" >='2018-08-01'
        AND U0."lastmod" < '2018-10-31'
    )
        OR 
    (
        U0."publication_date" IS NULL
        AND U0."lastmod" IS NULL
        AND U0."created_at" >= '2018-07-31 16:00:00+00:00'
        AND U0."created_at" < '2018-10-30 16:00:00+00:00'
    ) 
)
ORDER BY  "crawler_url"."url" ASC, U0."created_at" DESC

Текст таблицы содержит следующие поля и индексы (некоторые другие поля не отображаются)

                                            Table "public.characteristics_text"                                                                                                     
            Column         |           Type           |                             Modifiers                                                                                        
    ------------------------+--------------------------+-------------------------------------------------------------------                                                           
    id                     | integer                  | not null default nextval('characteristics_text_id_seq'::regclass)
    text                   | text                     | 
    created_at             | timestamp with time zone | not null
    lastmod                | date                     | 
    publication_date       | date                     | 
    Indexes:
        "characteristics_text_pkey" PRIMARY KEY, btree (id)
        "characteristics_text_fde81f11" btree (created_at)
        "characteristics_text_lastmod_3bff34c2_uniq" btree (lastmod)
        "characteristics_text_publication_date_772c1bda_uniq" btree (publication_date)
        "characteristics_text_publication_date_c6311385_idx" btree (publication_date, lastmod, created_at)

Я добавил три отдельных индекса для create_at, lastmod и publishing_date; и один индекс из нескольких столбцов для этих полей.

Но в запросе postgres EXPAIN это предложение where все еще использует Seq Scan , но не Index Scan .

->  Seq Scan on characteristics_text u0  (cost=0.00..19685.75 rows=14535 width=12)
    Filter: (
            (
                (publication_date >= '2018-01-01'::date) AND 
                (publication_date <= '2018-12-31'::date) AND 
                (
                        date_part(
                            'month'::text, (publication_date)::timestamp without time zone
                ) = 10::double precision)
            ) OR 

                ((publication_date IS NULL) AND (lastmod >= '2018-01-01'::date) AND (lastmod <= '2018-12-31'::date) AND (date_part('month'::text, (lastmod)::timestamp without time zone) = 10::double precision)) OR ((publication_date IS NULL) AND (lastmod IS NULL) AND (created_at >= '2017-12-31 16:00:00+00'::timestamp with time zone) AND (created_at <= '2018-12-31 15:59:59.999999+00'::timestamp with time zone) AND (date_part('month'::text, timezone('Asia/Hong_Kong'::text, created_at)) = 10::double precision)) OR ((publication_date >= '2018-08-01'::date) AND (publication_date < '2018-10-31'::date)) OR ((publication_date IS NULL) AND (lastmod >= '2018-08-01'::date) AND (lastmod < '2018-10-31'::date)) OR ((publication_date IS NULL) AND (lastmod IS NULL) AND (created_at >= '2018-07-31 16:00:00+00'::timestamp with time zone) AND (created_at < '2018-10-30 16:00:00+00'::timestamp with time zone))
)

Мои вопросы:
1. Можно ли заставить postgres использовать сканирование индекса для этого сложного предложения SELECT?
2. Нужно ли создавать один индекс из нескольких столбцов для каждого предложения AND? Например создание индекса (publication_date, lastmod) по этой причине?

    (
        U0."publication_date" IS NULL
        AND U0."lastmod" BETWEEN '2018-01-01' AND '2018-12-31'
        AND EXTRACT('month' FROM U0."lastmod") = 10
    )
  1. Работает ли индекс при поиске IS NULL? Нужно ли индексировать поле поиска IS NULL?

ОБНОВЛЕНО 4nov2018

Когда я пытаюсь свернуть запрос, проверяя поля по одному, поля publication_date и last_mod запускают сканирование индекса по отдельности, тогда как created_at не может:

Это потому, что created_at является меткой времени? Но почему индекс не работает для отметки времени?

explain SELECT DISTINCT
    ON ("crawler_url"."url") U0."id"
FROM "characteristics_text" U0 LEFT OUTER
JOIN "characteristics_text_urls"
    ON (U0."id" = "characteristics_text_urls"."text_id") LEFT OUTER
JOIN "crawler_url"
    ON ("characteristics_text_urls"."url_id" = "crawler_url"."id")
WHERE ( 
(
        U0."created_at" BETWEEN '2018-01-01 00:00:00+08:00' AND '2018-12-31 23:59:59.999999+08:00'
        AND EXTRACT('month' FROM U0."created_at" AT TIME ZONE 'Asia/Hong_Kong') = 10
    )   
)
ORDER BY  "crawler_url"."url" ASC, U0."created_at" DESC



Unique  (cost=18004.05..18006.01 rows=393 width=86)
->  Sort  (cost=18004.05..18005.03 rows=393 width=86)
        Sort Key: crawler_url.url, u0.created_at
        ->  Nested Loop Left Join  (cost=0.71..17987.11 rows=393 width=86)
            ->  Nested Loop Left Join  (cost=0.42..17842.25 rows=393 width=16)
                    ->  Seq Scan on characteristics_text u0  (cost=0.00..15467.37 rows=393 width=12)
                        Filter: ((created_at >= '2017-12-31 16:00:00+00'::timestamp with time zone) AND (created_at <= '2018-12-31 15:59:59.999999+00'::timestamp with time zone) AND (date_part('month'::text, timezone('Asia/Hong_Kong'::text, created_at)) = 10::double precision))
                    ->  Index Scan using characteristics_text_urls_65eb77fe on characteristics_text_urls  (cost=0.42..6.03 rows=1 width=8)
                        Index Cond: (u0.id = text_id)
            ->  Index Scan using crawler_url_pkey on crawler_url  (cost=0.29..0.36 rows=1 width=78)
                    Index Cond: (characteristics_text_urls.url_id = id)

publication_date кажется триггер индекса сканирования:

(
    U0."publication_date" IS NULL
    AND U0."lastmod" >='2018-08-01'
    AND U0."lastmod" < '2018-10-31'
)


Unique  (cost=17053.26..17085.63 rows=6473 width=86)
->  Sort  (cost=17053.26..17069.44 rows=6473 width=86)
        Sort Key: crawler_url.url, u0.created_at
        ->  Nested Loop Left Join  (cost=11130.73..16643.51 rows=6473 width=86)
            ->  Hash Right Join  (cost=11130.44..14257.63 rows=6473 width=16)
                    Hash Cond: (characteristics_text_urls.text_id = u0.id)
                    ->  Seq Scan on characteristics_text_urls  (cost=0.00..1858.01 rows=120601 width=8)
                    ->  Hash  (cost=11049.53..11049.53 rows=6473 width=12)
                        ->  Bitmap Heap Scan on characteristics_text u0  (cost=186.95..11049.53 rows=6473 width=12)
                                Recheck Cond: ((publication_date IS NULL) AND (lastmod >= '2018-08-01'::date) AND (lastmod < '2018-10-31'::date))
                                ->  Bitmap Index Scan on characteristics_text_publication_date_c6311385_idx  (cost=0.00..185.33 rows=6473 width=0)
                                    Index Cond: ((publication_date IS NULL) AND (lastmod >= '2018-08-01'::date) AND (lastmod < '2018-10-31'::date))
            ->  Index Scan using crawler_url_pkey on crawler_url  (cost=0.29..0.36 rows=1 width=78)
                    Index Cond: (characteristics_text_urls.url_id = id)

Ответы [ 3 ]

0 голосов
/ 02 ноября 2018

ОК, полное сканирование таблицы (seq_scan) может на самом деле быть быстрее, чем многократное сканирование индекса. Это зависит от конкретной «селективности» ваших условий фильтрации.

Прежде всего, ваше предложение WHERE имеет шесть условий фильтрации, OR ed. Это означает, что если вы хотите использовать индексы, PostgreSQL должен будет использовать его 6 раз, а затем выполнить «ИЛИ индекса», чтобы объединить результаты. Это может быть не дешево.

Итак, во-первых, вам нужно знать, какова ожидаемая селективность каждого из 6 условий фильтрации. Это количество выбранных строк по сравнению с общим числом строк в таблице. Сделай это; Несколько простых запросов SQL дадут вам ответ. Разместите ответ здесь.

Теперь, если сумма всех шести селективностей составляет более 5%, тогда полное сканирование таблицы (алгоритм, который вы используете сейчас) выполняется быстрее. Не беспокойтесь о индексах.

В противном случае может помочь следующий индекс:

create index ix1 on characteristics_text (
  publication_date, 
  lastmod,
  created_at,
  1);
0 голосов
/ 06 ноября 2018

На весь 2018 год приходится 60% из 100 000 записей, в результате чего база данных использовала сканирование seq. Смена BEWEEN на весь год до одного месяца дает сканирование индекса.

  AND U0."created_at" >= '2018-10-01 00:00:00+00:00'
    AND U0."created_at" <= '2018-10-31 23:59:59.999999+00:00')

Полный SQL:

SELECT DISTINCT
    ON ("crawler_url"."url") U0."id"
FROM "characteristics_text" U0 LEFT OUTER
JOIN "characteristics_text_urls"
    ON (U0."id" = "characteristics_text_urls"."text_id") LEFT OUTER
JOIN "crawler_url"
    ON ("characteristics_text_urls"."url_id" = "crawler_url"."id")
WHERE (
        (U0."publication_date" >= '2018-10-01'
        AND U0."publication_date" <= '2018-11-01')

        OR (U0."publication_date" IS NULL
        AND U0."lastmod" >= '2018-10-01'
        AND U0."lastmod" <= '2018-11-01'
        )

        OR 

        (U0."publication_date" IS NULL
        AND U0."lastmod" IS NULL
        AND U0."created_at" >= '2018-10-01 00:00:00+00:00'
        AND U0."created_at" <= '2018-10-31 23:59:59.999999+00:00')

        OR 

        (U0."publication_date" >= '2018-08-01'
        AND U0."publication_date" < '2018-10-31')

        OR 

        (U0."publication_date" IS NULL
        AND U0."lastmod" >= '2018-08-01'
        AND U0."lastmod" < '2018-10-31')

        OR 

        (U0."publication_date" IS NULL
        AND U0."lastmod" IS NULL
        AND U0."created_at" >= '2018-07-31 16:00:00+00:00'
        AND U0."created_at" < '2018-10-30 16:00:00+00:00')
    )
ORDER BY  "crawler_url"."url" ASC

В выражении EXPLAIN показано сканирование индекса для каждого условия AND, поэтому всего имеется 6 сканирований индекса.

Unique  (cost=22885.16..22962.39 rows=15446 width=88)
->  Sort  (cost=22885.16..22923.77 rows=15446 width=88)
        Sort Key: crawler_url.url
        ->  Hash Right Join  (cost=18669.29..21068.51 rows=15446 width=88)
            Hash Cond: (crawler_url.id = characteristics_text_urls.url_id)
            ->  Seq Scan on crawler_url  (cost=0.00..1691.88 rows=55288 width=88)
            ->  Hash  (cost=18476.21..18476.21 rows=15446 width=8)
                    ->  Hash Right Join  (cost=14982.09..18476.21 rows=15446 width=8)
                        Hash Cond: (characteristics_text_urls.text_id = u0.id)
                        ->  Seq Scan on characteristics_text_urls  (cost=0.00..1907.25 rows=115525 width=8)
                        ->  Hash  (cost=14789.01..14789.01 rows=15446 width=4)
                                ->  Bitmap Heap Scan on characteristics_text u0  (cost=516.57..14789.01 rows=15446 width=4)
                                    Recheck Cond: (((publication_date >= '2018-10-01'::date) AND (publication_date <= '2018-11-01'::date)) OR ((publication_date IS NULL) AND (lastmod >= '2018-10-01'::date) AND (lastmod <= '2018-11-01'::date)) OR ((publication_date IS NULL) AND (lastmod IS NULL) AND (created_at >= '2018-10-01 00:00:00+00'::timestamp with time zone) AND (created_at <= '2018-10-31 23:59:59.999999+00'::timestamp with time zone)) OR ((publication_date >= '2018-08-01'::date) AND (publication_date < '2018-10-31'::date)) OR ((publication_date IS NULL) AND (lastmod >= '2018-08-01'::date) AND (lastmod < '2018-10-31'::date)) OR ((publication_date IS NULL) AND (lastmod IS NULL) AND (created_at >= '2018-07-31 16:00:00+00'::timestamp with time zone) AND (created_at < '2018-10-30 16:00:00+00'::timestamp with time zone)))
                                    ->  BitmapOr  (cost=516.57..516.57 rows=16081 width=0)
                                            ->  Bitmap Index Scan on characteristics_text_publication_date_772c1bda_uniq  (cost=0.00..4.53 rows=11 width=0)
                                                Index Cond: ((publication_date >= '2018-10-01'::date) AND (publication_date <= '2018-11-01'::date))
                                            ->  Bitmap Index Scan on characteristics_text_publication_date_c6311385_idx  (cost=0.00..6.49 rows=166 width=0)
                                                Index Cond: ((publication_date IS NULL) AND (lastmod >= '2018-10-01'::date) AND (lastmod <= '2018-11-01'::date))
                                            ->  Bitmap Index Scan on characteristics_text_publication_date_c6311385_idx  (cost=0.00..14.61 rows=413 width=0)
                                                Index Cond: ((publication_date IS NULL) AND (lastmod IS NULL) AND (created_at >= '2018-10-01 00:00:00+00'::timestamp with time zone) AND (created_at <= '2018-10-31 23:59:59.999999+00'::timestamp with time zone))
                                            ->  Bitmap Index Scan on characteristics_text_publication_date_772c1bda_uniq  (cost=0.00..74.61 rows=3419 width=0)
                                                Index Cond: ((publication_date >= '2018-08-01'::date) AND (publication_date < '2018-10-31'::date))
                                            ->  Bitmap Index Scan on characteristics_text_publication_date_c6311385_idx  (cost=0.00..108.20 rows=3503 width=0)
                                                Index Cond: ((publication_date IS NULL) AND (lastmod >= '2018-08-01'::date) AND (lastmod < '2018-10-31'::date))
                                            ->  Bitmap Index Scan on characteristics_text_publication_date_c6311385_idx  (cost=0.00..284.95 rows=8569 width=0)
                                                Index Cond: ((publication_date IS NULL) AND (lastmod IS NULL) AND (created_at >= '2018-07-31 16:00:00+00'::timestamp with time zone) AND (created_at < '2018-10-30 16:00:00+00'::timestamp with time zone))
0 голосов
/ 02 ноября 2018

Я сомневаюсь, что вы получите индекс, который будет полезен здесь. Что вы можете сделать, это разбить этот запрос на 4 или 5 частей, а затем использовать UNION, чтобы снова объединить результаты. (UNION удалит дубликаты, а UNION ALL вернет все строки).

UNION - довольно дорогая операция, поэтому нужно учитывать, сколько строк это возвращает. Использование индекса может принести большую эффективность, чем потеряет UNION, если удалит достаточное количество строк. Если возвращено много строк, ваша текущая форма примерно так же хороша, как и собирается.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...