我正在尝试尽可能地加快此查询速度。它并不是非常慢,但我需要它尽可能快。
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)
cities表包含248016条记录。 国家表包含252条记录。 区域表包含4005条记录。
以下是查询的解释输出:http://explain.depesz.com/s/fWe
非常感谢任何帮助。基本上我只是在寻找建议或指出我可能错过的东西。
答案 0 :(得分:4)
在您的子查询中,您应该返回已经返回的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
:no i
),并将索引更改为如下所示:
create index "cities(lower(name))" on cities ((lower(name)) text_pattern_ops);
答案 1 :(得分:1)
如果你确实需要它尽可能快,那么我的建议是在搜索时完全避免查询数据库。由于城市和国家的名称通常是静态的而不是 - 它们不经常改变 - 我建议离线进行连接并将结果存储为针对您想要进行的搜索进行优化的格式
答案 2 :(得分:1)
答案 3 :(得分:0)
我假设您在regions.id
和countries.id
上有索引,因为它们听起来好像是主键。
据我所知,两个内部联接没有使用索引,因为cities.region_id
和regions.country_id
不是可以在这里使用的索引的一部分(因为在它们所在的索引中)包含,它们列在最后)。
您可以交换现有两个索引((region_id, name)
而不是(name, region_id)
)中的列,也可以只为这些列创建新索引。我假设连接将使用那些索引。