Nano Hash - криптовалюты, майнинг, программирование

Как индексировать SQL с несколькими условиями И, вложенными в ИЛИ

Хочу ускорить следующий sql (стоимость 19685.75). Могу ли я проиндексировать этот sql, который имеет несколько сложных вложенных условий AND, сочетающихся с OR в выражении 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)

Я добавил три отдельных индекса для created_at, lastmod и publish_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 быть индексом?

ОБНОВЛЕНО 4 ноября 2018 г.

Когда я пытаюсь минимизировать запрос, проверяя поля одно за другим, поля 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)

  • Начните с сокращения вашего запроса до простого SELECT для этой таблицы с одним из ваших условий WHERE. Посмотрите, использует ли он index. Если это так, продолжайте добавлять, пока не выясните, какой из них вызывает последовательное сканирование, и продолжайте оттуда. 02.11.2018
  • @eurotrash кажется, что планировщик автоматически переключается на последовательное сканирование для created_at, см. выше раздел ОБНОВЛЕНО 4 ноября 2018 г. 04.11.2018
  • Две мысли. Во-первых, избавьтесь от вызова EXTRACT, это уже то, что не будет работать с вашим индексом. У вас должно остаться только U0."created_at" BETWEEN.... Во-вторых, вы ищете created_at в течение всего года. Это большой процент записей в таблице? Если это так, возможно, поэтому он не использует индекс. Если нет, я сильно подозреваю, что это связано с часовыми поясами. Я сам никогда не использую часовые пояса, поэтому не могу посоветовать здесь, но я знаю, что запросы, связанные с часовыми поясами, не всегда могут использовать индексы, как ожидалось. 04.11.2018
  • @eurotrash, вы правы, за весь 2018 год приходится 60% из 100 000 записей. Мне просто нужны данные за октябрь. Я написал этот странный оператор EXTRACT, потому что он генерируется из функции поиска месяца django docs.djangoproject.com/en/dev/ref/models/querysets/#month. Должен был использовать BETWEEN '2018-10-01 00:00:00+08:00' AND '2018-12-31 23:59:59.999999+08:00' 06.11.2018

Ответы:


1

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

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

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

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

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

create index ix1 on characteristics_text (
  publication_date, 
  lastmod,
  created_at,
  1);
02.11.2018
  • да, весь 2018 год имеет 60% из 100 000 записей. Изменение с BEWEEN всего года на BETWEEN '2018-10-01 00:00:00+08:00' AND '2018-12-31 23:59:59.999999+08:00' дает сканирование индекса. 06.11.2018

  • 2

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

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

    02.11.2018

    3

    Весь 2018 год имеет 60% из 100 000 записей, что заставило базу данных использовать последовательное сканирование. Переход от МЕЖДУ целым годом к одному месяцу приводит к сканированию индекса.

      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))
    
    06.11.2018
    Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..