ดัชนีใดที่จะใช้กับค่าซ้ำจำนวนมาก


14

ลองทำข้อสมมติสองสามข้อ:

ฉันมีตารางที่มีลักษณะดังนี้:

 a | b
---+---
 a | -1
 a | 17
  ...
 a | 21
 c | 17
 c | -3
  ...
 c | 22

ข้อเท็จจริงเกี่ยวกับชุดของฉัน:

  • ขนาดของตารางทั้งหมดคือ ~ 10 10แถว

  • ฉันมีแถว ~ 100k ที่มีค่าaในคอลัมน์aคล้ายกับค่าอื่น ๆ (เช่นc)

  • นั่นหมายถึง ~ 100k ค่าที่แตกต่างในคอลัมน์ 'a'

  • select sum(b) from t where a = 'c'ส่วนใหญ่เป็นคำสั่งของฉันจะอ่านทั้งหมดหรือส่วนใหญ่ของค่าสำหรับค่าที่กำหนดในเช่น

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

  • ตารางไม่ค่อยมีการปรับปรุงหากเราเคยกังวลเกี่ยวกับความเร็วในการอ่านเท่านั้น

  • ตารางค่อนข้างแคบ (พูดประมาณ ~ 25 ไบต์ต่อ tuple, + 23 ไบต์ค่าใช้จ่าย)

ตอนนี้คำถามคือดัชนีประเภทใดที่ฉันควรใช้? ความเข้าใจของฉันคือ:

  • BTreeปัญหาของฉันที่นี่คือดัชนี BTree จะมีขนาดใหญ่มากเท่าที่ฉันรู้ว่ามันจะเก็บค่าที่ซ้ำกัน หาก BTree มีขนาดใหญ่ฉันต้องอ่านทั้งดัชนีและส่วนต่าง ๆ ของตารางที่ดัชนีชี้ไป (เราสามารถใช้fillfactor = 100เพื่อลดขนาดของดัชนีได้เล็กน้อย)

  • BRINความเข้าใจของฉันคือฉันสามารถมีดัชนีขนาดเล็กได้ที่ค่าใช้จ่ายในการอ่านหน้าไร้ประโยชน์ การใช้วิธีการเล็ก ๆpages_per_rangeนั้นดัชนีนั้นใหญ่กว่า (ซึ่งเป็นปัญหาของ BRIN เนื่องจากฉันต้องอ่านดัชนีทั้งหมด) โดยมีวิธีการขนาดใหญ่pages_per_rangeที่ฉันจะอ่านหน้าไร้ประโยชน์มากมาย มีสูตรเวทย์มนตร์ที่จะค้นหาคุณค่าที่ดีpages_per_rangeซึ่งคำนึงถึงการแลกเปลี่ยนเหล่านั้นหรือไม่?

  • GIN / GiSTไม่แน่ใจว่าสิ่งเหล่านี้มีความเกี่ยวข้องที่นี่เนื่องจากส่วนใหญ่จะใช้สำหรับการค้นหาข้อความแบบเต็ม แต่ฉันก็ได้ยินว่าพวกเขาสามารถจัดการกับคีย์ที่ซ้ำกันได้ดี a GINหรือGiSTดรรชนีช่วยที่นี่หรือไม่

คำถามอื่นคือ Postgres จะใช้ความจริงที่ว่าตารางมีCLUSTERed (สมมติว่าไม่มีการอัพเดต) ในเครื่องมือวางแผนการสืบค้น (เช่นโดยการค้นหาแบบไบนารีสำหรับหน้าเริ่มต้น / สิ้นสุดที่เกี่ยวข้อง) ค่อนข้างเกี่ยวข้องฉันสามารถเก็บคอลัมน์ทั้งหมดของฉันใน BTree และวางตารางทั้งหมด (หรือบรรลุสิ่งที่เทียบเท่าฉันเชื่อว่าดัชนีเหล่านั้นเป็นกลุ่มในเซิร์ฟเวอร์ SQL) มีดัชนีไฮบริด BTree / BRIN ที่จะช่วยได้ไหม

ฉันควรหลีกเลี่ยงการใช้อาร์เรย์ในการจัดเก็บค่าของฉันเนื่องจากแบบสอบถามของฉันจะจบลงด้วยวิธีการที่อ่านได้น้อยลง (ฉันเข้าใจว่านี่จะช่วยลดค่าใช้จ่ายของ 23 ไบต์ต่อค่าโสหุ้ย tuple ด้วยการลดจำนวนของ tuples)


"ส่วนใหญ่ใช้สำหรับการค้นหาข้อความแบบเต็ม" GiST ถูกใช้อย่างกว้างขวางโดย PostGIS
jpmc26

คำตอบ:


15

BTree

ปัญหาของฉันอยู่ที่นี่ว่าดัชนี BTree จะมีขนาดใหญ่เนื่องจากมันจะเก็บค่าที่ซ้ำกัน (มันมีเช่นกันเพราะมันไม่สามารถถือว่าตารางที่มีการจัดเรียงทางกายภาพ) ถ้า BTree มีขนาดใหญ่ฉันจะต้องอ่านทั้งดัชนีและส่วนต่าง ๆ ของตารางที่ดัชนีชี้เกินไป ...

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

Brin

ความเข้าใจของฉันคือฉันสามารถมีดัชนีขนาดเล็กที่นี่ที่ค่าใช้จ่ายในการอ่านหน้าไร้ประโยชน์ การใช้วิธีการเล็ก ๆpages_per_rangeนั้นดัชนีนั้นใหญ่กว่า (ซึ่งเป็นปัญหาของ BRIN เนื่องจากฉันต้องอ่านดัชนีทั้งหมด) โดยมีวิธีการขนาดใหญ่pages_per_rangeที่ฉันจะอ่านหน้าไร้ประโยชน์มากมาย

ถ้าคุณไม่สามารถจ่ายค่าใช้จ่ายในการจัดเก็บข้อมูลของดัชนี btree ครอบคลุม Brin เหมาะสำหรับคุณเพราะคุณมีการจัดกลุ่มอยู่ในสถานที่ (ซึ่งเป็นสิ่งสำคัญสำหรับ Brin ที่จะเป็นประโยชน์) ดัชนี Brin มีขนาดเล็กpages_per_rangeเพื่อให้ทุกหน้ามีแนวโน้มที่จะอยู่ในหน่วยความจำถ้าคุณเลือกค่าที่เหมาะสม

มีสูตรวิเศษในการค้นหามูลค่าที่ดีของ pages_per_range ที่คำนึงถึงการแลกเปลี่ยนที่ไม่ชอบเหล่านั้นหรือไม่?

ไม่มีสูตรเวทมนต์ แต่เริ่มต้นด้วยขนาดที่pages_per_range น้อยกว่าขนาดเฉลี่ย (เป็นหน้า) ที่ครอบครองโดยaค่าเฉลี่ย คุณอาจพยายามย่อ: (จำนวนหน้า BRIN ที่สแกน) + (จำนวนหน้าฮีปที่สแกน) สำหรับการสืบค้นทั่วไป ค้นหาHeap Blocks: lossy=nในแผนการดำเนินการpages_per_range=1และเปรียบเทียบกับค่าอื่น ๆpages_per_rangeเช่นดูจำนวนบล็อกฮีปที่ไม่จำเป็นที่ถูกสแกน

GIN / สรุปสาระสำคัญ

ไม่แน่ใจว่าสิ่งเหล่านี้มีความเกี่ยวข้องที่นี่เนื่องจากส่วนใหญ่จะใช้สำหรับการค้นหาข้อความแบบเต็ม แต่ฉันก็ได้ยินว่าพวกเขาจัดการกับคีย์ที่ซ้ำกันได้ดี a GIN/ GiSTindex จะช่วยที่นี่หรือไม่

GIN อาจมีมูลค่าการพิจารณา แต่อาจไม่ใช่ GiST - แต่ถ้าการจัดกลุ่มตามธรรมชาติดีจริงๆ BRIN น่าจะเป็นทางออกที่ดีกว่า

นี่คือการเปรียบเทียบตัวอย่างระหว่างประเภทดัชนีที่แตกต่างกันสำหรับข้อมูลจำลองเล็กน้อยเช่นคุณ:

ตารางและดัชนี:

create table foo(a,b,c) as
select *, lpad('',20)
from (select chr(g) a from generate_series(97,122) g) a
     cross join (select generate_series(1,100000) b) b
order by a;
create index foo_btree_covering on foo(a,b);
create index foo_btree on foo(a);
create index foo_gin on foo using gin(a);
create index foo_brin_2 on foo using brin(a) with (pages_per_range=2);
create index foo_brin_4 on foo using brin(a) with (pages_per_range=4);
vacuum analyze;

ขนาดความสัมพันธ์:

select relname "name", pg_size_pretty(siz) "size", siz/8192 pages, (select count(*) from foo)*8192/siz "rows/page"
from( select relname, pg_relation_size(C.oid) siz
      from pg_class c join pg_namespace n on n.oid = c.relnamespace
      where nspname = current_schema ) z;
ชื่อ | ขนาด | หน้า | แถว / หน้า
: ----------------- | : ------ | ----: | --------:
foo | 149 MB | 19118 | 135
foo_btree_covering | 56 MB | 7132 | 364
foo_btree | 56 MB | 7132 | 364
foo_gin | 2928 kB | 366 | 7103
foo_brin_2 | 264 kB | 33 | 78787
foo_brin_4 | 136 kB | 17 | 152941

ครอบคลุม btree:

explain analyze select sum(b) from foo where a='a';
| QUERY PLAN |
| : ------------------------------------------------- -------------------------------------------------- ------------------------------------------- |
| ผลรวม (ราคา = 3282.57..3282.58 แถว = 1 ความกว้าง = 8) (เวลาจริง = 45.942..45.942 แถว = 1 ลูป = 1)
| -> ดัชนีสแกนอย่างเดียวโดยใช้ foo_btree_covering บน foo (ราคา = 0.43 ..3017.80 แถว = 105907 กว้าง = 4) (เวลาจริง = 0.038..27.286 แถว = 100000 ลูป = 1) |
| ดัชนี Cond: (a = 'a' :: text) |
| กองการดึงข้อมูล: 0 |
| เวลาในการวางแผน: 0.099 ms |
| เวลาดำเนินการ: 45.968 ms |

btree ธรรมดา:

drop index foo_btree_covering;
explain analyze select sum(b) from foo where a='a';
| QUERY PLAN |
| : ------------------------------------------------- -------------------------------------------------- ----------------------------- |
| ผลรวม (ราคา = 4064.57..4064.58 แถว = 1 ความกว้าง = 8) (เวลาจริง = 54.242..54.242 แถว = 1 ลูป = 1)
| -> การสแกนดัชนีโดยใช้ foo_btree บน foo (ราคา = 0.43..3799.80 แถว = ความกว้าง 105907 = 4) (เวลาจริง = 0.037..33.084 แถว = 100000 ลูป = 1) |
| ดัชนี Cond: (a = 'a' :: text) |
| เวลาในการวางแผน: 0.135 ms |
| เวลาดำเนินการ: 54.280 ms |

BRIN pages_per_range = 4:

drop index foo_btree;
explain analyze select sum(b) from foo where a='a';
| QUERY PLAN |
| : ------------------------------------------------- -------------------------------------------------- ----------------------------- |
| ผลรวม (ราคา = 21595.38..21595.39 แถว = 1 ความกว้าง = 8) (เวลาจริง = 52.455..52.455 แถว = 1 ลูป = 1) |
| -> Bitmap Heap สแกนบน foo (ราคา = 888.78..21330.61 แถว = 105907 width = 4) (เวลาจริง = 2.738..31.967 แถว = 100000 ลูป = 1) |
| ตรวจสอบอีกครั้ง Cond: (a = 'a' :: text) |
| แถวถูกลบโดยดัชนีรั้งอีกครั้ง: 96 |
| บล็อกฮีป: lossy = 736 |
| -> ดัชนีบิตแมปสแกน foo_brin_4 (ราคา = 0.00..862.30 แถว = 105907 กว้าง = 0) (เวลาจริง = 2.720..2.720 แถว = 7360 ลูป = 1) |
| ดัชนี Cond: (a = 'a' :: text) |
| เวลาในการวางแผน: 0.101 ms |
| เวลาดำเนินการ: 52.501 ms |

BRIN pages_per_range = 2:

drop index foo_brin_4;
explain analyze select sum(b) from foo where a='a';
| QUERY PLAN |
| : ------------------------------------------------- -------------------------------------------------- ----------------------------- |
| ผลรวม (ราคา = 21659.38..21659.39 แถว = 1 ความกว้าง = 8) (เวลาจริง = 53.971..53.971 แถว = 1 ลูป = 1)
| -> Bitmap Heap สแกนบน foo (ราคา = 952.78..21394.61 แถว = 105907 width = 4) (เวลาจริง = 5.286..33.492 แถว = 100000 ลูป = 1) |
| ตรวจสอบอีกครั้ง Cond: (a = 'a' :: text) |
| แถวถูกลบโดยดัชนีรั้งอีกครั้ง: 96 |
| บล็อกฮีป: lossy = 736 |
| -> ดัชนีบิตแมปสแกน foo_brin_2 (ราคา = 0.00..926.30 แถว = 105907 กว้าง = 0) (เวลาจริง = 5.275..5.275 แถว = 7360 ลูป = 1) |
| ดัชนี Cond: (a = 'a' :: text) |
| เวลาในการวางแผน: 0.095 ms |
| เวลาดำเนินการ: 54.016 ms |

จิน:

drop index foo_brin_2;
explain analyze select sum(b) from foo where a='a';
| QUERY PLAN |
| : ------------------------------------------------- -------------------------------------------------- ------------------------------ |
| ผลรวม (ราคา = 21687.38..21687.39 แถว = 1 ความกว้าง = 8) (เวลาจริง = 55.331..55.331 แถว = 1 ลูป = 1) |
| -> Bitmap Heap สแกนบน foo (ราคา = 980.78..21422.61 แถว = 105907 width = 4) (เวลาจริง = 12.377..33.956 แถว = 100000 ลูป = 1) |
| ตรวจสอบอีกครั้ง Cond: (a = 'a' :: text) |
| บล็อกฮีป: แน่นอน = 736 |
| -> ดัชนีบิตแมปสแกน foo_gin (ราคา = 0.00..954.30 แถว = ความกว้าง 1,0907 = 0) (เวลาจริง = 12.271 ..12.271 แถว = 100000 ลูป = 1) |
| ดัชนี Cond: (a = 'a' :: text) |
| เวลาในการวางแผน: 0.118 ms |
| เวลาดำเนินการ: 55.366 ms |

dbfiddle ที่นี่


ดังนั้นดัชนีที่ครอบคลุมจะข้ามการอ่านตารางทั้งหมดที่ค่าใช้จ่ายของพื้นที่ดิสก์หรือไม่ ดูเหมือนจะเป็นการแลกเปลี่ยนที่ดี ฉันคิดว่าเราหมายถึงสิ่งเดียวกันสำหรับดัชนี BRIN โดย 'อ่านดัชนีทั้งหมด' (แก้ไขฉันถ้าฉันผิด) ฉันหมายถึงการสแกนดัชนี BRIN ทั้งหมดซึ่งฉันคิดว่าเป็นสิ่งที่เกิดขึ้นในdbfiddle.uk/ ......ไม่?
foo

@foo เกี่ยวกับ"(มีเช่นกันเนื่องจากไม่สามารถถือว่าตารางถูกจัดเรียงทางร่างกายได้)" ลำดับทางกายภาพ (คลัสเตอร์หรือไม่) ของตารางไม่เกี่ยวข้อง ดัชนีมีค่าในลำดับที่ถูกต้อง แต่ดัชนี B-tree ของ Postgres ต้องเก็บค่าทั้งหมด (และใช่หลาย ๆ ครั้ง) นั่นคือวิธีการออกแบบ การเก็บแต่ละค่าที่แตกต่างเพียงครั้งเดียวจะเป็นคุณสมบัติที่ดี / การปรับปรุง คุณสามารถแนะนำให้กับนักพัฒนา Postgres (และยังช่วยในการนำไปใช้) แจ็คควรแสดงความคิดเห็นฉันคิดว่าการติดตั้ง b-trees ของ Oracle นั้นทำได้
ypercubeᵀᴹ

1
@foo - คุณถูกต้องอย่างสมบูรณ์การสแกนดัชนีBRINจะสแกนดัชนีทั้งหมดเสมอ ( pgcon.org/2016/schedule/attachments/ ......สไลด์สุดท้ายที่ 2) - แม้ว่ามันจะไม่ปรากฏในแผนอธิบายในซอ , ใช่ไหม?
แจ็คบอกว่าลอง topanswers.xyz

2
@ ypercubeᵀᴹคุณสามารถใช้ COMPRESS บน Oracle ที่เก็บคำนำหน้าแต่ละคำที่แตกต่างกันหนึ่งครั้งต่อบล็อก
แจ็คบอกว่าลอง topanswers.xyz

@ JackDouglas ฉันอ่านBitmap Index Scanตามความหมาย 'อ่านดัชนีบรินทั้งหมด' แต่อาจเป็นเพราะการอ่านผิด Oracle COMPRESSดูเหมือนว่ามีบางสิ่งที่จะมีประโยชน์ที่นี่เพราะมันจะลดขนาดของทรี B แต่ฉันติดอยู่กับ pg!
foo

6

นอกจากbtreeและbrinซึ่งดูเหมือนตัวเลือกที่เหมาะสมที่สุดแล้วตัวเลือกแปลกใหม่ที่อาจจะคุ้มค่าในการตรวจสอบพวกเขาอาจมีประโยชน์หรือไม่ในกรณีของคุณ:

  • INCLUDEดัชนี พวกเขาจะ - หวังว่า - ในเวอร์ชันหลักถัดไป (10) ของ Postgres ประมาณเดือนกันยายน 2560 ดัชนีบน(a) INCLUDE (b)มีโครงสร้างเดียวกันกับดัชนี(a)แต่รวมอยู่ในหน้าใบไม้ค่าทั้งหมดของb(แต่ไม่มีการเรียงลำดับ) SELECT * FROM t WHERE a = 'a' AND b = 2 ;ซึ่งหมายความว่าคุณไม่สามารถใช้งานได้ตัวอย่างสำหรับ ดัชนีอาจถูกนำมาใช้ แต่ในขณะที่(a,b)ดัชนีจะค้นหาแถวที่ตรงกันด้วยการค้นหาเพียงครั้งเดียวดัชนี include จะต้องผ่านค่า (อาจเป็น 100K ในกรณีของคุณ) ที่จับคู่a = 'a'และตรวจสอบbค่า
    ในทางกลับกันดัชนีนั้นมีความกว้างน้อยกว่าดัชนีเล็กน้อย(a,b)และคุณไม่จำเป็นต้องมีคำสั่งซื้อbสำหรับการคำนวณของSUM(b)คุณ คุณอาจมีตัวอย่างเช่น(a) INCLUDE (b,c,d) ซึ่งสามารถใช้สำหรับคิวรีที่คล้ายกับของคุณที่รวมอยู่ในทั้ง 3 คอลัมน์

  • กรอง (บางส่วน) ดัชนี ข้อเสนอแนะที่อาจเสียงบิตบ้า*ครั้งแรก:

    CREATE INDEX flt_a  ON t (b) WHERE (a = 'a') ;
    ---
    CREATE INDEX flt_xy ON t (b) WHERE (a = 'xy') ;
    

    หนึ่งดัชนีสำหรับแต่ละaค่า ในกรณีของคุณประมาณ 100K ดัชนี ในขณะนี้ฟังดูดีให้พิจารณาว่าแต่ละดัชนีจะเล็กมากทั้งขนาด (จำนวนแถว) และความกว้าง (เนื่องจากจะเก็บbค่าเท่านั้น) ในทุกด้านแม้ว่ามัน (ดัชนี 100K ร่วมกัน) จะทำหน้าที่เป็นดัชนี b-tree ใน(a,b)ขณะที่ใช้พื้นที่ของ(b)ดัชนี
    ข้อเสียคือคุณจะต้องสร้างและบำรุงรักษาด้วยตนเองทุกครั้งที่มีการเพิ่มมูลค่าใหม่aลงในตาราง เนื่องจากตารางของคุณค่อนข้างเสถียรโดยไม่มีการแทรก / อัปเดต (หรือใด ๆ ) จำนวนมากจึงไม่มีปัญหา

  • ตารางสรุป เนื่องจากตารางค่อนข้างเสถียรคุณสามารถสร้างและเติมตารางสรุปด้วยการรวมทั่วไปที่คุณต้องการ ( sum(b), sum(c), sum(d), avg(b), count(distinct b)และอื่น ๆ ) มันจะมีขนาดเล็ก (เพียง 100K แถว) และจะต้องมีประชากรเพียงครั้งเดียวและมีการปรับปรุงเฉพาะเมื่อแทรก / ปรับปรุง / ลบแถวในตารางหลัก

*: คัดลอกความคิดจาก บริษัท นี้ที่ใช้ดัชนี 10 ล้านรายการในระบบการผลิตของพวกเขา: กอง: ใช้ดัชนี Postgresql กำลังผลิต 10 ล้านรายการ (และเพิ่มขึ้นเรื่อย ๆ)


1 น่าสนใจ แต่ที่คุณชี้ให้เห็น pg 10 ยังไม่ออก 2 ไม่เสียงบ้า (หรืออย่างน้อยกับ 'ภูมิปัญญาทั่วไป') ผมจะมีการอ่านตั้งแต่ที่คุณชี้ให้เห็นว่าสามารถทำงานร่วมกับฉันเกือบจะไม่มีการเขียนขั้นตอนการทำงาน 3. จะไม่ทำงานสำหรับฉันฉันใช้SUMเป็นตัวอย่าง แต่ในทางปฏิบัติแบบสอบถามของฉันไม่สามารถคำนวณล่วงหน้าได้ (พวกเขาจะคล้ายกับselect ... from t where a = '?' and ??wjere ??จะเป็นเงื่อนไขที่ผู้ใช้กำหนดอื่น ๆ
foo

1
เราไม่สามารถช่วยได้ถ้าเราไม่รู้ว่าอะไร??คือ)
ypercubeᵀᴹ

คุณพูดถึงดัชนีที่กรองแล้ว สิ่งที่เกี่ยวกับการแบ่งตารางหรือไม่
jpmc26

@ jpmc26 ขำ ๆ ฉันคิดว่าจะเพิ่มคำตอบว่าคำแนะนำของดัชนีที่กรองแล้วนั้นเป็นรูปแบบของการแบ่งพาร์ติชัน การแบ่งพาร์ติชันอาจมีประโยชน์ที่นี่ แต่ฉันไม่แน่ใจ มันจะส่งผลให้ดัชนี / ตารางขนาดเล็กจำนวนมาก
ypercubeᵀᴹ

2
ฉันคาดว่าบางส่วนที่ครอบคลุมดัชนี btree จะเป็นราชาแห่งการปฏิบัติงานที่นี่เนื่องจากข้อมูลแทบจะไม่เคยอัพเดต แม้ว่านั่นหมายถึงดัชนี 100k ขนาดดัชนีทั้งหมดมีขนาดเล็กที่สุด (ยกเว้นดัชนี BRIN แต่มี Postgres ต้องอ่านและกรองหน้าฮีปเพิ่มเติม) การสร้างดัชนีสามารถดำเนินการอัตโนมัติด้วย SQL แบบไดนามิก คำสั่งตัวอย่างDOในคำตอบที่เกี่ยวข้องนี้
Erwin Brandstetter
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.