Chỉ số nào để sử dụng với nhiều giá trị trùng lặp?


14

Hãy đưa ra một vài giả định:

Tôi có bảng trông như thế này:

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

Sự thật về bộ của tôi:

  • Kích thước của toàn bộ bảng là ~ 10 10 hàng.

  • Tôi có ~ 100k hàng với giá trị atrong cột a, tương tự cho các giá trị khác (ví dụ c).

  • Điều đó có nghĩa là ~ 100k giá trị riêng biệt trong cột 'a'.

  • Hầu hết các câu hỏi của tôi sẽ đọc tất cả hoặc hầu hết các giá trị cho một giá trị được đưa ra trong một, ví dụ select sum(b) from t where a = 'c'.

  • Bảng được viết theo cách các giá trị liên tiếp gần nhau về mặt vật lý (có thể được viết theo thứ tự hoặc chúng tôi giả sử CLUSTERđã được sử dụng trên bảng và cột đó a).

  • Bảng hiếm khi được cập nhật, chúng tôi chỉ quan tâm đến tốc độ đọc.

  • Bảng tương đối hẹp (giả sử ~ 25 byte mỗi bộ, + 23 byte trên đầu).

Bây giờ câu hỏi là, tôi nên sử dụng loại chỉ mục nào? Hiểu biết của tôi là:

  • BTree Vấn đề của tôi ở đây là chỉ số BTree sẽ rất lớn vì theo như tôi biết thì nó sẽ lưu trữ các giá trị trùng lặp (vì nó không thể giả sử bảng được sắp xếp vật lý). Nếu BTree là rất lớn, cuối cùng tôi phải đọc cả chỉ mục và các phần của bảng mà chỉ mục trỏ tới. (Chúng ta có thể sử dụng fillfactor = 100để giảm kích thước của chỉ mục một chút.)

  • BRIN Sự hiểu biết của tôi là tôi có thể có một chỉ mục nhỏ ở đây với chi phí đọc các trang vô dụng. Sử dụng một pages_per_rangephương tiện nhỏ có nghĩa là chỉ mục lớn hơn (đó là vấn đề với BRIN vì tôi cần đọc toàn bộ chỉ mục), có một pages_per_rangephương tiện lớn là tôi sẽ đọc rất nhiều trang vô dụng. Có một công thức kỳ diệu để tìm ra một giá trị tốt pages_per_rangecó tính đến những sự đánh đổi đó không?

  • GIN / GiST Không chắc chắn những thứ đó có liên quan ở đây vì chúng chủ yếu được sử dụng cho tìm kiếm toàn văn bản, nhưng tôi cũng nghe nói rằng chúng rất giỏi trong việc xử lý các khóa trùng lặp. Một GINhoặc GiSTchỉ số sẽ giúp ở đây?

Một câu hỏi khác là, Postgres sẽ sử dụng thực tế là một bảng có CLUSTERed (giả sử không có cập nhật) trong trình lập kế hoạch truy vấn (ví dụ: bằng cách tìm kiếm nhị phân cho các trang bắt đầu / kết thúc có liên quan)? Hơi liên quan, tôi có thể lưu trữ tất cả các cột của mình trong một BTree và thả bảng hoàn toàn (hoặc đạt được một cái gì đó tương đương, tôi tin rằng đó là các chỉ số được nhóm trong máy chủ SQL)? Có một số chỉ số BTree / BRIN lai sẽ giúp ở đây?

Tôi muốn tránh sử dụng mảng để lưu trữ các giá trị của mình vì truy vấn của tôi sẽ kết thúc ít đọc hơn theo cách đó (tôi hiểu điều này sẽ giảm chi phí 23 byte cho mỗi bộ dữ liệu bằng cách giảm số lượng bộ dữ liệu).


"chủ yếu được sử dụng cho tìm kiếm toàn văn bản" GiST được PostGIS sử dụng khá rộng rãi.
jpmc26

Câu trả lời:


15

BTree

Vấn đề của tôi ở đây là chỉ số BTree sẽ rất lớn kể từ khi nó sẽ lưu trữ các giá trị trùng lặp (nó cũng vậy, vì nó không thể giả định bảng được sắp xếp vật lý). Nếu BTree rất lớn, cuối cùng tôi cũng phải đọc cả chỉ mục và các phần của bảng mà chỉ mục cũng chỉ ...

Không nhất thiết - Có chỉ số btree là 'bao phủ' sẽ là thời gian đọc nhanh nhất và nếu đó là tất cả những gì bạn muốn (nghĩa là nếu bạn có đủ khả năng lưu trữ thêm), thì đó là cách tốt nhất của bạn.

BRIN

Sự hiểu biết của tôi là tôi có thể có một chỉ số nhỏ ở đây với chi phí đọc các trang vô dụng. Sử dụng một pages_per_rangephương tiện nhỏ có nghĩa là chỉ mục lớn hơn (đó là vấn đề với BRIN vì tôi cần đọc toàn bộ chỉ mục), có một pages_per_rangephương tiện lớn là tôi sẽ đọc rất nhiều trang vô dụng.

Nếu bạn không đủ khả năng lưu trữ chi phí lưu trữ của chỉ số btree, BRIN là lý tưởng cho bạn, vì bạn đã phân cụm sẵn (điều này rất quan trọng đối với BRIN là hữu ích). Các chỉ mục BRIN rất nhỏ , vì vậy tất cả các trang có khả năng nằm trong bộ nhớ nếu bạn chọn một giá trị phù hợp pages_per_range.

Có một công thức kỳ diệu để tìm ra một giá trị tốt của Pages_per_range có tính đến những sự đánh đổi đó không?

Không có công thức ma thuật, nhưng bắt đầu với pages_per_range phần nào nhỏ hơn kích thước trung bình (tính theo trang) chiếm agiá trị trung bình . Có lẽ bạn đang cố gắng giảm thiểu: (số trang BRIN được quét) + (số trang heap được quét) cho một truy vấn thông thường. Tìm kiếm Heap Blocks: lossy=ntrong kế hoạch thực hiện pages_per_range=1và so sánh với các giá trị khác cho pages_per_range- tức là xem có bao nhiêu khối heap không cần thiết đang được quét.

GIN / GiST

Không chắc chắn những thứ đó có liên quan ở đây vì chúng chủ yếu được sử dụng cho tìm kiếm toàn văn bản, nhưng tôi cũng nghe rằng chúng rất tốt trong việc xử lý các khóa trùng lặp. Một GIN/ GiSTchỉ số sẽ giúp ở đây?

GIN có thể đáng để xem xét, nhưng có lẽ không phải là GiST - tuy nhiên nếu phân cụm tự nhiên thực sự tốt, thì BRIN có thể sẽ là một lựa chọn tốt hơn.

Dưới đây là so sánh mẫu giữa các loại chỉ mục khác nhau cho dữ liệu giả giống như của bạn:

bảng và chỉ mục:

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;

kích thước quan hệ:

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;
tên | kích thước | trang | hàng / trang
: ----------------- | : ------ | ----: | --------:
foo | 149 MB | 19118 | 135
foo_btree_covering | 56 MB | 7132 | 364
foo_btree | 56 MB | 7132 | 364
foo_gin | 2928 kB | 365 | 7103
foo_brin_2 | 264 kB | 33 | 78787
foo_brin_4 | 136 kB | 17 | 152941

bao gồm btree:

explain analyze select sum(b) from foo where a='a';
| KẾ HOẠCH NHANH |
| : ------------------------------------------------- -------------------------------------------------- ------------------------------------------- |
| Tổng hợp (chi phí = 3282,57..3282,58 hàng = 1 chiều rộng = 8) (thời gian thực tế = 45.942..45.942 hàng = 1 vòng = 1) |
| -> Chỉ quét chỉ mục bằng cách sử dụng foo_btree_covering trên foo (cost = 0.43..3017.80 hàng = 105907 width = 4) (thời gian thực tế = 0.038..27.286 hàng = 100000 vòng = 1) |
| Chỉ số Cond: (a = 'a' :: text) |
| Tải Heap: 0 |
| Thời gian lập kế hoạch: 0,099 ms |
| Thời gian thực hiện: 45.968 ms |

đồng bằng btree:

drop index foo_btree_covering;
explain analyze select sum(b) from foo where a='a';
| KẾ HOẠCH NHANH |
| : ------------------------------------------------- -------------------------------------------------- ----------------------------- |
| Tổng hợp (chi phí = 4064,57..4064,58 hàng = 1 chiều rộng = 8) (thời gian thực tế = 54.242..54.242 hàng = 1 vòng = 1) |
| -> Quét chỉ mục bằng cách sử dụng foo_btree trên foo (chi phí = 0,43..3799,80 hàng = 105907 chiều rộng = 4) (thời gian thực tế = 0,037..33.084 hàng = 100000 vòng = 1) |
| Chỉ số Cond: (a = 'a' :: text) |
| Thời gian lập kế hoạch: 0.135 ms |
| Thời gian thực hiện: 54.280 ms |

BRIN trang_per_range = 4:

drop index foo_btree;
explain analyze select sum(b) from foo where a='a';
| KẾ HOẠCH NHANH |
| : ------------------------------------------------- -------------------------------------------------- ----------------------------- |
| Tổng hợp (chi phí = 21595,38..21595,39 hàng = 1 chiều rộng = 8) (thời gian thực tế = 52.455..52.455 hàng = 1 vòng = 1) |
| -> Bitmap Heap Scan trên foo (chi phí = 888,78..21330.61 hàng = 105907 chiều rộng = 4) (thời gian thực tế = 2.738..31.967 hàng = 100000 vòng = 1) |
| Kiểm tra lại Cond: (a = 'a' :: văn bản) |
| Các hàng bị xóa bởi Index Recheck: 96 |
| Khối heap: lossy = 736 |
| -> Quét chỉ mục Bitmap trên foo_brin_4 (chi phí = 0,00..862.30 hàng = 105907 chiều rộng = 0) (thời gian thực tế = 2.720..2.720 hàng = 7360 vòng = 1) |
| Chỉ số Cond: (a = 'a' :: text) |
| Thời gian lập kế hoạch: 0.101 ms |
| Thời gian thực hiện: 52.501 ms |

BRIN trang_per_range = 2:

drop index foo_brin_4;
explain analyze select sum(b) from foo where a='a';
| KẾ HOẠCH NHANH |
| : ------------------------------------------------- -------------------------------------------------- ----------------------------- |
| Tổng hợp (chi phí = 21659,38..21659,39 hàng = 1 chiều rộng = 8) (thời gian thực tế = 53.971..53.971 hàng = 1 vòng = 1) |
| -> Bitmap Heap Scan trên foo (chi phí = 952,78..21394.61 hàng = 105907 chiều rộng = 4) (thời gian thực tế = 5.286..33.492 hàng = 100000 vòng = 1) |
| Kiểm tra lại Cond: (a = 'a' :: văn bản) |
| Các hàng bị xóa bởi Index Recheck: 96 |
| Khối heap: lossy = 736 |
| -> Quét chỉ mục Bitmap trên foo_brin_2 (chi phí = 0,00..926.30 hàng = 105907 chiều rộng = 0) (thời gian thực tế = 5.275..5.275 hàng = 7360 vòng = 1) |
| Chỉ số Cond: (a = 'a' :: text) |
| Thời gian lập kế hoạch: 0,095 ms |
| Thời gian thực hiện: 54.016 ms |

GIN:

drop index foo_brin_2;
explain analyze select sum(b) from foo where a='a';
| KẾ HOẠCH NHANH |
| : ------------------------------------------------- -------------------------------------------------- ------------------------------ |
| Tổng hợp (chi phí = 21687,38..21687,39 hàng = 1 chiều rộng = 8) (thời gian thực tế = 55.331..55.331 hàng = 1 vòng = 1) |
| -> Quét bitmap trên foo (chi phí = 980,78,21422,61 hàng = 105907 chiều rộng = 4) (thời gian thực tế = 12.377..33.956 hàng = 100000 vòng = 1) |
| Kiểm tra lại Cond: (a = 'a' :: văn bản) |
| Khối heap: chính xác = 736 |
| -> Quét chỉ mục Bitmap trên foo_gin (chi phí = 0,00..954.30 hàng = 105907 chiều rộng = 0) (thời gian thực tế = 12.271..12.271 hàng = 100000 vòng = 1) |
| Chỉ số Cond: (a = 'a' :: text) |
| Thời gian lập kế hoạch: 0.118 ms |
| Thời gian thực hiện: 55.366 ms |

dbfiddle ở đây


Vì vậy, một chỉ số bao gồm sẽ bỏ qua việc đọc bảng hoàn toàn với chi phí của không gian đĩa? Có vẻ như một sự đánh đổi tốt. Tôi nghĩ rằng chúng tôi có ý nghĩa tương tự đối với chỉ số BRIN bằng cách 'đọc toàn bộ chỉ mục' (sửa tôi nếu tôi sai), ý tôi là quét toàn bộ chỉ số BRIN mà tôi nghĩ là những gì đang xảy ra trong dbfiddle.uk/ , không?
foo

@foo về "(nó cũng vậy, vì nó không thể giả sử bảng được sắp xếp vật lý)." Thứ tự vật lý (cụm hoặc không) của bảng là không liên quan. Chỉ số có các giá trị theo đúng thứ tự. Nhưng các chỉ mục cây B của Postgres phải lưu trữ tất cả các giá trị (và có, nhiều lần). Đó là cách chúng được thiết kế. Lưu trữ mỗi giá trị riêng biệt chỉ một lần sẽ là một tính năng / cải tiến tốt đẹp. Bạn có thể đề xuất nó cho các nhà phát triển Postgres (và thậm chí giúp triển khai nó.) Jack nên bình luận, tôi nghĩ rằng việc triển khai b-cây của Oracle thực hiện điều đó.
ypercubeᵀᴹ

1
@foo - bạn hoàn toàn chính xác, việc quét chỉ mục BRIN luôn quét toàn bộ chỉ mục ( pgcon.org/2016/schedule/attachments/ Kẻ , slide thứ 2) - mặc dù điều đó không được hiển thị trong kế hoạch giải thích trong fiddle , Là nó?
Jack nói hãy thử topanswers.xyz

2
@ ypercubeᵀᴹ bạn có thể sử dụng COMPRESS trên Oracle để lưu trữ từng tiền tố riêng biệt một lần cho mỗi khối.
Jack nói hãy thử topanswers.xyz

@JackDoumund Tôi đọc Bitmap Index Scancó nghĩa là 'đọc toàn bộ chỉ số brin' nhưng có lẽ đó là đọc sai. Oracle COMPRESStrông giống như một cái gì đó sẽ hữu ích ở đây vì nó sẽ làm giảm kích thước của cây B, nhưng tôi bị mắc kẹt với pg!
foo

6

Bên cạnh btreebrin có vẻ là những lựa chọn hợp lý nhất, một số tùy chọn khác, kỳ lạ có thể đáng để nghiên cứu - chúng có thể hữu ích hoặc không trong trường hợp của bạn:

  • INCLUDEchỉ số . Chúng sẽ - hy vọng - trong phiên bản chính tiếp theo (10) của Postgres, đâu đó vào khoảng tháng 9 năm 2017. Một chỉ mục trên (a) INCLUDE (b)có cấu trúc giống như một chỉ mục trên (a)nhưng bao gồm trong các trang lá, tất cả các giá trị của b(nhưng không được sắp xếp). Điều đó có nghĩa là bạn không thể sử dụng nó chẳng hạn SELECT * FROM t WHERE a = 'a' AND b = 2 ;. Chỉ mục có thể được sử dụng nhưng trong khi một (a,b)chỉ mục sẽ tìm thấy các hàng khớp với một tìm kiếm duy nhất, chỉ mục bao gồm sẽ phải đi qua các giá trị (có thể là 100K như trong trường hợp của bạn) khớp a = 'a'và kiểm tra các bgiá trị.
    Mặt khác, chỉ mục hơi rộng hơn chỉ mục một chút (a,b)và bạn không cần thứ tự bcho truy vấn của mình để tính toánSUM(b) . Bạn cũng có thể có ví dụ(a) INCLUDE (b,c,d) có thể được sử dụng cho các truy vấn tương tự như các truy vấn tổng hợp trên cả 3 cột.

  • Các chỉ mục được lọc (một phần) . Một đề nghị nghe có vẻ hơi điên rồ * lúc đầu:

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

    Một chỉ số cho mỗi agiá trị. Trong trường hợp của bạn khoảng 100K chỉ mục. Mặc dù điều này nghe có vẻ nhiều, hãy xem xét rằng mỗi chỉ mục sẽ rất nhỏ, cả về kích thước (số lượng hàng) và chiều rộng (vì nó sẽ chỉ lưu trữ bcác giá trị). Tuy nhiên, trong tất cả các khía cạnh khác, nó (các chỉ mục 100K cùng nhau) sẽ hoạt động như một chỉ mục cây b (a,b)trong khi sử dụng không gian của một (b)chỉ mục.
    Nhược điểm là bạn sẽ phải tự tạo và bảo trì chúng, mỗi lần một giá trị mới ađược thêm vào bảng. Vì bảng của bạn khá ổn định, không có nhiều (hoặc bất kỳ) chèn / cập nhật nào, điều đó dường như không phải là vấn đề.

  • Bảng tóm tắt. Vì bảng khá ổn định, bạn luôn có thể tạo và điền vào bảng tóm tắt với các tổng hợp phổ biến nhất bạn sẽ cần ( sum(b), sum(c), sum(d), avg(b), count(distinct b), v.v.). Nó sẽ nhỏ (chỉ 100K hàng) và sẽ chỉ phải được điền một lần và chỉ được cập nhật khi các hàng được chèn / cập nhật / xóa trên bảng chính.

*: ý tưởng được sao chép từ công ty này chạy 10 triệu chỉ mục trong hệ thống sản xuất của họ: Heap: Chạy 10 triệu chỉ số Postgresql trong sản xuất (và sẽ tiếp tục tăng) .


1 là thú vị nhưng như bạn chỉ ra pg 10 chưa ra. 2 nghe vẻ điên rồ (hoặc ít nhất là chống lại 'trí tuệ thông thường'), tôi sẽ đọc từ khi bạn chỉ ra rằng nó có thể hoạt động với quy trình viết gần như không viết của tôi. 3. sẽ không làm việc cho tôi, tôi đã sử dụng SUMlàm ví dụ, nhưng trong thực tế truy vấn của tôi không thể precomputed (họ có nhiều như select ... from t where a = '?' and ??wjere ??sẽ có một số điều kiện người dùng định nghĩa khác.
foo

1
Chà, chúng tôi không thể giúp nếu chúng tôi không biết cái gì ??là;)
ypercubeᵀᴹ

Bạn đề cập đến các chỉ mục được lọc. Còn phân vùng bảng thì sao?
jpmc26

@ jpmc26 buồn cười, tôi đã nghĩ đến việc thêm vào câu trả lời rằng gợi ý về các chỉ mục được lọc theo nghĩa là một dạng phân vùng. Phân vùng cũng có thể hữu ích ở đây nhưng tôi không chắc chắn. Nó sẽ dẫn đến rất nhiều chỉ mục / bảng nhỏ.
ypercubeᵀᴹ

2
Tôi hy vọng một phần bao gồm các chỉ số btree sẽ là vua hiệu suất ở đây, vì dữ liệu gần như không bao giờ được cập nhật. Ngay cả khi điều đó có nghĩa là chỉ số 100k. Tổng kích thước chỉ mục là nhỏ nhất (ngoại trừ chỉ mục BRIN, nhưng ở đó Postgres phải đọc và lọc các trang heap bổ sung). Tạo chỉ mục có thể được tự động hóa với SQL động. Ví dụ DOtuyên bố trong câu trả lời liên quan này .
Erwin Brandstetter
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.