ดัชนีในคีย์หลักไม่ได้ใช้ในการเข้าร่วมง่าย


16

ฉันมีคำนิยามตารางและดัชนีต่อไปนี้:

CREATE TABLE munkalap (
    munkalap_id serial PRIMARY KEY,
    ...
);

CREATE TABLE munkalap_lepes (
    munkalap_lepes_id serial PRIMARY KEY,
    munkalap_id integer REFERENCES munkalap (munkalap_id),
    ...
);

CREATE INDEX idx_munkalap_lepes_munkalap_id ON munkalap_lepes (munkalap_id);

ทำไมไม่มีดัชนีใน munkalap_id ที่ใช้ในการสืบค้นต่อไปนี้?

EXPLAIN ANALYZE SELECT ml.* FROM munkalap m JOIN munkalap_lepes ml USING (munkalap_id);

QUERY PLAN
Hash Join  (cost=119.17..2050.88 rows=38046 width=214) (actual time=0.824..18.011 rows=38046 loops=1)
  Hash Cond: (ml.munkalap_id = m.munkalap_id)
  ->  Seq Scan on munkalap_lepes ml  (cost=0.00..1313.46 rows=38046 width=214) (actual time=0.005..4.574 rows=38046 loops=1)
  ->  Hash  (cost=78.52..78.52 rows=3252 width=4) (actual time=0.810..0.810 rows=3253 loops=1)
        Buckets: 1024  Batches: 1  Memory Usage: 115kB
        ->  Seq Scan on munkalap m  (cost=0.00..78.52 rows=3252 width=4) (actual time=0.003..0.398 rows=3253 loops=1)
Total runtime: 19.786 ms

มันเหมือนกันแม้ว่าฉันจะเพิ่มตัวกรอง:

EXPLAIN ANALYZE SELECT ml.* FROM munkalap m JOIN munkalap_lepes ml USING (munkalap_id) WHERE NOT lezarva;

QUERY PLAN
Hash Join  (cost=79.60..1545.79 rows=1006 width=214) (actual time=0.616..10.824 rows=964 loops=1)
  Hash Cond: (ml.munkalap_id = m.munkalap_id)
  ->  Seq Scan on munkalap_lepes ml  (cost=0.00..1313.46 rows=38046 width=214) (actual time=0.007..5.061 rows=38046 loops=1)
  ->  Hash  (cost=78.52..78.52 rows=86 width=4) (actual time=0.587..0.587 rows=87 loops=1)
        Buckets: 1024  Batches: 1  Memory Usage: 4kB
        ->  Seq Scan on munkalap m  (cost=0.00..78.52 rows=86 width=4) (actual time=0.014..0.560 rows=87 loops=1)
              Filter: (NOT lezarva)
Total runtime: 10.911 ms

คำตอบ:


22

หลายคนเคยได้ยินคำแนะนำว่า "การสแกนตามลำดับไม่ดี" และพยายามกำจัดพวกเขาออกจากแผน แต่มันไม่ง่ายเลย หากการสืบค้นครอบคลุมทุกแถวในตารางการสแกนตามลำดับเป็นวิธีที่เร็วที่สุดในการรับแถวเหล่านั้น นี่คือเหตุผลที่คิวรีการเข้าร่วมดั้งเดิมของคุณใช้การสแกน seq เนื่องจากต้องมีแถวทั้งหมดในตารางทั้งสอง

เมื่อวางแผนเคียวรีเครื่องมือวางแผนของ Postgres จะประเมินค่าใช้จ่ายของการดำเนินการต่างๆ (การคำนวณลำดับและการสุ่ม IO) ภายใต้โครงร่างที่เป็นไปได้ที่แตกต่างกันและเลือกแผนที่ประมาณการว่ามีต้นทุนต่ำที่สุด เมื่อทำการ IO จากการหมุนที่เก็บข้อมูล (ดิสก์) โดยปกติแล้ว IO แบบสุ่มจะช้ากว่า Sequential IO อย่างมากการกำหนดค่า pg เริ่มต้นสำหรับrandom_page_cost และ seq_page_costประมาณราคา 4: 1

สิ่งที่ต้องพิจารณาเหล่านี้มาพิจารณาเมื่อพิจารณาวิธีการเข้าร่วมหรือตัวกรองซึ่งใช้ดัชนีเทียบกับวิธีหนึ่งซึ่งสแกนตารางตามลำดับ เมื่อใช้ดัชนีแผนอาจค้นหาแถวอย่างรวดเร็วผ่านดัชนีจากนั้นต้องพิจารณาการอ่านบล็อกแบบสุ่มเพื่อแก้ไขข้อมูลแถว ในกรณีที่แบบสอบถามที่สองของคุณซึ่งเพิ่มภาคการกรองWHERE NOT lezarvaคุณสามารถดูว่าสิ่งนี้มีผลต่อการประมาณการการวางแผนในผลลัพธ์อธิบายการวิเคราะห์ ผู้วางแผนประมาณ 1006 แถวที่เกิดจากการเข้าร่วม (ซึ่งค่อนข้างใกล้เคียงกับชุดผลลัพธ์จริงของ 964) เนื่องจาก munkalap_lepes ของตารางที่ใหญ่กว่ามีแถวประมาณ 38K ผู้วางแผนเห็นว่าการเข้าร่วมจะต้องเข้าถึงประมาณ 1006/38046 หรือ 1/38 ของแถวในตาราง นอกจากนี้ยังรู้ว่าความกว้างของแถวเฉลี่ยคือ 214 ไบต์และบล็อกคือ 8K ดังนั้นจึงมีประมาณ 38 แถว / บล็อก

ด้วยสถิติเหล่านี้ผู้วางแผนเห็นว่ามีความเป็นไปได้ที่การเข้าร่วมจะต้องอ่านบล็อคข้อมูลทั้งหมดหรือเกือบทั้งหมดของตาราง เนื่องจากการค้นหาดัชนีไม่ได้ฟรีเช่นกันและการคำนวณเพื่อสแกนบล็อกที่ประเมินสภาพตัวกรองนั้นมีราคาถูกมากเมื่อเทียบกับ IO ผู้วางแผนได้เลือกที่จะสแกนตารางตามลำดับและหลีกเลี่ยงค่าใช้จ่ายของดัชนีและอ่านแบบสุ่มตามที่คำนวณ seq scan จะเร็วขึ้น

ในโลกแห่งความเป็นจริงข้อมูลมักจะมีอยู่ในหน่วยความจำผ่านหน้าแคชของระบบปฏิบัติการดังนั้นทุก ๆ บล็อกที่อ่านต้องใช้ IO มันอาจเป็นเรื่องยากที่จะคาดการณ์ว่าแคชจะมีประสิทธิภาพสำหรับการสืบค้นที่กำหนดอย่างไร แต่ผู้วางแผน Pg ใช้การวิเคราะห์พฤติกรรมแบบง่าย ๆ ค่าการกำหนดค่าeffective_cache_size จะแจ้งให้ผู้ประเมินประมาณการถึงความเป็นไปได้ของต้นทุน IO ที่เกิดขึ้นจริง ค่าที่มากขึ้นจะทำให้ประเมินค่าใช้จ่ายที่ต่ำกว่าในการสุ่ม IO และอาจทำให้เกิดอคติต่อวิธีการขับเคลื่อนดัชนีผ่านการสแกนตามลำดับ


ขอบคุณนี่คือคำอธิบายที่ดีที่สุดและกระชับที่สุดที่ฉันได้อ่าน ชี้แจงประเด็นสำคัญบางประการ
dezso

1
คำอธิบายที่ยอดเยี่ยม อย่างไรก็ตามการคำนวณแถว / หน้าข้อมูลนั้นค่อนข้างปิดไป คุณต้องคำนึงถึงส่วนหัวของหน้า (24 ไบต์) + 4 ไบต์สำหรับตัวชี้รายการแต่ละแถว + ส่วนหัวของแถวHeapTupleHeader(23 ไบต์ต่อแถว) + NULL bitmask + การจัดตำแหน่งตาม MAXALIGN ในที่สุดจำนวน padding ที่ไม่รู้จักเนื่องจากการจัดตำแหน่งข้อมูลขึ้นอยู่กับชนิดข้อมูลของคอลัมน์และลำดับของพวกเขา โดยรวมแล้วมีไม่เกิน 33 แถวในหน้า 8 kb ในกรณีนี้ (ไม่คำนึงถึง TOAST)
Erwin Brandstetter

1
@ErwinBrandstetter ขอบคุณที่กรอกข้อมูลในการคำนวณขนาดแถวที่เข้มงวดยิ่งขึ้น ฉันมักจะสันนิษฐานเอาท์พุทประมาณความกว้างของแถวโดยอธิบายจะรวมข้อควรพิจารณาต่อแถวเช่นส่วนหัวและ NULL-bitmask แต่ไม่ใช่ค่าใช้จ่ายระดับหน้า
dbenhur

1
@ dbenhur: คุณสามารถเรียกใช้อย่างรวดเร็วEXPLAIN ANALYZE SELECT foo from barด้วยตารางดัมมี่พื้นฐานในการตรวจสอบ นอกจากนี้พื้นที่ว่างบนดิสก์ที่เกิดขึ้นจริงขึ้นอยู่กับการจัดตำแหน่งข้อมูลซึ่งเป็นเรื่องยากที่จะคำนึงถึงเมื่อมีการดึงแถวบางแถวเท่านั้น ความกว้างของแถวEXPLAINหมายถึงข้อกำหนดพื้นที่พื้นฐานสำหรับชุดคอลัมน์ที่ดึงออกมา
Erwin Brandstetter

5

คุณกำลังเรียกคืนแถวทั้งหมดจากทั้งสองตารางดังนั้นจึงไม่มีประโยชน์จริงโดยใช้การสแกนดัชนี การสแกนดัชนีจะใช้งานได้ก็ต่อเมื่อคุณเลือกเพียงไม่กี่แถวจากตาราง (โดยทั่วไปแล้วจะน้อยกว่า 10% -15%)


ใช่คุณพูดถูก :) ฉันพยายามชี้แจงสถานการณ์ด้วยตัวพิมพ์ที่เฉพาะเจาะจงมากขึ้นดูคำถามสุดท้าย
dezso

@dezso: สิ่งเดียวกัน หากคุณมีดัชนีใน(lezarva, munkalap_id)และมันก็เพียงพอแล้วมันอาจถูกนำมาใช้ NOTทำให้น่าจะเป็นน้อย
ypercubeᵀᴹ

ฉันได้เพิ่มดัชนีบางส่วนตามคำแนะนำของคุณและมีการใช้งานดังนั้นครึ่งหนึ่งของปัญหาจึงได้รับการแก้ไข แต่ผมจะไม่คาดหวังดัชนีในต่างประเทศที่สำคัญเป็นที่ไร้ประโยชน์ที่ได้รับว่าอยากจะเข้าร่วมกับเพียง 87 ค่าเมื่อเทียบกับเดิม 3252.
Dezso

1
@dezso แถวเฉลี่ย 214 ไบต์กว้างดังนั้นคุณจะมีน้อยกว่า 40 แถวต่อบล็อกข้อมูล 8K การเลือกของดัชนีก็ประมาณ 1/40 (1006/38046) ดังนั้นตัวเลข Pg ที่อ่านบล็อกทั้งหมดเรียงตามลำดับนั้นมีราคาถูกกว่าดังนั้นการอ่านบล็อกที่มีความน่าจะเป็นที่มีจำนวนเท่ากันโดยสุ่มเมื่อใช้ดัชนี tradoffs ที่ประมาณไว้เหล่านี้อาจมีผลต่อการกำหนดค่า effective_cache_size และ random_page_cost
dbenhur

@ dbenhur: คุณสามารถให้ความคิดเห็นของคุณเป็นคำตอบที่เหมาะสมหรือไม่?
dezso
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.