CREATE INDEX users_search_idx ON auth_user USING gin( username gin_trgm_ops,first_name gin_trgm_ops,last_name gin_trgm_ops );
我正在执行以下查询:
PREPARE user_search (TEXT,INT) AS SELECT username,email,first_name,last_name,( -- would probably do per-field weightings here s_username + s_first_name + s_last_name ) rank FROM auth_user,similarity(username,$1) s_username,similarity(first_name,$1) s_first_name,similarity(last_name,$1) s_last_name WHERE username % $1 OR first_name % $1 OR last_name % $1 ORDER BY rank DESC LIMIT $2;
auth_user表有620万行.
查询的速度似乎在很大程度上取决于相似性查询可能返回的结果数.
通过set_limit增加相似性阈值会有所帮助,但会通过消除部分匹配来降低结果的有用性.
我们使用Elasticsearch返回<任何查询200ms,同时做更复杂(更好)的排名. 我想知道是否有任何方法可以改善这一点以获得更一致的性能? 我的理解是GIN索引(倒排索引)与Elasticsearch使用的基本方法相同,所以我认为可以进行一些优化. EXPLAIN ANALYZE EXECUTE user_search(‘mel’,20)显示:
Limit (cost=54099.81..54099.86 rows=20 width=52) (actual time=10302.092..10302.104 rows=20 loops=1) -> Sort (cost=54099.81..54146.66 rows=18739 width=52) (actual time=10302.091..10302.095 rows=20 loops=1) Sort Key: (((s_username.s_username + s_first_name.s_first_name) + s_last_name.s_last_name)) DESC Sort Method: top-N heapsort Memory: 26kB -> nested Loop (cost=382.74..53601.17 rows=18739 width=52) (actual time=118.164..10293.765 rows=8380 loops=1) -> nested Loop (cost=382.74..53132.69 rows=18739 width=56) (actual time=118.150..10262.804 rows=8380 loops=1) -> nested Loop (cost=382.74..52757.91 rows=18739 width=52) (actual time=118.142..10233.990 rows=8380 loops=1) -> Bitmap Heap Scan on auth_user (cost=382.74..52383.13 rows=18739 width=48) (actual time=118.128..10186.816 rows=8380loops=1)" Recheck Cond: (((username)::text % 'mel'::text) OR ((first_name)::text % 'mel'::text) OR ((last_name)::text %'mel'::text))" Rows Removed by Index Recheck: 2434523 Heap Blocks: exact=49337 lossy=53104 -> BitmapOr (cost=382.74..382.74 rows=18757 width=0) (actual time=107.436..107.436 rows=0 loops=1) -> Bitmap Index Scan on users_search_idx (cost=0.00..122.89 rows=6252 width=0) (actual time=40.200..40.200rows=88908 loops=1)" Index Cond: ((username)::text % 'mel'::text) -> Bitmap Index Scan on users_search_idx (cost=0.00..122.89 rows=6252 width=0) (actual time=43.847..43.847rows=102028 loops=1)" Index Cond: ((first_name)::text % 'mel'::text) -> Bitmap Index Scan on users_search_idx (cost=0.00..122.89 rows=6252 width=0) (actual time=23.387..23.387rows=58740 loops=1)" Index Cond: ((last_name)::text % 'mel'::text) -> Function Scan on similarity s_username (cost=0.00..0.01 rows=1 width=4) (actual time=0.004..0.004 rows=1 loops=8380) -> Function Scan on similarity s_first_name (cost=0.00..0.01 rows=1 width=4) (actual time=0.002..0.002 rows=1 loops=8380) -> Function Scan on similarity s_last_name (cost=0.00..0.01 rows=1 width=4) (actual time=0.002..0.002 rows=1 loops=8380) Execution time: 10302.559 ms
Server是在Amazon RDS上运行的Postgres 9.6.1
更新
1.
发布问题后不久,我发现了这个信息:https://www.postgresql.org/message-id/464F3C5D.2000700@enterprisedb.com
所以我试过了
-> SHOW work_mem; 4MB -> SET work_mem='12MB'; -> EXECUTE user_search('mel',20); (results returned in ~1.5s)
这取得了很大的进步(之前> 10s)!
对于类似的查询,1.5s仍然比ES慢,所以我仍然希望听到任何优化查询的建议.
2.
在回答评论时,在看到这个问题(Postgresql GIN index slower than GIST for pg_trgm)后,我尝试用GIST索引代替GIN索引进行完全相同的设置.
尝试上面的相同搜索,它返回~3.5秒,使用默认的work_mem =’4MB’.增加work_mem没有任何区别.
由此我得出结论,GIST索引的内存效率更高(没有像GIN那样遇到病态情况)但是当GIN正常工作时比GIN慢.这与推荐GIN索引的文档中描述的内容一致.
3.
我仍然不明白为什么花这么多时间:
-> Bitmap Heap Scan on auth_user (cost=382.74..52383.13 rows=18739 width=48) (actual time=118.128..10186.816 rows=8380loops=1)" Recheck Cond: (((username)::text % 'mel'::text) OR ((first_name)::text % 'mel'::text) OR ((last_name)::text %'mel'::text))" Rows Removed by Index Recheck: 2434523 Heap Blocks: exact=49337 lossy=53104
我不明白为什么需要这个步骤或者它正在做什么.
对于每个用户名%$1子句,下面有三个位图索引扫描…这些结果然后与BitmapOr步骤结合使用.这些部分都非常快.
但即使在我们没有用完工作mem的情况下,我们仍然在Bitmap堆扫描中花费将近一秒钟.
解决方法
1.
使用包含连接值的1列创建GiST索引:
CREATE INDEX users_search_idx ON auth_user USING gist((username || ' ' || first_name || ' ' || last_name) gist_trgm_ops);
这假设所有3列都被定义为NOT NULL(您没有指定).否则你需要做更多.
为什么不简化concat_ws()?
> Combine two columns and add into one new column
> Faster query with pattern-matching on multiple text fields
> Combine two columns and add into one new column
2.
使用正确的nearest-neighbor查询,匹配以上索引:
SELECT username,$1) AS s_username,$1) AS s_first_name,$1) AS s_last_name,row_number() OVER () AS rank -- greatest similarity first FROM auth_user WHERE (username || ' ' || first_name || ' ' || last_name) % $1 -- !! ORDER BY (username || ' ' || first_name || ' ' || last_name) <-> $1 -- !! LIMIT $2;
WHERE和ORDER BY中的表达式必须与索引表达式匹配!
特别是ORDER BY等级(就像你拥有的那样)对于从更大的合格行池中选择一个小LIMIT总是表现不佳,因为它不能直接使用索引:必须为每个符合条件的行计算排名后面的复杂表达式,然后所有必须在可以返回小的最佳匹配选择之前进行排序.这比真正的最近邻居查询要贵得多,这种查询可以直接从索引中选择最好的结果,甚至不用看其余的.
具有空窗口定义的row_number()仅反映相同SELECT的ORDER BY生成的排序.
相关答案:
> Best index for similarity function
> Search in 300 million addresses with pg_trgm
至于您的第3项,我添加了您引用的问题的答案,应该解释它:
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。