Помогите ускорить PostgreSQL запрос - PullRequest
3 голосов
/ 30 августа 2011

Я пытаюсь максимально ускорить этот запрос.Это не очень медленно, но мне нужно, чтобы это было как можно быстрее.

SELECT name
FROM (
  SELECT cities.name || ', ' || regions.name || ', ' || countries.code AS name
  FROM cities
  INNER JOIN regions ON regions.id = cities.region_id
  INNER JOIN countries ON countries.id = regions.country_id
) AS t1
GROUP BY name
HAVING LOWER(name) ILIKE 'asheville%'
ORDER BY name ASC
LIMIT 10;

Эти индексы существуют:

UNIQUE INDEX index_cities_on_name_and_region_id ON cities USING btree (name, region_id)
UNIQUE INDEX index_countries_on_code ON countries USING btree (code)
UNIQUE INDEX index_countries_on_name ON countries USING btree (name)
UNIQUE INDEX index_regions_on_code_and_country_id ON regions USING btree (code, country_id)

таблица городов содержит 248016 записей.Таблица стран содержит 252 записи.Таблица регионов содержит 4005 записей.

Вот объяснение запроса: http://explain.depesz.com/s/fWe

Любая помощь будет принята с благодарностью.По сути, я просто ищу предложения или, может быть, указываю на то, что, возможно, пропустил.

Ответы [ 4 ]

4 голосов
/ 30 августа 2011

В вашем подзапросе вы должны вернуть и name, который вы уже возвращаете, и cities.name as cname.Затем вы должны сделать ilike на cname вместо name.Проблема в том, что сейчас PostgreSQL не может рассчитывать на то, что, поскольку 'ashville%' не содержит запятых, он может просто посмотреть на название города в подзапросе, поэтому он действительно должен(и, основываясь на вашем объяснении) итерируйте и постройте каждую возможную строку, чтобы выполнить этот последний фильтр.Если вы вернете cities.name обратно к верхнему запросу, это значительно улучшит производительность, поскольку сейчас он серьезно не может использовать ни один из тех индексов, которые у вас есть.

На самом деле, вы должны пройти весь путь здесь,и просто удалите конкатенацию строк внутри запроса и верните то, что вы действительно хотели: select cities.name as city, regions.name as region, countries.code as country, и измените сортировку на order by t1.city, t1.region, t1.country.

Кроме того, вы действительно запрашиваете города, которыеиметь 'ashville%', или это просто косвенный способ поиска городов, которые 'ashville', но вам приходится иметь дело с разграничением запятых внутри?Затем, снаружи, используйте lower(t1.city) = 'ashville' (обратите внимание, что =: lower(x) ilike 'lower' бессмысленно медленен).

Кроме того, вам нужно исправить эти индексы: то, что вы действительно хотите, это create index whatever on cities((lower(name))), так как это то, чтовы на самом деле ищете, а не name: вы не сможете использовать эти индексы, если вы ищете что-то, что не имеет отношения к тому, что у вас есть в индексе.

(Выможно было бы взглянуть на order by name позже и подумать, что оно больше не будет ускоряться, но это нормально: цель здесь - быстро отфильтровать от множества возможных мест до крошечного набора тех, которыми вы собираетесь управлятьна; то, что осталось, может быть быстро отсортировано в памяти, поскольку вы, вероятно, имеете дело с 10-20 результатами.)

Из-за этого, поскольку regions.id и countries.id, вероятно, primary key s,другие индексы можно удалить, если они существуют только для этого запроса.

Наконец, выровняйте запрос до одного уровня, удалите group by и замените его на distinct.Проблема в том, что мы хотим убедиться, что не заставляем PostgreSQL генерировать полный набор перед попыткой фильтрации: мы хотим убедиться, что у него достаточно знаний о цели, чтобы иметь возможность использовать индекс города для быстрого сканирования напрямуюв города, которые могут совпадать, и , а затем переходят к заполнению информации о регионе и стране.

(PostgreSQL, как правило, очень очень хорош в этом, дажечерез подзапрос, но поскольку у нас есть предложение group by через having, я могу видеть ситуации, когда он больше не сможет выводить.)

(правка) На самом деле, подождите: выиметь уникальный индекс на cities (name, region_id), поэтому вам даже не нужен distinct ... все, что он делает, это делает запрос бессмысленно более сложным.Я просто удалил его из запроса: результат будет таким же, так как вы не сможете получить результат, когда у вас будет один и тот же город в одном регионе / стране, который будет возвращен дважды.

select
    cities.name as city,
    regions.name as region,
    countries.code as country
from cities
join regions on
    regions.id = cities.region_id
join countries on
    countries.id = regions.country_id
where
    lower(cities.name) = 'asheville'
order by
    cities.name,
    regions.name,
    countries.code
limit 10;

create index "cities(lower(name))" on cities ((lower(name)));

(правка) Если, кстати, вы на самом деле намереваетесь выполнить сопоставление префиксов, то вам нужно изменить = 'asheville' обратно на like 'ashevill%' (обратите внимание на like: нет i),и измените указанный индекс следующим образом:

create index "cities(lower(name))" on cities ((lower(name)) text_pattern_ops);
1 голос
/ 30 августа 2011
  1. Новые индексы:

    • region.id
    • cities.region_id
    • regions.country_id

  2. Меньше работы

    • объединение строк занимает много времени; вместо того, чтобы делать это в запросе, рассмотрите возможность сделать это в своем скрипте, который использует результат
    • вместо того, чтобы делать конкатенацию и затем фильтровать результаты, вы должны сначала выполнить фильтрацию, а затем выполнить конкатенацию / функции ( кредит: тому, что говорит Джей Фриман в комментариях )

  3. Показатели лома

    • Ни один из ваших текущих индексов не используется. Вы должны отказаться от них, поскольку они просто замедляют ваш запрос.
1 голос
/ 30 августа 2011

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

0 голосов
/ 30 августа 2011

Полагаю, у вас есть индексы regions.id и countries.id, которые звучат так, как если бы они были первичным ключом.

Насколько я могу судить, два внутренних объединения не используют индекс, потому что cities.region_id и regions.country_id не являются частью индекса, который может использоваться здесь (поскольку в индексах, где они содержатся, они перечислены в конце).

Вы можете поменять столбцы в существующих двух индексах ((region_id, name) вместо (name, region_id)) или просто создать новые индексы только для этих столбцов. Я полагаю, что тогда объединения будут использовать эти индексы.

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