Почему Postgres предпочитает последовательное сканирование частичному индексу с явным условием where?

У меня есть простой запрос, например select * from xxx where col is not null limit 10. Я не знаю, почему Postgres предпочитает последовательное сканирование, которое намного медленнее, чем частичный индекс (я проанализировал таблицу). Как отладить такую ​​проблему?

В таблице более 4 миллионов строк. И около 350 000 строк выполнено pid is not null.

Я думаю, что может быть что-то не так с оценкой стоимости. Стоимость последовательного сканирования ниже, чем сканирование индекса. Но как в этом копаться?

У меня есть предположение, но я не уверен в этом. Ненулевые строки занимают около 10% от общего числа строк. Это означает, что он может получить 10 ненулевых строк при последовательном сканировании 100 строк. И он думает, что стоимость последовательного сканирования 100 строк ниже, чем индексное сканирование 10 строк, а затем случайная выборка 10 полных строк. Это?

> \d data_import
+--------------------+--------------------------+----------------------------------------------------------------------------+
| Column             | Type                     | Modifiers                                                                  |
|--------------------+--------------------------+----------------------------------------------------------------------------|
| id                 | integer                  |  not null default nextval('data_import_id_seq'::regclass) |
| name               | character varying(64)    |                                                                            |
| market_activity_id | integer                  |  not null                                                                  |
| hmsr_id            | integer                  |  not null default (-1)                                                     |
| site_id            | integer                  |  not null default (-1)                                                     |
| hmpl_id            | integer                  |  not null default (-1)                                                     |
| hmmd_id            | integer                  |  not null default (-1)                                                     |
| hmci_id            | integer                  |  not null default (-1)                                                     |
| hmkw_id            | integer                  |  not null default (-1)                                                     |
| creator_id         | integer                  |                                                                            |
| created_at         | timestamp with time zone |                                                                            |
| updated_at         | timestamp with time zone |                                                                            |
| bias               | integer                  |                                                                            |
| pid                | character varying(128)   |  default NULL::character varying                                           |
+--------------------+--------------------------+----------------------------------------------------------------------------+
Indexes:
    "data_import_pkey" PRIMARY KEY, btree (id)
    "unique_hmxx" UNIQUE, btree (site_id, hmsr_id, hmpl_id, hmmd_id, hmci_id, hmkw_id) WHERE pid IS NULL
    "data_import_pid_idx" UNIQUE, btree (pid) WHERE pid IS NOT NULL
    "data_import_created_at_idx" btree (created_at)
    "data_import_hmsr_id" btree (hmsr_id)
    "data_import_updated_at_idx" btree (updated_at)



> set enable_seqscan to false;
apollon> explain (analyse, verbose)  select * from data_import where pid is not null limit 10
+-------------------------------------------------------------------------------------------------------------------------------------------------------------
| QUERY PLAN
|-------------------------------------------------------------------------------------------------------------------------------------------------------------
| Limit  (cost=0.42..5.68 rows=10 width=84) (actual time=0.059..0.142 rows=10 loops=1)
|   Output: id, name, market_activity_id, hmsr_id, site_id, hmpl_id, hmmd_id, hmci_id, hmkw_id, creator_id, created_at, updated_at, bias, pid
|   ->  Index Scan using data_import_pid_idx on public.data_import  (cost=0.42..184158.08 rows=350584 width=84) (actual time
|         Output: id, name, market_activity_id, hmsr_id, site_id, hmpl_id, hmmd_id, hmci_id, hmkw_id, creator_id, created_at, updated_at, bias, pid
|         Index Cond: (data_import.pid IS NOT NULL)
| Planning time: 0.126 ms
| Execution time: 0.177 ms
+-------------------------------------------------------------------------------------------------------------------------------------------------------------
EXPLAIN
Time: 0.054s

> set enable_seqscan to true;
> explain (analyse, verbose)  select * from data_import where pid is not null limit 10
+---------------------------------------------------------------------------------------------------------------------------------------------------+
| QUERY PLAN                                                                                                                                        |
|---------------------------------------------------------------------------------------------------------------------------------------------------|
| Limit  (cost=0.00..2.37 rows=10 width=84) (actual time=407.042..407.046 rows=10 loops=1)                                                          |
|   Output: id, name, market_activity_id, hmsr_id, site_id, hmpl_id, hmmd_id, hmci_id, hmkw_id, creator_id, created_at, updated_at, bias, pid       |
|   ->  Seq Scan on public.data_import  (cost=0.00..83016.60 rows=350584 width=84) (actual time=407.041..407.045 rows=10 loops=1)  |
|         Output: id, name, market_activity_id, hmsr_id, site_id, hmpl_id, hmmd_id, hmci_id, hmkw_id, creator_id, created_at, updated_at, bias, pid |
|         Filter: (data_import.pid IS NOT NULL)                                                                                    |
|         Rows Removed by Filter: 3672502                                                                                                           |
| Planning time: 0.116 ms                                                                                                                           |
| Execution time: 407.078 ms                                                                                                                        |
+---------------------------------------------------------------------------------------------------------------------------------------------------+
EXPLAIN
Time: 0.426s
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
5
0
990
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ваша проблема заключается в

Rows Removed by Filter: 3672502

PostgreSQL знает распределение значений и то, как они коррелируют с физической структурой таблицы, но не знает, что все строки в начале таблицы имеют NULL для pid.

Если бы значения NULL были распределены равномерно, последовательное сканирование быстро нашло бы 10 совпадений и остановилось, но в нынешнем виде ему нужно прочитать 3672512 строк, чтобы найти 10 совпадающих.

Если вы добавите ORDER BY pid (даже если вам это не нужно) перед LIMIT, оптимизатор сделает все правильно.

Он знает распределение значений, но не NULL.

Laurenz Albe 22.02.2019 07:46

Пожалуйста, прочитайте, что я написал. Он знает процент, а не распределение. Рекомендую посмотреть pg_stats, особенно null_frac и correlation, тогда многое станет понятнее.

Laurenz Albe 22.02.2019 07:55

Большое спасибо. Думаю, я угадал. Есть ли какой-нибудь инструмент или расширение, чтобы показать подробный расчет стоимости диска и стоимости процессора?

zxdvd 22.02.2019 08:40

Другие вопросы по теме