[英]Postgres pg_trgm GIN index ignored in a specific join
我有一個包含多個文本字段的表item
,例如name
、 unique_attr
、 category
等,所有這些我都使用 GIN (gin_trgm_ops) 索引進行了索引,以實現更快的ilike
查詢,實際上,即使連接到表inventory_membership
使用索引並加快執行時間。 Output 我的解釋:
explain analyze select i.* from item i
join inventory_membership im on im.inventory_id = i.inventory_id
where i.name ilike '%blu%' or unique_attr ilike '%blu%' or category ilike '%blu%'
or brand ilike '%blu%';
Hash Join (cost=98.64..4584.98 rows=87302 width=478) (actual time=4.258..30.393 rows=57584 loops=1)
Hash Cond: (i.inventory_id = im.inventory_id)
-> Bitmap Heap Scan on item i (cost=95.45..3584.23 rows=4982 width=478) (actual time=3.706..10.529 rows=3340 loops=1)
Recheck Cond: ((name ~~* '%blu%'::text) OR (unique_attr ~~* '%blu%'::text) OR (category ~~* '%blu%'::text) OR (brand ~~* '%blu%'::text))
Heap Blocks: exact=715
-> BitmapOr (cost=95.45..95.45 rows=5130 width=0) (actual time=3.622..3.622 rows=0 loops=1)
-> Bitmap Index Scan on item_name_idx (cost=0.00..42.97 rows=3596 width=0) (actual time=1.612..1.612 rows=3160 loops=1)
Index Cond: (name ~~* '%blu%'::text)
-> Bitmap Index Scan on item_unique_attr_idx (cost=0.00..12.01 rows=1 width=0) (actual time=0.586..0.586 rows=32 loops=1)
Index Cond: (unique_attr ~~* '%blu%'::text)
-> Bitmap Index Scan on item_category_idx (cost=0.00..22.78 rows=1437 width=0) (actual time=0.888..0.888 rows=1394 loops=1)
Index Cond: (category ~~* '%blu%'::text)
-> Bitmap Index Scan on item_brand_idx (cost=0.00..12.72 rows=96 width=0) (actual time=0.532..0.532 rows=42 loops=1)
Index Cond: (brand ~~* '%blu%'::text)
-> Hash (cost=1.97..1.97 rows=97 width=4) (actual time=0.059..0.060 rows=87 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 12kB
-> Seq Scan on inventory_membership im (cost=0.00..1.97 rows=97 width=4) (actual time=0.010..0.032 rows=87 loops=1)
Planning Time: 0.924 ms
Execution Time: 42.093 ms
我們可以看到item_name_idx
、 item_unique_attr_idx
、 item_category_idx
和item_brand_idx
GIN 索引正在用於索引條件。 偉大的。
但是,當我加入另一個表(只有id
和name
列的inventory
表)時,索引會消失。 解釋:
explain analyze select i.* from item i
join inventory inv on inv.id = i.inventory_id
join inventory_membership im on im.inventory_id = i.inventory_id
where i.name ilike '%blu%' or unique_attr ilike '%blu%' or category ilike '%blu%' or brand
ilike '%blu%';
Hash Join (cost=4.67..1172.61 rows=60407 width=478) (actual time=0.775..121.787 rows=57584 loops=1)
Hash Cond: (inv.id = im.inventory_id)
-> Merge Join (cost=1.49..440.81 rows=4982 width=482) (actual time=0.111..101.857 rows=3340 loops=1)
Merge Cond: (i.inventory_id = inv.id)
-> Index Scan using item_inventory_id_idx on item i (cost=0.29..13946.60 rows=4982 width=478) (actual time=0.085..99.857 rows=3340 loops=1)
Filter: ((name ~~* '%blu%'::text) OR (unique_attr ~~* '%blu%'::text) OR (category ~~* '%blu%'::text) OR (brand ~~* '%blu%'::text))
Rows Removed by Filter: 34858
-> Sort (cost=1.20..1.22 rows=8 width=4) (actual time=0.020..0.025 rows=8 loops=1)
Sort Key: inv.id
Sort Method: quicksort Memory: 25kB
-> Seq Scan on inventory inv (cost=0.00..1.08 rows=8 width=4) (actual time=0.006..0.009 rows=8 loops=1)
-> Hash (cost=1.97..1.97 rows=97 width=4) (actual time=0.650..0.651 rows=87 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 12kB
-> Seq Scan on inventory_membership im (cost=0.00..1.97 rows=97 width=4) (actual time=0.005..0.028 rows=87 loops=1)
Planning Time: 7.193 ms
Execution Time: 132.427 ms
你可以看到 GIN 索引消失了,解釋使用的唯一索引是item_inventory_id_idx
- 這是常規的 FK BTREE 索引。 此外,執行時間也過得很快。 為什么?
您注意到您主要對庫存名稱感興趣,並且庫存表中只有 8 行。 8 行是查詢計划器更喜歡merge join
而不是hash join
的原因,后者在兩個表都很大時效果更好。 合並連接需要排序列表中的inventory_id
(這正是索引的含義),這意味着它寧願不使用您的GIN 索引,因為它認為這樣效率會降低。
現在,沒有數據,你可以做幾件事,我不知道哪一個會更快。 您已經嘗試過的第一個是在scalar subquery
中獲取庫存名稱:
SELECT i.*, (select name from inventory where id = i.inventory_id) as inventoryName
FROM item i
JOIN inventory_membership im ON im.inventory_id = i.inventory_id
WHERE i.name ilike '%blu%' or unique_attr ilike '%blu%' or category ilike '%blu%'
or brand ilike '%blu%';
但這意味着這個select
語句被執行 57k 次,每行一次。 第二種是使用您擁有的查詢,但查看將i.inventory_id
更改為inv.id
in inventory_membership
是否會改變任何內容。
SELECT i.*, inv.name as inventoryName
FROM item i
JOIN inventory inv ON inv.id = i.inventory_id
JOIN inventory_membership im ON im.inventory_id = inv.id -- <- this changed
WHERE i.name ilike '%blu%' or unique_attr ilike '%blu%' or category ilike '%blu%'
or brand ilike '%blu%';
最后,正如它在這個問題中所說,您可能會在獲取庫存名稱之前強制執行第一個查詢,使用 CTE 或帶有OFFSET 0
的子查詢。
WITH my_items AS (
SELECT i.*
FROM item i
JOIN inventory_membership im ON im.inventory_id = i.inventory_id
WHERE i.name ilike '%blu%' or unique_attr ilike '%blu%' or category ilike '%blu%'
or brand ilike '%blu%'
)
SELECT i.*, inv.name as inventoryName
FROM my_items i
JOIN inventory inv ON inv.id = i.inventory_id
或者
SELECT i.*, inv.name as inventoryName
FROM (
SELECT i.*
FROM item i
JOIN inventory_membership im ON im.inventory_id = i.inventory_id
WHERE i.name ilike '%blu%' or unique_attr ilike '%blu%' or category ilike '%blu%'
or brand ilike '%blu%'
OFFSET 0 -- <- this forces the subquery to be evaluated separate from the rest of the query
) i
JOIN inventory inv ON inv.id = i.inventory_id
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.