ดัชนีไม่ได้ใช้กับ `= any () 'แต่ใช้กับ` in'


15

ตารางtมีสองดัชนี:

create table t (a int, b int);
create type int_pair as (a int, b int);
create index t_row_idx on t (((a,b)::int_pair));
create index t_a_b_idx on t (a,b);

insert into t (a,b)
select i, i
from generate_series(1, 100000) g(i)
;

ไม่มีการใช้ดัชนีกับanyผู้ประกอบการ:

explain analyze
select *
from t
where (a,b) = any(array[(1,1),(1,2)])
;
                                            QUERY PLAN                                             
---------------------------------------------------------------------------------------------------
 Seq Scan on t  (cost=0.00..1693.00 rows=1000 width=8) (actual time=0.042..126.789 rows=1 loops=1)
   Filter: (ROW(a, b) = ANY (ARRAY[ROW(1, 1), ROW(1, 2)]))
   Rows Removed by Filter: 99999
 Planning time: 0.122 ms
 Execution time: 126.836 ms

แต่หนึ่งในนั้นถูกใช้กับinโอเปอเรเตอร์:

explain analyze
select *
from t
where (a,b) in ((1,1),(1,2))
;
                                                    QUERY PLAN                                                    
------------------------------------------------------------------------------------------------------------------
 Index Only Scan using t_a_b_idx on t  (cost=0.29..8.32 rows=1 width=8) (actual time=0.028..0.029 rows=1 loops=1)
   Index Cond: (a = 1)
   Filter: ((b = 1) OR (b = 2))
   Heap Fetches: 1
 Planning time: 0.161 ms
 Execution time: 0.066 ms

มันใช้ดัชนีบันทึกถ้าบันทึกถูกโยนลงในประเภทที่ถูกต้อง:

explain analyze
select *
from t
where (a,b)::int_pair = any(array[row(1,1),row(1,2)])
;
                                                  QUERY PLAN                                                  
--------------------------------------------------------------------------------------------------------------
 Index Scan using t_row_idx on t  (cost=0.42..12.87 rows=2 width=8) (actual time=0.106..0.126 rows=1 loops=1)
   Index Cond: (ROW(a, b)::int_pair = ANY (ARRAY[ROW(1, 1), ROW(1, 2)]))
 Planning time: 0.208 ms
 Execution time: 0.203 ms

เหตุใดนักวางแผนไม่ใช้ดัชนีที่ไม่บันทึกสำหรับanyผู้ดำเนินการเนื่องจากใช้สำหรับinผู้ดำเนินการ


คำถามที่น่าสนใจนี้เกิดขึ้นจากการสนทนาที่เกี่ยวข้องกับ SO: stackoverflow.com/a/34601242/939860
Erwin Brandstetter

คำตอบ:


13

ภายในมีสองรูปแบบที่แยกจากกันของINเช่นเดียวกับการANYสร้าง

หนึ่งในแต่ละชุดจะเทียบเท่ากับอีกชุดหนึ่งและexpr IN (<set>)ยังนำไปสู่แผนคิวรีแบบเดียวกับexpr = ANY(<set>)ที่สามารถใช้ดัชนีธรรมดาได้ รายละเอียด:

ดังนั้นทั้งสองเคียวรีต่อไปนี้จะเทียบเท่ากันและทั้งคู่สามารถใช้ดัชนีธรรมดาt_a_b_idx(ซึ่งอาจเป็นวิธีแก้ปัญหาหากคุณพยายามให้เคียวรีใช้ดัชนี):

EXPLAIN ANALYZE
SELECT *
FROM t
WHERE (a,b) = ANY(VALUES (1,1),(1,2));

หรือ:

...
WHERE (a,b) IN (VALUES (1,1),(1,2));

เหมือนกันสำหรับทั้งสอง:

                                                        QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------
 Nested Loop  (cost=0.33..16.71 rows=1 width=8) (actual time=0.101..0.101 rows=0 loops=1)
   ->  Unique  (cost=0.04..0.05 rows=2 width=8) (actual time=0.068..0.070 rows=2 loops=1)
         ->  Sort  (cost=0.04..0.04 rows=2 width=8) (actual time=0.067..0.068 rows=2 loops=1)
               Sort Key: "*VALUES*".column1, "*VALUES*".column2
               Sort Method: quicksort  Memory: 25kB
               ->  Values Scan on "*VALUES*"  (cost=0.00..0.03 rows=2 width=8) (actual time=0.005..0.005 rows=2 loops=1)
   ->  Index Only Scan using t_plain_idx on t  (cost=0.29..8.32 rows=1 width=8) (actual time=0.009..0.009 rows=0 loops=2)
         Index Cond: ((a = "*VALUES*".column1) AND (b = "*VALUES*".column2))
         Heap Fetches: 0
 Planning time: 4.080 ms
 Execution time: 0.202 ms

อย่างไรก็ตามสิ่งนี้ไม่สามารถส่งผ่านไปยังฟังก์ชันได้อย่างง่ายดายเนื่องจากไม่มี "ตัวแปรตาราง" ใน Postgres ซึ่งนำไปสู่ปัญหาที่เริ่มหัวข้อนี้:

มีวิธีแก้ไขปัญหาต่าง ๆ สำหรับปัญหานั้น หนึ่งเป็นคำตอบทางเลือกที่ฉันเพิ่มที่นั่น บางคนอื่น ๆ :


รูปแบบที่สองของแต่ละคนที่แตกต่างกันANYจะใช้เวลาที่เกิดขึ้นจริงอาร์เรย์ในขณะที่INใช้จุลภาคคั่นรายการของค่า

สิ่งนี้มีผลที่แตกต่างกันสำหรับการพิมพ์อินพุต ดังที่เราเห็นในEXPLAINผลลัพธ์ของคำถามรูปแบบนี้:

WHERE (a,b) = ANY(ARRAY[(1,1),(1,2)]);

ถูกมองว่าเป็นชวเลขสำหรับ:

ROW(a, b) = ANY (ARRAY[ROW(1, 1), ROW(1, 2)])

และเปรียบเทียบค่า ROW จริง Postgres ขณะนี้ยังไม่ฉลาดพอที่จะเห็นว่าดัชนีในประเภทคอมโพสิตt_row_idxมีผลบังคับใช้ และไม่ได้ตระหนักว่าดัชนีอย่างง่ายt_a_b_idxควรมีผลบังคับใช้เช่นกัน

นักแสดงที่ชัดเจนช่วยในการเอาชนะการขาดความฉลาด:

WHERE (a,b)::int_pair = ANY(ARRAY[(1,1),(1,2)]::int_pair[]);

การคัดเลือกตัวถูกดำเนินการทางขวา ( ::int_pair[]) นั้นเป็นทางเลือก (แม้ว่าจะดีกว่าสำหรับประสิทธิภาพและเพื่อหลีกเลี่ยงความคลุมเครือ) เมื่อตัวถูกดำเนินการด้านซ้ายมีประเภทที่รู้จักกันดีตัวถูกดำเนินการด้านขวาจะถูกข่มขู่จาก "บันทึกที่ไม่ระบุชื่อ" เป็นประเภทการจับคู่ จากนั้นผู้ประกอบการจะถูกกำหนดอย่างไม่น่าสงสัย และ Postgres เลือกดัชนีที่ใช้งานได้ขึ้นอยู่กับตัวดำเนินการและตัวถูกดำเนินการด้านซ้าย สำหรับโอเปอเรเตอร์จำนวนมากที่กำหนด a COMMUTATORตัววางแผนคิวรีสามารถพลิกโอเปอแรนด์เพื่อนำนิพจน์ที่จัดทำดัชนีไปทางซ้าย แต่นั่นเป็นไปไม่ได้กับการANYสร้าง

ที่เกี่ยวข้อง:

.. ค่าจะถูกนำมาเป็นองค์ประกอบและ Postgres สามารถเปรียบเทียบค่าจำนวนเต็มแต่ละรายการตามที่เราเห็นในEXPLAINผลลัพธ์อีกครั้ง:

Filter: ((b = 1) OR (b = 2))

ดังนั้น Postgres จึงพบว่าt_a_b_idxสามารถใช้ดัชนีอย่างง่ายได้


ดังนั้นจะมีวิธีแก้ไขปัญหาอื่นสำหรับกรณีเฉพาะในตัวอย่าง : เนื่องจากประเภทคอมโพสิตที่กำหนดเองint_pairในตัวอย่างนั้นเกิดขึ้นเทียบเท่ากับประเภทแถวของตารางtเองเราจึงสามารถทำให้ง่ายขึ้น:

CREATE INDEX t_row_idx2 ON t ((t));

จากนั้นแบบสอบถามนี้จะใช้ดัชนีโดยไม่มีการคัดเลือกนักแสดงเพิ่มเติม:

EXPLAIN ANALYZE
SELECT *
FROM   t
WHERE  t = ANY(ARRAY[(1,1),(1,2)]);
                                                      QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on t  (cost=40.59..496.08 rows=1000 width=8) (actual time=0.19
1..0.191 rows=0 loops=1)
   Recheck Cond: (t.* = ANY (ARRAY[ROW(1, 1), ROW(1, 2)]))
   ->  Bitmap Index Scan on t_row_idx2  (cost=0.00..40.34 rows=1000 width=0) (actual time=0.188..0.188 rows=0 loops=1)
         Index Cond: (t.* = ANY (ARRAY[ROW(1, 1), ROW(1, 2)]))
 Planning time: 2.575 ms
 Execution time: 0.267 ms

แต่กรณีการใช้งานทั่วไปจะไม่สามารถใช้ประเภทตารางที่มีอยู่โดยนัยได้


1
การเพิ่มเล็กน้อย: ในขณะที่IN(...)รายการสั้น ๆอาจถูกแปล (โดยผู้วางแผน) เป็น... OR ...นิพจน์ในกรณีข้างต้นมันมักจะถูกแปลเป็นเพียงแค่ANY('{...}')นั่นคือการใช้อาร์เรย์ ดังนั้นในกรณีส่วนใหญ่INรายการของค่าและANYอาเรย์ก็เหมือนกัน
dezso

1
@dezso: สำหรับกรณีที่ง่ายที่สุดใช่ คำถามที่แสดงให้เห็นถึงกรณีที่IN(...) ไม่สามารถ= ANY('{...}')ได้รับการแปลไป
Erwin Brandstetter
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.