【问题标题】:Perform multiples PgSQL full text search in one query在一个查询中执行多个 PgSQL 全文搜索
【发布时间】:2018-09-22 19:46:31
【问题描述】:

我正在 PgSQL 中使用类似的查询执行全文搜索(目的是根据搜索到的城市名称返回地理编码城市列表):

SELECT * 
FROM   cities 
WHERE  city_tsvector @@ to_tsquery('Paris') 
AND    postcode LIKE '75%'

这个查询在我的数据库上执行得非常快(city 表中有 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 回答编辑:

  • 选项 #1:邮政编码前两个字符的索引

查询需要 11 秒

详细解释:

唯一(成本=71133.21..71138.53 行=100 宽度=40) 输出:t.myid, (st_astext(gc2.gps_coordinates)) -> 排序(成本=71133.21..71135.87 行=1064 宽度=40) 输出:t.myid, (st_astext(gc2.gps_coordinates)) 排序键:t.myid -> 哈希右连接(成本=2.26..71079.72 行=1064 宽度=40) 输出:t.myid, st_astext(gc2.gps_coordinates) 哈希条件: (left((gc2.postcode)::text, 2) = t.mypostcode) 加入过滤器:(gc2.city_tsvector @@ to_tsquery(t.cityname)) -> public.geo_cities gc2 上的 Seq 扫描(成本=0.00..13083.26 行=425626 宽度=69) 输出: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_ (...) -> 哈希(成本=1.01..1.01 行=100 宽度=72) 输出:t.myid、t.cityname、t.mypostcode -> t 上的函数扫描(成本=0.01..1.01 行=100 宽度=72) 输出:t.myid、t.cityname、t.mypostcode 函数调用:UNNEST(“{289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,28914887922(...)
  • 选项 #2:按邮政编码过滤时使用 = 而不是 LIKE

查询需要 12 秒

详细解释:

唯一(成本=71665.25..71670.57 行=100 宽度=40) 输出:t.myid, (st_astext(gc2.gps_coordinates)) -> 排序(成本=71665.25..71667.91 行=1064 宽度=40) 输出:t.myid, (st_astext(gc2.gps_coordinates)) 排序键:t.myid -> 哈希右连接(成本=2.26..71611.75 行=1064 宽度=40) 输出:t.myid, st_astext(gc2.gps_coordinates) 哈希条件:((substring((gc2.postcode)::text, 1, 2))::text = t.mypostcode) 加入过滤器:(gc2.city_tsvector @@ to_tsquery(t.cityname)) -> public.geo_cities gc2 上的 Seq 扫描(成本=0.00..13083.26 行=425626 宽度=69) 输出: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_ (...) -> 哈希(成本=1.01..1.01 行=100 宽度=72) 输出:t.myid、t.cityname、t.mypostcode -> t 上的函数扫描(成本=0.01..1.01 行=100 宽度=72) 输出:t.myid、t.cityname、t.mypostcode 函数调用:UNNEST(“{289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,289148879225,28914887922(...)

【问题讨论】:

  • 您是否只需要从城市表中获取“地理编码”列还是还需要其他一些列?
  • 你可以修改数据库吗?我不确定您有多少不同的邮政编码以及它们的格式,但也许在邮政编码的两个首字母上有一个索引并对其进行过滤会有所帮助
  • 我只需要 myid 和地理编码列
  • @jmriego 在邮政编码字段上有一个索引,查询时间减少了 10 秒,但仍然比逐行执行要慢得多
  • 对不起@frinux 我的意思是在索引的前两个字符上有一个索引。 CREATE INDEX start_postcode_cities ON 城市(左(邮编,2));然后过滤 where left(postcode,2) IN ('11', 22'...

标签: sql postgresql performance join full-text-search


【解决方案1】:

第一部分 - 对postcode 执行索引范围扫描。

关键的优化是让每次搜索都在 4000 行而不是 400k 行上工作,如果按邮政编码的前两位过滤得当,这应该很容易。

选项#1:在邮政编码的前两个字符上创建索引:

create index ix1 on cities (substring(postcode from 1 for 2));

选项#2:当使用postcode 过滤时,使用= 而不是LIKE。您需要创建一个伪列 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 个条件。

试试这个,然后发布新的执行计划。

【讨论】:

  • 结果在原帖内。对于第 2 部分,当您说邮政编码重复多次(并且低估了 4 次)时,您标记了一个点。但是,使用您的方法,我失去了保留我的身份的方式,这是保持地理编码城市与我的原始记录之间的联系所必需的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-10-25
  • 2020-08-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-18
相关资源
最近更新 更多