2015-03-17 3 views
1

Если я хочу выбрать 0,5% строк или даже 5% строк из следующей таблицы с помощью ПК, планировщик запросов правильно решит использовать индекс PK. Вот таблица:Почему PostgreSql не использует индекс PK?

create table weather as 
with numbers as(
select generate_series as id from generate_series(0,1048575)) 
select id, 
50 + 50*sin(id) as temperature_in_f, 
50 + 50*sin(id) as humidity_in_percent 
from numbers; 

alter table weather 
add constraint pk_weather primary key(id); 

vacuum analyze weather; 

В статы уточненный, и следующий запрос действительно использует индекс PK:

explain analyze select sum(w.id), sum(humidity_in_percent), count(*) 
from weather as w 
where w.id between 1 and 66720; 

Предположим, однако, что мы должны присоединиться к этому столу другой, гораздо меньше, один:

create table lightnings 
as 
select id as weather_id 
from weather 
where humidity_in_percent between 99.99 and 100; 

alter table lightnings 
add constraint pk_lightnings 
primary key(weather_id); 

analyze lightnings; 

Вот мой присоединиться, в четырех логически эквивалентных формах:

explain analyze select sum(w.id), count(*) from weather as w 
where w.humidity_in_percent between 99.99 and 100 
and exists(select * from lightnings as l 
    where l.weather_id=w.id); 

explain analyze select sum(w.id), count(*) 
from weather as w 
join lightnings as l 
    on l.weather_id=w.id 
where w.humidity_in_percent between 99.99 and 100; 

explain analyze select sum(w.id), count(*) 
from lightnings as l 
join weather as w 
    on l.weather_id=w.id 
where w.humidity_in_percent between 99.99 and 100; 

-- replaced explicit join with where clause 
explain analyze select sum(w.id), count(*) 
from lightnings as l, weather as w 
where w.humidity_in_percent between 99.99 and 100 
and l.weather_id=w.id; 

К сожалению, планировщик запросова прибегает к сканируя всю погодную таблицу: оценка

"Aggregate (cost=22645.68..22645.69 rows=1 width=4) (actual time=167.427..167.427 rows=1 loops=1)" 
" -> Hash Join (cost=180.12..22645.52 rows=32 width=4) (actual time=2.500..166.444 rows=6672 loops=1)" 
"  Hash Cond: (w.id = l.weather_id)" 
"  -> Seq Scan on weather w (cost=0.00..22407.64 rows=5106 width=4) (actual time=0.013..158.593 rows=6672 loops=1)" 
"    Filter: ((humidity_in_percent >= 99.99::double precision) AND (humidity_in_percent <= 100::double precision))" 
"    Rows Removed by Filter: 1041904" 
"  -> Hash (cost=96.72..96.72 rows=6672 width=4) (actual time=2.479..2.479 rows=6672 loops=1)" 
"    Buckets: 1024 Batches: 1 Memory Usage: 235kB" 
"    -> Seq Scan on lightnings l (cost=0.00..96.72 rows=6672 width=4) (actual time=0.009..0.908 rows=6672 loops=1)" 
"Planning time: 0.326 ms" 
"Execution time: 167.581 ms" 

планировщик запросов на сколько строк в таблице погоды будет выбраны строки = 5106. Это более или менее близко к точному значению 6672. Если я выберу это небольшое количество строк в таблице погоды через id, используется индекс PK. Если я выберу одну и ту же сумму через соединение с другой таблицей, планировщик запросов отправляется на сканирование таблицы.

Что мне не хватает?

select version() 
"PostgreSQL 9.4.0" 

Edit: если я удалю условие на влажность, планировщик запросов правильно распознает, что условие на weather.id довольно избирательно, и решает использовать индекс на ПК:

explain analyze select sum(w.id), count(*) from weather as w 
where exists(select * from lightnings as l 
    where l.weather_id=w.id); 
"Aggregate (cost=14677.84..14677.85 rows=1 width=4) (actual time=37.200..37.200 rows=1 loops=1)" 
" -> Nested Loop (cost=0.42..14644.48 rows=6672 width=4) (actual time=0.022..36.189 rows=6672 loops=1)" 
"  -> Seq Scan on lightnings l (cost=0.00..96.72 rows=6672 width=4) (actual time=0.011..0.868 rows=6672 loops=1)" 
"  -> Index Only Scan using pk_weather on weather w (cost=0.42..2.17 rows=1 width=4) (actual time=0.005..0.005 rows=1 loops=6672)" 
"    Index Cond: (id = l.weather_id)" 
"    Heap Fetches: 0" 
"Planning time: 0.321 ms" 
"Execution time: 37.254 ms" 

Еще добавление условия полностью путает планировщик запросов.

+0

Не могли бы вы показать нам свой файл конфигурации? Особенно random_page_cost, это оказывает огромное влияние на последовательное чтение и индексное сканирование –

+0

Я убедился, что set random_page_cost = 4; - Никакой разницы. – AlexC

+0

Более низкое значение для random_page_cost выгодно сканирует индекс, ваш первичный ключ. установите random_page_cost равным 2.6; дает мне сканирование индекса по всем примерам запросов. –

ответ

1

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

Посмотрите на линии на плане:

Hash Join (cost=180.12..22645.52 rows=32 width=4) (actual time=2.500..166.444 rows=6672 loops=1)" 

Он ожидает 32 строки в результате объединения, но на самом деле 6672 результат.

Во всяком случае, это в значительной степени имеет возможность:

  1. полную проверка на меньшем столе, и поиск индекса на большем, с предикатом используется для фильтрации строк, проведенных после объединения (и ожидая, что большая часть строк будет отфильтрована).
  2. Полное сканирование в обеих таблицах с удалением строк предикатом в большой таблице и хеш-соединение результата.
  3. Сканирование большой таблицы с удалением строк предикатом и поиск индекса в таблице меньшего размера, которая может не найти значение.

Вторая из них считается самой низкой стоимостью, и я думаю, что это правильно сделать на основании доказательств, которые она имеет, поскольку хеш-соединения очень эффективны для объединения многих строк.

Конечно, вероятно, было бы более эффективным разместить индекс в погоде (влажность_интерфейс, id) в данном конкретном случае, но я подозреваю, что это модифицированная версия вашей реальной ситуации (сумма столбца id?). поэтому конкретные рекомендации могут быть неприменимы.

+0

+1 Благодарим вас за объяснение. Это очень странно. Таблица погоды имеет 1M строк, и QP это знает. QP ожидает только 32 строки - почему бы не использовать индекс на PK для извлечения этих 32 строк? – AlexC

+0

Он считает, что ему по-прежнему приходится извлекать 6 000-ичных строк данных таблицы с помощью индекса, а затем применять фильтр к строкам, когда они извлекаются, независимо от того, выбраны ли все, кроме 32. Такая ситуация может быть сложной для оптимизации запросов, поскольку критическая ситуация может быть связана с тем, нужно ли управлять запросом из меньшей таблицы освещения или отфильтрованной таблицы погоды.Мощность объединения также сложна для расчета, особенно когда требуемые наборы данных из двух таблиц коррелированы - оптимизатор этого не знает. –

+0

Да, «это модифицированная версия моей реальной ситуации» - и также упрощена. – AlexC

0

Я считаю, что различия между вашим первым и вторым запросами, которые используют индекс, а остальные нет, находятся в предложении where.

В первом запросе предложение where находится на w.id, которое индексируется.

В других 3, эффективное предложение where находится на w.humidity_in_percent. Я проверил следующее ...

create index wh_idx on weather(humidity_in_percent); 

explain analyse select sum(w.id), count(*) from weather as w 
where w.humidity_in_percent between 99.99 and 100 
and exists(select * from lightnings as l 
    where l.weather_id=w.id); 

и получить гораздо лучший план. Я попытался опубликовать фактический план, но у меня возникли проблемы с форматированием его для правильного отображения, извините.

+0

Да, добавление индекса влажности помогает. Мой вопрос в том, почему полезный индекс для id не используется. – AlexC

+0

Опять же, моя догадка заключается в том, что вы не используете индекс, потому что ваше предложение where не находится на w.id (индекс), но находится на w.humidity_in_percent (не индексируется). Я только добавил индекс для проверки моего утверждения. Если вы хотите использовать индекс, вам нужно надеть поля в свой поиск. – StAlphonzo

+0

Я не понимаю, что вы подразумеваете под термином «where where is not on w.id» - IMO условие EXISTS является частью предложения WHERE, и оно определенно находится в столбце w.id. – AlexC

Смежные вопросы