Çok sayıda yinelenen değerle hangi dizin kullanılır?


14

Birkaç varsayım yapalım:

Şöyle bir tablo var:

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

Benim setimle ilgili bilgiler:

  • Tüm tablonun boyutu ~ 10 10 satırdır.

  • Diğer değerler (örneğin ) benzer a, sütunda değeri ~ ~ 100k satır var .ac

  • Bu, 'a' sütununda ~ 100k farklı değer anlamına gelir.

  • Sorgularımın çoğu, örneğin belirli bir değer için değerlerin tümünü veya çoğunu okuyacaktır select sum(b) from t where a = 'c'.

  • Tablo, ardışık değerler fiziksel olarak yakın olacak şekilde yazılır (sırayla yazılır veya CLUSTERbu tablo ve sütunda kullanıldığını varsayarız a).

  • Tablo nadiren güncellenirse, sadece okuma hızı ile ilgileniriz.

  • Tablo nispeten dardır (her bir demet için ~ 25 bayt, + 23 bayt ek yük).

Şimdi soru şu, ne tür bir dizin kullanmalıyım? Benim anlayışım:

  • BTree Burada benim sorunum BTree endeksi ben bildiğim kadarıyla yinelenen değerleri (tablo fiziksel olarak sıralanmış olduğunu varsayamazsınız çünkü) depolamak beri büyük olacaktır. BTree büyükse, hem indeksi hem de indeksin işaret ettiği tablonun bölümlerini okumak zorundayım. ( fillfactor = 100Dizinin boyutunu biraz azaltmak için kullanabiliriz .)

  • BRIN Anladığım kadarıyla, işe yaramaz sayfaları okumak pahasına burada küçük bir dizin oluşturabileceğim. Küçük bir pages_per_rangedizin kullanmak daha büyük anlamına gelir (tüm dizini okumak gerekir çünkü BRIN ile ilgili bir sorun), pages_per_rangeçok sayıda işe yaramaz sayfa okuyacağım büyük bir araç. pages_per_rangeBu değiş tokuşları dikkate alan iyi bir değer bulmak için sihirli bir formül var mı ?

  • GIN / GiST Çoğunlukla tam metin araması için kullanıldığından, bunların burada alakalı olduğundan emin değilim, ancak yinelenen anahtarlarla uğraşmada iyi olduklarını duyuyorum. A GINveya GiSTdizin burada yardımcı olur mu?

Başka bir soru, Postgres'in CLUSTERsorgu planlayıcıda bir tablonun düzenlendiğini (güncelleme olmadığı varsayılarak) (örn. İlgili başlangıç ​​/ bitiş sayfalarını ikili olarak arayarak) kullanması mıdır? Biraz ilgili, tüm sütunlarımı bir BTree'de saklayabilir ve tabloyu tamamen bırakabilir miyim (veya eşdeğer bir şey elde edebilirim, bunların SQL sunucusunda kümelenmiş dizinler olduğuna inanıyorum)? Burada yardımcı olacak hibrit BTree / BRIN indeksi var mı?

Benim sorgu bu şekilde daha az okunabilir sona erecek çünkü benim değerleri depolamak için diziler kullanmaktan kaçınmak istiyorum (Bu tuples sayısını azaltarak tepesi başına 23 bayt maliyetini azaltacağını anlıyorum).


"çoğunlukla tam metin araması için kullanılır" GiST, PostGIS tarafından oldukça yaygın bir şekilde kullanılmaktadır.
jpmc26

Yanıtlar:


15

B-ağacı

Burada benim sorunum BTree endeksi yinelenen değerleri depolamak afaict büyük olacaktır (tablonun fiziksel olarak sıralandığı varsayılamıyor çünkü). BTree büyükse, hem indeksi hem de tablonun dizin işaret ettiği bölümlerini okumak zorunda kalıyorum ...

Mutlaka değil - 'kapsayan' bir btree indeksine sahip olmak en hızlı okuma süresi olacaktır ve eğer istediğiniz her şey buysa (yani ekstra depolama alanı varsa), o zaman en iyi bahistir.

BRIN

Anladığım kadarıyla, işe yaramaz sayfaları okumak pahasına burada küçük bir dizin oluşturabilirim. Küçük bir pages_per_rangedizin kullanmak daha büyük anlamına gelir (tüm dizini okumak gerekir çünkü BRIN ile ilgili bir sorun), pages_per_rangeçok sayıda işe yaramaz sayfa okuyacağım büyük bir araç.

Bir kaplama btree indeksinin depolama yükünü karşılayamıyorsanız, BRIN sizin için idealdir, çünkü zaten yerinde kümelemeye sahipsiniz (bu BRIN'nin yararlı olması için çok önemlidir ). BRIN dizinleri küçük olduğundan , uygun bir değer seçerseniz tüm sayfaların bellekte olması muhtemeldir pages_per_range.

Bu değiş tokuşları hesaba katan iyi bir pages_per_range değeri bulmak için sihirli bir formül var mı?

Sihirli formül yok, ancak ortalama değerin işgal ettiği ortalama boyuttan (sayfalarda) pages_per_range biraz daha az ile başlayın a. Muhtemelen simge durumuna küçültmeye çalışıyorsunuz: (taranan BRIN sayfa sayısı) + (taranan yığın sayfa sayısı). Heap Blocks: lossy=nYürütme planını arayın pages_per_range=1ve diğer değerlerle karşılaştırın pages_per_range- yani kaç gereksiz yığın bloğunun tarandığını görün.

CİN / GiST

Çoğunlukla tam metin araması için kullanıldıklarından, bunların alakalı olduğundan emin değilim, ancak yinelenen anahtarlarla uğraşmada iyi olduklarını duyuyorum. Misiniz ya bir GIN/ GiSTburada indeks yardım?

GIN düşünmeye değer olabilir, ancak muhtemelen GiST değil - ancak doğal kümelenme gerçekten iyi ise, BRIN muhtemelen daha iyi bir bahis olacaktır.

İşte sizinki gibi kukla veriler için farklı dizin türleri arasında örnek bir karşılaştırma:

tablo ve dizinler:

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;

ilişki boyutları:

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;
adı | boyut | sayfalar | satır / sayfa
: ----------------- | : ------ | ----: | --------:
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 kapsayan:

explain analyze select sum(b) from foo where a='a';
| QUERY PLAN |
| : ------------------------------------------------- -------------------------------------------------- ------------------------------------------- |
| Toplam (maliyet = 3282.57..3282.58 satır = 1 genişlik = 8) (gerçek zaman = 45.942..45.942 satır = 1 döngü = 1) |
| -> Sadece Dizin foo üzerinde foo_btree_covering kullanarak tarama (maliyet = 0.43..3017.80 satır = 105907 genişlik = 4) (gerçek zaman = 0.038..27.286 satır = 100000 döngü = 1) |
| Endeks Koşulu: (a = 'a' :: metin) |
| Öbek Getiriyor: 0 |
| Planlama süresi: 0.099 ms |
| Yürütme süresi: 45.968 ms |

düz ağaç

drop index foo_btree_covering;
explain analyze select sum(b) from foo where a='a';
| QUERY PLAN |
| : ------------------------------------------------- -------------------------------------------------- ----------------------------- |
| Toplam (maliyet = 4064.57..4064.58 satır = 1 genişlik = 8) (gerçek zaman = 54.242..54.242 satır = 1 döngü = 1) |
| -> Foo üzerinde foo_btree kullanarak Dizin Tarama (maliyet = 0.43..3799.80 satır = 105907 genişlik = 4) (gerçek zaman = 0.037..33.084 satır = 100000 döngü = 1) |
| Endeks Koşulu: (a = 'a' :: metin) |
| Planlama süresi: 0.135 ms |
| Yürütme süresi: 54.280 ms |

BRIN pages_per_range = 4:

drop index foo_btree;
explain analyze select sum(b) from foo where a='a';
| QUERY PLAN |
| : ------------------------------------------------- -------------------------------------------------- ----------------------------- |
| Toplam (maliyet = 21595.38..21595.39 satır = 1 genişlik = 8) (gerçek zaman = 52.455..52.455 satır = 1 döngü = 1) |
| -> Bitmap Yığın Foo'da tarama (maliyet = 888.78..21330.61 satır = 105907 genişlik = 4) (gerçek zaman = 2.738..31.967 satır = 100000 döngü = 1) |
| Cond'u tekrar kontrol et: (a = 'a' :: metin) |
| Index Recheck tarafından kaldırılan satırlar: 96 |
| Yığın Blokları: kayıplı = 736 |
| -> Bitmap Dizini foo_brin_4 üzerinde tarama (maliyet = 0.00..862.30 satır = 105907 genişlik = 0) (gerçek zaman = 2.720..2.720 satır = 7360 döngü = 1) |
| Endeks Koşulu: (a = 'a' :: metin) |
| Planlama süresi: 0.101 ms |
| Yürütme süresi: 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 |
| : ------------------------------------------------- -------------------------------------------------- ----------------------------- |
| Toplam (maliyet = 21659.38..21659.39 satır = 1 genişlik = 8) (gerçek zaman = 53.971..53.971 satır = 1 döngü = 1) |
| -> Bitmap Yığın Foo'da tarama (maliyet = 952.78..21394.61 satır = 105907 genişlik = 4) (gerçek zaman = 5.286..33.492 satır = 100000 döngü = 1) |
| Cond'u tekrar kontrol et: (a = 'a' :: metin) |
| Index Recheck tarafından kaldırılan satırlar: 96 |
| Yığın Blokları: kayıplı = 736 |
| -> Bitmap Dizini foo_brin_2 üzerinde tarama (maliyet = 0.00..926.30 satır = 105907 genişlik = 0) (gerçek zaman = 5.275..5.275 satır = 7360 döngü = 1) |
| Endeks Koşulu: (a = 'a' :: metin) |
| Planlama süresi: 0.095 ms |
| Yürütme süresi: 54.016 ms |

CİN:

drop index foo_brin_2;
explain analyze select sum(b) from foo where a='a';
| QUERY PLAN |
| : ------------------------------------------------- -------------------------------------------------- ------------------------------ |
| Toplam (maliyet = 21687.38..21687.39 satır = 1 genişlik = 8) (gerçek zaman = 55.331..55.331 satır = 1 döngü = 1) |
| -> Bitmap Yığın Foo'da tarama (maliyet = 980.78..21422.61 satır = 105907 genişlik = 4) (gerçek zaman = 12.377..33.956 satır = 100000 döngü = 1) |
| Cond'u tekrar kontrol et: (a = 'a' :: metin) |
| Yığın Blokları: tam = 736 |
| -> Bitmap Dizini foo_gin üzerinde tarama (maliyet = 0.00..954.30 satır = 105907 genişlik = 0) (gerçek zaman = 12.271..12.271 satır = 100000 döngü = 1) |
| Endeks Koşulu: (a = 'a' :: metin) |
| Planlama süresi: 0.118 ms |
| Yürütme süresi: 55,366 ms |

dbfiddle burada


Öyleyse bir kaplama dizini disk alanı pahasına tabloyu tamamen okumayı atlar mı? İyi bir takas gibi görünüyor. Bence (yanılıyorsam doğru ben) 'bütün endeksi okumak', ben neler olduğunu düşünüyorum bütün BRIN endeksi tarayarak anlamına tarafından BRIN endeksi için aynı anlama düşünüyorum dbfiddle.uk/... hayır?
foo

@foo hakkında "(tablonun fiziksel olarak sıralandığını varsayamayacağı için de vardır)." Tablonun fiziksel sırası (küme ya da değil) önemsizdir. Dizin değerleri doğru sırada. Ancak Postgres B-ağacı dizinleri tüm değerleri (ve evet, birden çok kez) saklamak zorundadır. Bu şekilde tasarlanırlar. Her bir ayrı değeri sadece bir kez saklamak hoş bir özellik / gelişme olacaktır. Postgres geliştiricilerine önerebilirsin (ve hatta uygulamaya yardımcı olursun.) Jack yorum yapmalı, bence Oracle'ın b-ağaçlar uygulaması bunu yapıyor.
ypercubeᵀᴹ

1
@foo - tamamen haklısınız, bir BRIN indeksi taraması her zaman tüm indeksi tarar ( pgcon.org/2016/schedule/attachments/… , 2. son slayt) - bu kemandaki açıklama planında gösterilmez , bu mu?
Jack diyor ki topanswers.xyz

2
@ ypercubeᵀᴹ, Oracle'da her farklı öneki blok başına bir kez saklayan COMPRESS'i kullanabilirsiniz.
Jack diyor ki topanswers.xyz

@JackDouglas Bitmap Index Scan'brin indeksinin tamamını oku' anlamında okudum ama belki de yanlış okuma. Oracle COMPRESS, B-ağacının boyutunu azaltacağı için burada yararlı olacak bir şeye benziyor, ancak pg ile sıkıştım!
foo

6

Ayrıca btree ve yenilikçi kalite hangi en mantıklı seçenek, değer araştıran olabilecek diğer egzotik seçenekler görünüyor - onlar belki senin durumunda yararlı ya da değil:

  • INCLUDEdizinleri . Onlar olacak - umarım - Postgres bir sonraki ana sürüm (10) 'de, bir yerlerde Eylül 2017 An endeksi etrafında (a) INCLUDE (b)bir endeks olarak aynı yapıya sahiptir (a)fakat, bütün değerleri yaprak sayfalarında içerir b(ama sırasız). Bu, örneğin onu kullanamayacağınız anlamına gelir SELECT * FROM t WHERE a = 'a' AND b = 2 ;. Endeks kullanılan ancak bir süre olabilir (a,b)endeksi tek aramaya ile eşleşen satırları bulacaktır endeksi (muhtemelen 100K senin durumunda olduğu gibi) geçmek zorunda olacak içerdiğini maçı değerleri a = 'a've kontrol bdeğerleri.
    Öte yandan, dizin dizinden biraz daha az geniştir (a,b)ve bsorgunuzun hesaplanması için sıraya ihtiyacınız yoktur SUM(b). Örneğin,(a) INCLUDE (b,c,d) 3 sütunun hepsinde toplanan sizinkine benzer sorgular için kullanılabilir.

  • Filtrelenmiş (kısmi) dizinler . İlk başta biraz çılgınca gelebilecek * bir öneri :

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

    Her adeğer için bir dizin . Sizin durumunuzda yaklaşık 100 bin dizin. Bu kulağa çok fazla gelse de, her dizinin hem boyut (satır sayısı) hem de genişlik olarak (yalnızca bdeğerleri depolayacağı için ) çok küçük olacağını düşünün . Bununla birlikte, diğer tüm yönlerde, (100K indeksleri birlikte) (a,b), bir (b)dizin alanını kullanırken b-ağacı dizini olarak işlev görür .
    Dezavantajı, atabloya her yeni değer eklendiğinde bunları kendiniz oluşturmanız ve sürdürmeniz gerekmesidir. Tablonuz oldukça istikrarlı olduğundan, çok fazla (veya herhangi bir eklenti / güncelleme olmadan), bu bir sorun gibi görünmüyor.

  • Özet tabloları. Tablo oldukça kararlı olduğu için, her zaman ihtiyacınız olan en yaygın toplamalarla ( sum(b), sum(c), sum(d), avg(b), count(distinct b)vb.) Bir özet tablo oluşturabilir ve doldurabilirsiniz . Küçük olacaktır (yalnızca 100K satır) ve yalnızca bir kez doldurulmalı ve yalnızca ana tabloya satırlar eklendiğinde / güncellendiğinde / silinirken güncellenmelidir.

*: üretim sistemlerinde 10 milyon endeks çalıştıran bu şirketten kopyalanan fikir: Öbek: Üretimde 10 Milyon Postgresql Endeks Çalıştırma (ve sayma) .


1 ilginç ama işaret ettiğiniz gibi pg 10 henüz çıkmadı. 2 yapar ses deli (ya da 'ortak akıl' karşı en azından), seni işaret olarak beri okuma olacak bu benim neredeyse hiç yazıyor iş akışıyla işe yarayabilir. 3. Benim için iş, kullandığım olmaz SUM(örnek olarak, ama pratikte benim sorguları önceden hesaplanabilir edilemez onlar konum gibi daha fazla select ... from t where a = '?' and ??wjere ??diğer bazı kullanıcı tanımlı koşulu olacaktır.
foo

1
Peki, ne ??olduğunu bilmiyorsak yardımcı olamayız ;)
ypercubeᵀᴹ

Filtrelenmiş dizinlerden bahsediyorsunuz. Tabloyu bölümlere ayırmaya ne dersiniz?
jpmc26

@ jpmc26 komik, cevapta filtrelenmiş dizin önerisinin bir anlamda bir bölümleme biçimini eklemeyi düşünüyordum. Bölümleme de burada yardımcı olabilir, ancak emin değilim. Çok sayıda küçük dizin / tablo ile sonuçlanır.
ypercubeᵀᴹ

2
Kısmi örtme btree dizinlerinin burada performansın kralı olmasını bekliyorum, çünkü veriler neredeyse hiç güncellenmiyor. Bu 100 bin dizin demek olsa bile. Toplam dizin boyutu en küçüktür (bir BRIN dizini hariç, ancak Postgres'in yığın sayfalarını ek olarak okuması ve filtrelemesi gerekir). Dizin oluşturma, dinamik SQL ile otomatikleştirilebilir. Bu ilgili cevaptaki örnek DOifade .
Erwin Brandstetter
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.