我在PgSQL中使用这样的查询进行全文搜索(目的是根据搜索到的城市名称返回地理编码城市列表):
SELECT *
FROM cities
WHERE city_tsvector @@ to_tsquery('Paris')
AND postcode LIKE '75%'
此查询在我的数据库上执行得非常快(城市表中的425626个条目):大约100毫秒。到目前为止,非常好。
现在,我必须同时在400个城市进行搜索。
400 x 100ms = 40秒,这对我的用户来说太长了。
我正在尝试编写单个查询,以便一次执行此搜索。一个细节:我必须搜索的城市没有存储在数据库中。
所以我写了这样的查询:
SELECT DISTINCT ON (myid) *
FROM unnest(
array[19977,19978,19979, (and so on)]::int[],
array['SAULXURES','ARGENTEUIL','OBERHOFFEN&SUR&MODER', (and so on)]::text[],
array['67','95','67','44', (and so on))]::text[]
) AS t(myid, cityname,mypostcode)
LEFT JOIN cities gc2 ON gc2.city_tsvector @@ to_tsquery(cityname) AND gc2.postcode LIKE CONCAT(mypostcode,'%')
ORDER BY myid
;
结果只是灾难性的:对于相同的搜索,查询的速度要慢4倍!
是否可以执行此类查询以便执行时间更短?
由于
修改
这是表城市结构(425626行):
使用@ The-Impaler回复编辑:
查询需要11秒
EXPLAIN VERBOSE:
Unique (cost=71133.21..71138.53 rows=100 width=40) Output: t.myid, (st_astext(gc2.gps_coordinates)) -> Sort (cost=71133.21..71135.87 rows=1064 width=40) Output: t.myid, (st_astext(gc2.gps_coordinates)) Sort Key: t.myid -> Hash Right Join (cost=2.26..71079.72 rows=1064 width=40) Output: t.myid, st_astext(gc2.gps_coordinates) Hash Cond: (left((gc2.postcode)::text, 2) = t.mypostcode) Join Filter: (gc2.city_tsvector @@ to_tsquery(t.cityname)) -> Seq Scan on public.geo_cities gc2 (cost=0.00..13083.26 rows=425626 width=69) Output: gc2.id, gc2.country_code, gc2.city, gc2.postcode, gc2.gps_coordinates, gc2.administrative_level_1_name, gc2.administrative_level_1_code, gc2.administrative_level_2_name, gc2.administrative_level_2_code, gc2.administrative_level_ (...) -> Hash (cost=1.01..1.01 rows=100 width=72) Output: t.myid, t.cityname, t.mypostcode -> Function Scan on t (cost=0.01..1.01 rows=100 width=72) Output: t.myid, t.cityname, t.mypostcode Function Call: unnest('{289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,28914887922 (...)
查询需要12秒
EXPLAIN VERBOSE:
Unique (cost=71665.25..71670.57 rows=100 width=40) Output: t.myid, (st_astext(gc2.gps_coordinates)) -> Sort (cost=71665.25..71667.91 rows=1064 width=40) Output: t.myid, (st_astext(gc2.gps_coordinates)) Sort Key: t.myid -> Hash Right Join (cost=2.26..71611.75 rows=1064 width=40) Output: t.myid, st_astext(gc2.gps_coordinates) Hash Cond: ((substring((gc2.postcode)::text, 1, 2))::text = t.mypostcode) Join Filter: (gc2.city_tsvector @@ to_tsquery(t.cityname)) -> Seq Scan on public.geo_cities gc2 (cost=0.00..13083.26 rows=425626 width=69) Output: gc2.id, gc2.country_code, gc2.city, gc2.postcode, gc2.gps_coordinates, gc2.administrative_level_1_name, gc2.administrative_level_1_code, gc2.administrative_level_2_name, gc2.administrative_level_2_code, gc2.administrative_level_ (...) -> Hash (cost=1.01..1.01 rows=100 width=72) Output: t.myid, t.cityname, t.mypostcode -> Function Scan on t (cost=0.01..1.01 rows=100 width=72) Output: t.myid, t.cityname, t.mypostcode Function Call: unnest('{289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,28914887922 (...)
答案 0 :(得分:1)
第一部分 - 在postcode
上执行索引范围扫描。
关键优化是让每个搜索工作在4000行而不是400k行,如果按照邮政编码的前两位数进行过滤,这应该很容易。
选项#1 :在邮政编码的前两个字符上创建索引:
create index ix1 on cities (substring(postcode from 1 for 2));
选项#2 :按=
过滤时,请使用LIKE
代替postcode
。您需要创建一个伪列minipostcode
并在其上创建索引:
create or replace function minipostcode(cities)
returns char(2) as $$
select substring($1.postcode from 1 for 2)
$$ stable language sql;
create index ix_cities_minipostcode on cities(minipostcode(cities));
您的SQL将更改为:
SELECT DISTINCT ON (myid) *
FROM unnest(
array[19977,19978,19979, (and so on)]::int[],
array['SAULXURES','ARGENTEUIL','OBERHOFFEN&SUR&MODER', (and so on)]::text[],
array['67','95','67','44', (and so on))]::text[]
) AS t(myid, cityname,mypostcode)
LEFT JOIN cities gc2 ON gc2.city_tsvector @@ to_tsquery(cityname)
AND gc2.minipostcode = mypostcode
ORDER BY myid
;
请参阅那里的gc2.minipostcode =
检查执行计划
获取上述每个选项的执行计划,并使用以下方法进行比较:
explain verbose <my-query>
请发布两个选项的执行计划。
第二部分 - 减少扫描次数。
一旦您确定它使用postcode
上的索引范围扫描,您就可以进一步优化它。
考虑到您的搜索有400个值,每个postcode
可能重复约4次。然后,为每个邮政编码执行一次索引范围扫描,只需执行此操作即可将执行时间缩短75%。
但是,您不能使用纯SQL执行此操作,您需要预处理SQL,每postcode
生成一个查询。您将不再使用unnest
,但您将使用您的应用程序语言预构建SQL。
例如,由于67
在您的示例中显示两次,因此应将其合并为单索引范围扫描,从而产生如下内容:
select from cities gc2
where (gc2.city_tsvector @@ to_tsquery('SAULXURES')
or gc2.city_tsvector @@ to_tsquery('OBERHOFFEN&SUR&MODER'))
and gc2.minipostcode = '67'
union
(next select for another postcode here, and so on...)
这比unnest
选项更优化,因为它执行最多 100索引范围扫描,即使您将搜索增加到1000或5000条件。
试试这个,并发布新的执行计划。