在 PostgreSQL 中,如何有效地对一列进行全文搜索,对另一列进行排序?
假设我有一个tbl
包含列a
, b
, c
, ... 和许多(> 一百万)行的表。我想对列进行全文搜索a
并按其他列对结果进行排序。
所以我va
从 column创建一个 tsvector a
,
ALTER TABLE tbl
ADD COLUMN va tsvector GENERATED ALWAYS AS (to_tsvector('english', a)) STORED;
为此创建一个索引iva
,
CREATE INDEX iva ON tbl USING GIN (va);
ib
和列的索引b
,
CREATE INDEX ib ON tbl (b);
然后我像这样查询
SELECT * FROM tbl WHERE va @@ to_tsquery('english', 'test') ORDER BY b LIMIT 100
现在 Postgres 明显的执行策略是:
ib
使用、过滤va @@ 'test'::tsquery
和在 100 次匹配后停止对常用词进行索引扫描,而对于稀有词使用
iva
with condition 进行(位图)索引扫描va @@ 'test'::tsquery
,然后b
手动排序
但是,Postgres 的查询规划器似乎没有考虑词频:
对于低
LIMIT
(例如100
),它总是使用策略 1(正如我检查过的EXPLAIN
),并且在我的情况下,对于稀有(或不出现)的单词需要一分钟以上的时间。但是,如果我通过设置 large (或 no) 来欺骗它使用策略 2LIMIT
,它会在一毫秒内返回!反之亦然,对于较大的
LIMIT
(例如200
),它总是使用策略 2,该策略适用于稀有词,但对于常用词非常慢
那么如何让 Postgres 在每种情况下都使用一个好的查询计划呢?
由于目前似乎没有办法让 Postgres 自动选择正确的计划,
如何获得包含特定词位的行数,以便决定最佳策略?
(
SELECT COUNT(*) FROM tbl WHERE va @@ to_tsquery('english', 'test')
非常慢(对于 10000 行中出现的词位约 1 秒),而且ts_stat
似乎也无济于事,除了建立我自己的词频列表)然后我如何告诉 Postgres 使用这个策略?
这里有一个具体的例子
我有一个items
包含 150 万行的表,其中有一个 tsvector 列v3
,我在其中进行文本搜索,还有一个列rating
进行排序。在这种情况下,如果 LIMIT 小于或等于 135,我确定查询规划器始终选择策略 1,否则选择策略 2
这里是对 LIMIT 135 的稀有词“aberdeen”(出现在 132 行中)的 EXPLAIN ANALYZE:
EXPLAIN (ANALYZE, BUFFERS) SELECT nm FROM items WHERE v3 @@ to_tsquery('english', 'aberdeen')
ORDER BY rating DESC NULLS LAST LIMIT 135
Limit (cost=0.43..26412.78 rows=135 width=28) (actual time=5915.455..499917.390 rows=132 loops=1)
Buffers: shared hit=4444267 read=2219412
I/O Timings: read=485517.381
-> Index Scan using ir on items (cost=0.43..1429202.13 rows=7305 width=28) (actual time=5915.453..499917.242 rows=132 loops=1)
Filter: (v3 @@ '''aberdeen'''::tsquery)"
Rows Removed by Filter: 1460845
Buffers: shared hit=4444267 read=2219412
I/O Timings: read=485517.381
Planning:
Buffers: shared hit=253
Planning Time: 1.270 ms
Execution Time: 499919.196 ms
并使用 LIMIT 136:
EXPLAIN (ANALYZE, BUFFERS) SELECT nm FROM items WHERE v3 @@ to_tsquery('english', 'aberdeen')
ORDER BY rating DESC NULLS LAST LIMIT 136
Limit (cost=26245.53..26245.87 rows=136 width=28) (actual time=29.870..29.889 rows=132 loops=1)
Buffers: shared hit=57 read=83
I/O Timings: read=29.085
-> Sort (cost=26245.53..26263.79 rows=7305 width=28) (actual time=29.868..29.876 rows=132 loops=1)
Sort Key: rating DESC NULLS LAST
Sort Method: quicksort Memory: 34kB
Buffers: shared hit=57 read=83
I/O Timings: read=29.085
-> Bitmap Heap Scan on items (cost=88.61..25950.14 rows=7305 width=28) (actual time=1.361..29.792 rows=132 loops=1)
Recheck Cond: (v3 @@ '''aberdeen'''::tsquery)"
Heap Blocks: exact=132
Buffers: shared hit=54 read=83
I/O Timings: read=29.085
-> Bitmap Index Scan on iv3 (cost=0.00..86.79 rows=7305 width=0) (actual time=1.345..1.345 rows=132 loops=1)
Index Cond: (v3 @@ '''aberdeen'''::tsquery)"
Buffers: shared hit=3 read=2
I/O Timings: read=1.299
Planning:
Buffers: shared hit=253
Planning Time: 1.296 ms
Execution Time: 29.932 ms
这里是 LIMIT 135 的常用词“游戏”(出现在 240464 行中):
EXPLAIN (ANALYZE, BUFFERS) SELECT nm FROM items WHERE v3 @@ to_tsquery('english', 'game')
ORDER BY rating DESC NULLS LAST LIMIT 135
Limit (cost=0.43..26412.78 rows=135 width=28) (actual time=3.240..542.252 rows=135 loops=1)
Buffers: shared hit=2876 read=1930
I/O Timings: read=529.523
-> Index Scan using ir on items (cost=0.43..1429202.13 rows=7305 width=28) (actual time=3.239..542.216 rows=135 loops=1)
Filter: (v3 @@ '''game'''::tsquery)
Rows Removed by Filter: 867
Buffers: shared hit=2876 read=1930
I/O Timings: read=529.523
Planning:
Buffers: shared hit=208 read=45
I/O Timings: read=15.626
Planning Time: 25.174 ms
Execution Time: 542.306 ms
并使用 LIMIT 136:
EXPLAIN (ANALYZE, BUFFERS) SELECT nm FROM items WHERE v3 @@ to_tsquery('english', 'game')
ORDER BY rating DESC NULLS LAST LIMIT 136
Limit (cost=26245.53..26245.87 rows=136 width=28) (actual time=69419.656..69419.675 rows=136 loops=1)
Buffers: shared hit=1757820 read=457619
I/O Timings: read=65246.893
-> Sort (cost=26245.53..26263.79 rows=7305 width=28) (actual time=69419.654..69419.662 rows=136 loops=1)
Sort Key: rating DESC NULLS LAST
Sort Method: top-N heapsort Memory: 41kB
Buffers: shared hit=1757820 read=457619
I/O Timings: read=65246.893
-> Bitmap Heap Scan on items (cost=88.61..25950.14 rows=7305 width=28) (actual time=110.959..69326.343 rows=240464 loops=1)
Recheck Cond: (v3 @@ '''game'''::tsquery)
Rows Removed by Index Recheck: 394527
Heap Blocks: exact=49894 lossy=132284
Buffers: shared hit=1757817 read=457619
I/O Timings: read=65246.893
-> Bitmap Index Scan on iv3 (cost=0.00..86.79 rows=7305 width=0) (actual time=100.537..100.538 rows=240464 loops=1)
Index Cond: (v3 @@ '''game'''::tsquery)
Buffers: shared hit=1 read=60
I/O Timings: read=26.870
Planning:
Buffers: shared hit=253
Planning Time: 1.195 ms
Execution Time: 69420.399 ms