การทำงานของดัชนีใน PostgreSQL


73

ฉันมีคำถามสองสามข้อเกี่ยวกับการทำงานของดัชนีใน PostgreSQL ฉันมีFriendsตารางที่มีดัชนีต่อไปนี้:

   Friends ( user_id1 ,user_id2) 

user_id1และuser_id2เป็นกุญแจต่างประเทศเข้าสู่userตาราง

  1. สิ่งเหล่านี้เทียบเท่ากันหรือไม่ ถ้าไม่เช่นนั้นทำไม

    Index(user_id1,user_id2) and Index(user_id2,user_id1)
  2. ถ้าฉันสร้างคีย์หลัก (user_id1, user_id2) มันจะสร้างดัชนีให้โดยอัตโนมัติหรือไม่

    หากดัชนีในคำถามแรกไม่เท่ากันดัชนีใดจะถูกสร้างขึ้นบนคำสั่งคีย์หลักด้านบน

คำตอบ:


77

นี่คือผลของการสอบถามตารางบนมีคอลัมน์ที่สองของดัชนีหลายคอลัมน์
เอฟเฟกต์นั้นง่ายต่อการทำซ้ำสำหรับทุกคน แค่ลองทำเองที่บ้าน

ฉันทดสอบกับ PostgreSQL 9.0.5บน Debian โดยใช้ตารางขนาดกลางของฐานข้อมูลจริงด้วย 23322 แถว ใช้ความสัมพันธ์ n: m ระหว่างตารางadr(ที่อยู่) และatt(แอตทริบิวต์) แต่ไม่เกี่ยวข้องที่นี่ แบบแผนประยุกต์:

CREATE TABLE adratt (
  adratt_id serial PRIMARY KEY
, adr_id    integer NOT NULL
, att_id    integer NOT NULL
, log_up    timestamp(0) NOT NULL DEFAULT (now())::timestamp(0)
, CONSTRAINT adratt_uni UNIQUE (adr_id, att_id)
);

UNIQUEจำกัด ได้อย่างมีประสิทธิภาพใช้ดัชนีที่ไม่ซ้ำกัน ฉันทำการทดสอบซ้ำโดยใช้ดัชนีธรรมดาเพื่อให้แน่ใจและได้ผลลัพธ์ที่เหมือนกันตามที่คาดไว้

CREATE INDEX adratt_idx ON adratt(adr_id, att_id)

ตารางจะรวมอยู่ในadratt_uniดัชนีและก่อนการทดสอบที่ฉันรัน:

CLUSTER adratt;
ANALYZE adratt;

การสแกนตามลำดับสำหรับการสืบค้น(adr_id, att_id)นั้นเร็วที่สุดเท่าที่จะเป็นไปได้ ดัชนีหลายคอลัมน์จะยังคงใช้สำหรับเงื่อนไขแบบสอบถามในคอลัมน์ดัชนีที่สองเพียงอย่างเดียว

ฉันเรียกใช้แบบสอบถามสองสามครั้งเพื่อเติมแคชและเลือกที่ดีที่สุดในสิบวิ่งเพื่อให้ได้ผลลัพธ์ที่เทียบเคียง

1. การสืบค้นโดยใช้ทั้งสองคอลัมน์

SELECT *
FROM   adratt
WHERE  att_id = 90
AND    adr_id = 10;

 adratt_id | adr_id | att_id |       log_up
-----------+--------+--------+---------------------
       123 |     10 |     90 | 2008-07-29 09:35:54
(1 row)

ผลลัพธ์ของEXPLAIN ANALYZE:

Index Scan using adratt_uni on adratt  (cost=0.00..3.48 rows=1 width=20) (actual time=0.022..0.025 rows=1 loops=1)
  Index Cond: ((adr_id = 10) AND (att_id = 90))
Total runtime: 0.067 ms

2. แบบสอบถามโดยใช้คอลัมน์แรก

SELECT * FROM adratt WHERE adr_id = 10

 adratt_id | adr_id | att_id |       log_up
-----------+--------+--------+---------------------
       126 |     10 |     10 | 2008-07-29 09:35:54
       125 |     10 |     13 | 2008-07-29 09:35:54
      4711 |     10 |     21 | 2008-07-29 09:35:54
     29322 |     10 |     22 | 2011-06-06 15:50:38
     29321 |     10 |     30 | 2011-06-06 15:47:17
       124 |     10 |     62 | 2008-07-29 09:35:54
     21913 |     10 |     78 | 2008-07-29 09:35:54
       123 |     10 |     90 | 2008-07-29 09:35:54
     28352 |     10 |    106 | 2010-11-22 12:37:50
(9 rows)

ผลลัพธ์ของEXPLAIN ANALYZE:

Index Scan using adratt_uni on adratt  (cost=0.00..8.23 rows=9 width=20) (actual time=0.007..0.023 rows=9 loops=1)
  Index Cond: (adr_id = 10)
Total runtime: 0.058 ms

3. การสืบค้นโดยใช้คอลัมน์ที่สอง

SELECT * FROM adratt WHERE att_id = 90

 adratt_id | adr_id | att_id |       log_up
-----------+--------+--------+---------------------
       123 |     10 |     90 | 2008-07-29 09:35:54
       180 |     39 |     90 | 2008-08-29 15:46:07
...
(83 rows)

ผลลัพธ์ของEXPLAIN ANALYZE:

Index Scan using adratt_uni on adratt  (cost=0.00..818.51 rows=83 width=20) (actual time=0.014..0.694 rows=83 loops=1)
  Index Cond: (att_id = 90)
Total runtime: 0.849 ms

4. ปิดการใช้งาน indexscan & bitmapscan

SET enable_indexscan = off;
SELECT * FROM adratt WHERE att_id = 90

เอาท์พุทวิเคราะห์อธิบาย:

Bitmap Heap Scan on adratt  (cost=779.94..854.74 rows=83 width=20) (actual time=0.558..0.743 rows=83 loops=1)
  Recheck Cond: (att_id = 90)
  ->  Bitmap Index Scan on adratt_uni  (cost=0.00..779.86 rows=83 width=0) (actual time=0.544..0.544 rows=83 loops=1)
        Index Cond: (att_id = 90)
Total runtime: 0.894 ms
SET enable_bitmapscan = off
SELECT * FROM adratt WHERE att_id = 90

ผลลัพธ์ของEXPLAIN ANALYZE:

Seq Scan on adratt  (cost=0.00..1323.10 rows=83 width=20) (actual time=0.009..2.429 rows=83 loops=1)
  Filter: (att_id = 90)
Total runtime: 2.680 ms

ข้อสรุป

ตามที่คาดไว้ดัชนีหลายคอลัมน์จะใช้สำหรับการสืบค้นในคอลัมน์ที่สองเพียงอย่างเดียว
ตามที่คาดไว้มีประสิทธิภาพน้อยกว่า แต่แบบสอบถามยังเร็วกว่า3xโดยไม่มีดัชนี
หลังจากปิดใช้งานการสแกนดัชนีตัววางแผนแบบสอบถามจะเลือกสแกนบิตแมปฮีปซึ่งทำงานเกือบจะเร็ว หลังจากปิดใช้งานแล้วก็จะกลับไปเป็นการสแกนตามลำดับ


การจัดกลุ่มจะสร้างความแตกต่างถ้าจำนวนการแข่งขันในดัชนีสูงพอ (ดูที่นี่เพื่อพิสูจน์ - โปรดสังเกตว่าการรันสองครั้งเพื่อรับข้อมูลที่แคช)
Jack Douglas

1
@ JackDouglas: ฉันได้คิดเรื่องนี้มากกว่านี้ Clustering อาจช่วยโดยทั่วไปเพราะมันเป็นได้อย่างมีประสิทธิภาพนอกจากนี้ยังมีและvacuum full reindexนอกเหนือจากนั้นจะช่วยให้การสแกนดัชนีในคอลัมน์แรกหรือทั้งสองคอลัมน์นำหน้าได้มากแต่จะทำให้ข้อความค้นหาในคอลัมน์ที่สองเสียหาย ในตารางที่ทำคลัสเตอร์ใหม่แถวที่มีค่าเดียวกันในคอลัมน์ที่สองจะถูกกระจายออกไปเพื่อให้สามารถอ่านบล็อกได้สูงสุด
Erwin Brandstetter

28

ใหม่ 1) ใช่และไม่ใช่

สำหรับแบบสอบถามที่ใช้ทั้งสองคอลัมน์เช่นwhere (user_id1, user_id2) = (1,2)ไม่สำคัญว่าจะสร้างดัชนีใด

สำหรับการสืบค้นที่มีเงื่อนไขในหนึ่งในคอลัมน์เท่านั้นเช่นwhere user_id1 = 1มันมีความสำคัญเพราะโดยปกติแล้วคอลัมน์ "นำหน้า" เท่านั้นที่สามารถใช้สำหรับการเปรียบเทียบโดยเครื่องมือเพิ่มประสิทธิภาพ ดังนั้นwhere user_id1 = 1จะสามารถใช้ดัชนี (user_id1, user_id2) แต่มันจะไม่สามารถดัชนี (user_id2, user_id1) ในทุกกรณี

หลังจากเล่นกับสิ่งนี้ (หลังจาก Erwin กรุณาแสดงให้เราเห็นว่ามีการตั้งค่าที่ใช้งานได้) ดูเหมือนว่ามันขึ้นอยู่กับการกระจายข้อมูลของคอลัมน์ที่สองแม้ว่าฉันจะยังไม่ทราบว่าสถานการณ์ใดที่ทำให้เครื่องมือเพิ่มประสิทธิภาพสามารถใช้คอลัมน์ต่อท้าย สำหรับเงื่อนไขที่

Oracle 11 ซึ่งสามารถ (บางครั้ง) ใช้คอลัมน์ที่ไม่ได้อยู่ที่จุดเริ่มต้นของการกำหนดดัชนี

ใหม่ 2) ใช่มันจะสร้างดัชนี

ขอบคุณจากคู่มือ

การเพิ่มคีย์หลักจะสร้างดัชนี btree ที่ไม่ซ้ำกันในคอลัมน์หรือกลุ่มของคอลัมน์ที่ใช้ในคีย์หลัก

อีก 2a) Primary Key (user_id1,user_id2)จะสร้างดัชนีใน (user_id1, user_id2) (ซึ่งคุณสามารถหาข้อมูลด้วยตัวเองมากได้อย่างง่ายดายโดยเพียงแค่การสร้างดังกล่าวเป็นคีย์หลัก)

ฉันขอแนะนำให้คุณอ่านบทเกี่ยวกับดัชนีในคู่มือโดยพื้นฐานแล้วมันจะตอบคำถามทุกข้อข้างต้น

นอกจากนี้ดัชนีใดที่จะสร้าง? โดย depesz ทำงานได้ดีในการอธิบายคำสั่งในคอลัมน์ดัชนีและหัวข้ออื่น ๆ ที่เกี่ยวข้องกับดัชนี


11

โฆษณาที่ 1)
มีข้อ จำกัด ใน PostgreSQL เป็นเช่น @a_horse_with_no_name อธิบาย จนถึงดัชนีดัชนีหลายคอลัมน์เวอร์ชัน 8.0สามารถใช้สำหรับการสืบค้นในคอลัมน์นำเท่านั้น สิ่งนี้ได้รับการปรับปรุงในเวอร์ชัน 8.1 คู่มือปัจจุบัน Postgres 10 (ปรับปรุง) อธิบายว่า:

ดัชนี B-tree หลายคอลัมน์สามารถใช้กับเงื่อนไขการสืบค้นที่เกี่ยวข้องกับส่วนย่อยใด ๆ ของคอลัมน์ดัชนี แต่ดัชนีจะมีประสิทธิภาพมากที่สุดเมื่อมีข้อ จำกัด ในคอลัมน์นำหน้า (ซ้ายสุด) กฎที่แน่นอนคือข้อ จำกัด ความเสมอภาคในคอลัมน์นำรวมถึงข้อ จำกัด ความไม่เท่าเทียมใด ๆ ในคอลัมน์แรกที่ไม่มีข้อ จำกัด ความเท่าเทียมกันจะถูกใช้เพื่อ จำกัด ส่วนของดัชนีที่ถูกสแกน ข้อ จำกัด เกี่ยวกับคอลัมน์ทางด้านขวาของคอลัมน์เหล่านี้จะถูกตรวจสอบในดัชนีดังนั้นพวกเขาจึงบันทึกการเยี่ยมชมตารางที่เหมาะสม แต่จะไม่ลดสัดส่วนของดัชนีที่ต้องสแกน ตัวอย่างเช่นเมื่อกำหนดดัชนี(a, b, c)และเงื่อนไขการสืบค้นWHERE a = 5 AND b >= 42 AND c < 77ดัชนีจะต้องถูกสแกนจากรายการแรกด้วยa= 5 และb= 42 ขึ้นไปจนถึงรายการสุดท้ายด้วยa= 5. รายการดัชนีที่มีc> = 77 จะถูกข้ามไป แต่พวกเขายังคงต้องถูกสแกนผ่าน โดยทั่วไปแล้วดัชนีนี้สามารถใช้สำหรับการสืบค้นที่มีข้อ จำกัดbและ / หรือcไม่มีข้อ จำกัดa- แต่ดัชนีทั้งหมดจะต้องถูกสแกนดังนั้นในกรณีส่วนใหญ่ผู้วางแผนจะต้องการสแกนตารางตามลำดับโดยใช้ดัชนี

เน้นการขุด ฉันสามารถยืนยันได้จากประสบการณ์
ยังเห็นกรณีทดสอบเพิ่มคำตอบต่อมาฉันที่นี่


11

นี่คือคำตอบของแจ็คความคิดเห็นจะไม่ทำ

มีไม่ครอบคลุมดัชนีใน PostgreSQLก่อนรุ่น 9.2 เนื่องจากรุ่น MVCC จะต้องมีการเยี่ยมชม tuple ทุกชุดในชุดผลลัพธ์เพื่อตรวจสอบการมองเห็น คุณอาจคิดถึง Oracle

นักพัฒนา PostgreSQL พูดคุยเกี่ยวกับ"สแกนดัชนีเท่านั้น" ในความเป็นจริงแล้วฟีเจอร์นี้ได้รับการเผยแพร่ใน Postgres 9.2 อ่านข้อความกระทำ
Depesz เขียนบล็อกโพสต์ข้อมูลมาก

ดัชนีการครอบคลุมที่แท้จริง (อัพเดต) ถูกนำมาใช้กับINCLUDEclause ด้วย Postgres 11 ที่เกี่ยวข้อง:

สิ่งนี้ก็ออกไปเล็กน้อยเช่นกัน:

ขึ้นอยู่กับความจริงที่ว่า 'การสแกนแบบเต็ม' ของดัชนีมักเร็วกว่า 'การสแกนแบบเต็ม' ของตารางที่จัดทำดัชนีเนื่องจากคอลัมน์พิเศษในตารางที่ไม่ปรากฏในดัชนี

ตามที่รายงานไว้ในความคิดเห็นเกี่ยวกับคำตอบอื่น ๆ ของฉันฉันได้เรียกใช้การทดสอบด้วยตารางจำนวนเต็มสองจำนวนและไม่มีอะไรอื่นอีก ดัชนีถือคอลัมน์เดียวกันกับตาราง ขนาดของดัชนี btree อยู่ที่ประมาณ 2/3 ของตาราง ไม่เพียงพอที่จะอธิบายการเร่งความเร็วของปัจจัยที่ 3 ฉันวิ่งทดสอบมากขึ้นตามการตั้งค่าของคุณทำให้ง่ายขึ้นเป็นสองคอลัมน์และมี 100,000 แถว ในการติดตั้ง PostgreSQL 9.0 ของฉันผลลัพธ์นั้นสอดคล้องกัน

ถ้าตารางมีคอลัมน์เพิ่มเติม speedup กับดัชนีกลายเป็นรูปธรรมมากขึ้น แต่ที่แน่นอนไม่ได้เป็นปัจจัยเดียวที่นี่

เพื่อสรุปประเด็นสำคัญ:

  • ดัชนีหลายคอลัมน์สามารถใช้กับข้อความค้นหาในคอลัมน์ที่ไม่นำหน้า แต่การเพิ่มความเร็วจะอยู่รอบตัวประกอบ 3 สำหรับเกณฑ์การคัดเลือก (เปอร์เซ็นต์ของแถวในผลลัพธ์) สูงกว่าสำหรับ tuples ที่ใหญ่กว่าลดลงสำหรับส่วนที่ใหญ่กว่าของตารางในชุดผลลัพธ์

  • สร้างดัชนีเพิ่มเติมในคอลัมน์เหล่านั้นหากประสิทธิภาพเป็นสิ่งสำคัญ

  • หากคอลัมน์ที่เกี่ยวข้องทั้งหมดถูกรวมอยู่ในดัชนี (ดัชนีครอบคลุม) และแถวที่เกี่ยวข้องทั้งหมด (ต่อบล็อก) ปรากฏขึ้นในการทำธุรกรรมทั้งหมดคุณสามารถรับ"การสแกนดัชนีเท่านั้น"ใน pg 9.2 หรือใหม่กว่า


7
  1. สิ่งเหล่านี้เทียบเท่ากันหรือไม่ ถ้าไม่เช่นนั้นทำไม

    ดัชนี (user_id1, user_id2) และดัชนี (user_id2, user_id1)

สิ่งเหล่านี้ไม่เทียบเท่าและดัชนีที่พูดโดยทั่วไป (bar, baz) จะไม่มีประสิทธิภาพสำหรับการสืบค้นของแบบฟอร์ม select * from foo where baz=?

เออร์วินได้แสดงให้เห็นว่าดัชนีดังกล่าวสามารถเพิ่มความเร็วในการสืบค้นได้ แต่ผลกระทบนี้มี จำกัด และไม่เป็นแบบเดียวกันกับที่คุณคาดว่าดัชนีจะปรับปรุงการค้นหา - ขึ้นอยู่กับความจริงที่ว่า 'การสแกนแบบเต็ม' ของดัชนีมักจะ เร็วกว่า 'การสแกนเต็มรูปแบบ' ของตารางที่จัดทำดัชนีเนื่องจากคอลัมน์พิเศษในตารางที่ไม่ปรากฏในดัชนี

สรุป: ดัชนีสามารถช่วยให้สืบค้นได้แม้ในคอลัมน์ที่ไม่เป็นผู้นำ แต่เป็นหนึ่งในสองวิธีรองและค่อนข้างน้อยและไม่ได้อยู่ในลักษณะที่น่าทึ่งโดยปกติคุณคาดหวังว่าดัชนีจะช่วยได้เนื่องจากโครงสร้าง btree

วิธีที่ดัชนีสามารถช่วยได้คือหากการสแกนแบบเต็มของดัชนีมีราคาถูกกว่าการสแกนแบบเต็มของตารางอย่างมีนัยสำคัญและ: 1. การค้นหาตารางมีราคาถูก (เพราะมีไม่กี่คนหรือพวกเขาเป็นกลุ่ม) หรือ 2. ดัชนีครอบคลุมดังนั้นจึงไม่มีการค้นหาตารางใด ๆ เลยดูความคิดเห็นของ Erwins ที่นี่

testbed:

create table foo(bar integer not null, baz integer not null, qux text not null);

insert into foo(bar, baz, qux)
select random()*100, random()*100, 'some random text '||g from generate_series(1,10000) g;

แบบสอบถาม 1 (ไม่มีดัชนีกด74 บัฟเฟอร์ ):

explain (buffers, analyze, verbose) select max(qux) from foo where baz=0;
                                                  QUERY PLAN
--------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=181.41..181.42 rows=1 width=32) (actual time=3.301..3.302 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=74
   ->  Seq Scan on stack.foo  (cost=0.00..181.30 rows=43 width=32) (actual time=0.043..3.228 rows=52 loops=1)
         Output: bar, baz, qux
         Filter: (foo.baz = 0)
         Buffers: shared hit=74
 Total runtime: 3.335 ms

แบบสอบถาม 2 (พร้อมดัชนี - เครื่องมือเพิ่มประสิทธิภาพจะละเว้นดัชนี - กดปุ่ม74 บัฟเฟอร์อีกครั้ง):

create index bar_baz on foo(bar, baz);

explain (buffers, analyze, verbose) select max(qux) from foo where baz=0;
                                                  QUERY PLAN
--------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=199.12..199.13 rows=1 width=32) (actual time=3.277..3.277 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=74
   ->  Seq Scan on stack.foo  (cost=0.00..199.00 rows=50 width=32) (actual time=0.043..3.210 rows=52 loops=1)
         Output: bar, baz, qux
         Filter: (foo.baz = 0)
         Buffers: shared hit=74
 Total runtime: 3.311 ms

แบบสอบถาม 2 (พร้อมดัชนี - และเราหลอกลวงเครื่องมือเพิ่มประสิทธิภาพให้ใช้):

explain (buffers, analyze, verbose) select max(qux) from foo where bar>-1000 and baz=0;
                                                       QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=115.56..115.57 rows=1 width=32) (actual time=1.495..1.495 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=36 read=30
   ->  Bitmap Heap Scan on stack.foo  (cost=73.59..115.52 rows=17 width=32) (actual time=1.370..1.428 rows=52 loops=1)
         Output: bar, baz, qux
         Recheck Cond: ((foo.bar > (-1000)) AND (foo.baz = 0))
         Buffers: shared hit=36 read=30
         ->  Bitmap Index Scan on bar_baz  (cost=0.00..73.58 rows=17 width=0) (actual time=1.356..1.356 rows=52 loops=1)
               Index Cond: ((foo.bar > (-1000)) AND (foo.baz = 0))
               Buffers: shared read=30
 Total runtime: 1.535 ms

ดังนั้นการเข้าถึงผ่านดัชนีจะเร็วเป็นสองเท่าในกรณีนี้ซึ่งมี30 บัฟเฟอร์ - ซึ่งในแง่ของการจัดทำดัชนีคือ 'เร็วขึ้นเล็กน้อย'! และ YMMV ขึ้นอยู่กับขนาดสัมพัทธ์ของตารางและดัชนีพร้อมกับจำนวนแถวกรองและลักษณะการจัดกลุ่ม ของข้อมูลในตาราง

ในทางตรงกันข้ามการสืบค้นในคอลัมน์นำจะใช้ประโยชน์จากโครงสร้าง btree ของดัชนี - ในกรณีนี้การกดปุ่ม2 บัฟเฟอร์ :

explain (buffers, analyze, verbose) select max(qux) from foo where bar=0;
                                                       QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=75.70..75.71 rows=1 width=32) (actual time=0.172..0.173 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=38
   ->  Bitmap Heap Scan on stack.foo  (cost=4.64..75.57 rows=50 width=32) (actual time=0.036..0.097 rows=59 loops=1)
         Output: bar, baz, qux
         Recheck Cond: (foo.bar = 0)
         Buffers: shared hit=38
         ->  Bitmap Index Scan on bar_baz  (cost=0.00..4.63 rows=50 width=0) (actual time=0.024..0.024 rows=59 loops=1)
               Index Cond: (foo.bar = 0)
               Buffers: shared hit=2
 Total runtime: 0.209 ms
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.