Làm việc với các chỉ mục trong PostgreSQL


73

Tôi có một vài câu hỏi liên quan đến hoạt động của các chỉ mục trong PostgreSQL. Tôi có một Friendsbảng với chỉ số sau:

   Friends ( user_id1 ,user_id2) 

user_id1user_id2là khóa ngoại để userbảng

  1. Là những tương đương? Nếu không thì tại sao?

    Index(user_id1,user_id2) and Index(user_id2,user_id1)
  2. Nếu tôi tạo Khóa chính (user_id1, user_id2), nó có tự động tạo chỉ mục cho nó không và

    Nếu các chỉ mục trong câu hỏi đầu tiên không tương đương, thì chỉ mục nào được tạo trên lệnh khóa chính ở trên?

Câu trả lời:


77

Dưới đây là kết quả của việc truy vấn một bảng trên cột thứ hai của chỉ mục nhiều màu .
Các hiệu ứng dễ dàng để tái sản xuất cho bất cứ ai. Chỉ cần thử nó ở nhà.

Tôi đã thử nghiệm với PostgreSQL 9.0.5 trên Debian bằng cách sử dụng bảng có kích thước trung bình của cơ sở dữ liệu thực tế với 23322 hàng. Nó thực hiện mối quan hệ n: m giữa các bảng adr(địa chỉ) và att(thuộc tính), nhưng điều đó không liên quan ở đây. Lược đồ đơn giản hóa:

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)
);

Các UNIQUEràng buộc có hiệu quả thực hiện một chỉ số duy nhất. Tôi đã lặp lại thử nghiệm với một chỉ số đơn giản để chắc chắn và có kết quả giống hệt như mong đợi.

CREATE INDEX adratt_idx ON adratt(adr_id, att_id)

Bảng được nhóm trên adratt_unichỉ mục và trước khi thử nghiệm tôi đã chạy:

CLUSTER adratt;
ANALYZE adratt;

Quét tuần tự cho các truy vấn trên (adr_id, att_id)nhanh nhất có thể. Chỉ mục nhiều màu vẫn sẽ được sử dụng cho một điều kiện truy vấn trên cột chỉ mục thứ hai.

Tôi đã chạy các truy vấn một vài lần để điền vào bộ đệm và chọn tốt nhất trong số mười lần chạy để có kết quả tương đương.

1. Truy vấn bằng cả hai cột

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)

Đầu ra của 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. Truy vấn bằng cột đầu tiên

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)

Đầu ra của 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. Truy vấn bằng cột thứ hai

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)

Đầu ra của 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. Vô hiệu hóa indexscan & bitmapscan

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

Đầu ra của ANALYZE GIẢI THÍCH:

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

Đầu ra của 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

Phần kết luận

Như mong đợi, chỉ mục nhiều cột được sử dụng cho một truy vấn trên cột thứ hai.
Như mong đợi, nó ít hiệu quả hơn, nhưng truy vấn vẫn nhanh hơn gấp 3 lần so với không có chỉ mục.
Sau khi vô hiệu hóa quét chỉ mục, trình hoạch định truy vấn chọn quét heap bitmap, thực hiện gần như nhanh. Chỉ sau khi vô hiệu hóa điều đó, nó mới quay trở lại quét liên tiếp.


phân cụm sẽ tạo ra sự khác biệt nếu số lượng các trận đấu trong chỉ mục đủ cao (xem ở đây để chứng minh - lưu ý các lần chạy kép để lấy dữ liệu được lưu trong bộ nhớ cache)
Jack Douglas

1
@JackDoumund: Tôi đã suy nghĩ thêm về điều này. Phân cụm có thể giúp chung, bởi vì nó cũng có hiệu quả a vacuum fullvà a reindex. Ngoài ra, nó sẽ giúp quét chỉ mục trên các cột đầu tiên hoặc cả hai cột hàng đầu rất nhiều , nhưng làm tổn thương các truy vấn trên cột thứ hai. Trong một bảng mới được phân cụm, các hàng có cùng giá trị trong cột thứ hai được trải ra, do đó tối đa các khối sẽ phải được đọc.
Erwin Brandstetter

28

lại 1) Có và không.

Đối với truy vấn sử dụng cả hai cột, ví dụ: where (user_id1, user_id2) = (1,2)không tạo ra chỉ mục nào.

Đối với một truy vấn chỉ có một điều kiện trên một trong các cột, ví dụ như where user_id1 = 1nó có vấn đề vì thông thường chỉ có thể sử dụng các cột "hàng đầu" để so sánh bởi trình tối ưu hóa. Vì vậy, where user_id1 = 1sẽ có thể sử dụng chỉ mục (user_id1, user_id2) nhưng nó sẽ không thể lập chỉ mục (user_id2, user_id1) cho tất cả các trường hợp.

Sau khi giải quyết vấn đề này (sau khi Erwin vui lòng chỉ cho chúng tôi một thiết lập nơi nó hoạt động), có vẻ như điều này phụ thuộc nhiều vào phân phối dữ liệu của cột thứ hai mặc dù tôi chưa tìm ra tình huống nào cho phép trình tối ưu hóa sử dụng các cột theo dõi cho một điều kiện WHERE.

Oracle 11 cũng có thể (đôi khi) sử dụng các cột không ở đầu định nghĩa chỉ mục.

lại 2) Có nó sẽ tạo ra một chỉ mục

Trích dẫn từ hướng dẫn

Thêm khóa chính sẽ tự động tạo chỉ mục btree duy nhất trên cột hoặc nhóm cột được sử dụng trong khóa chính.

re 2a) Primary Key (user_id1,user_id2)sẽ tạo một chỉ mục trên (user_id1, user_id2) (mà bạn có thể tự mình tìm ra rất dễ dàng bằng cách chỉ cần tạo một khóa chính như vậy)

Tôi đặc biệt khuyên bạn nên đọc chương về các chỉ mục trong hướng dẫn , về cơ bản nó sẽ trả lời tất cả các câu hỏi ở trên.

Ngoài ra, chỉ mục nào để tạo? bởi depesz thực hiện tốt công việc giải thích thứ tự trên các cột chỉ mục và các chủ đề liên quan đến chỉ mục khác.


11

Quảng cáo 1)
Có những hạn chế trong PostgreSQL như @a_horse_with_no_name mô tả . Cho đến khi chỉ mục đa phiên bản 8.0 chỉ có thể được sử dụng cho các truy vấn trên (các) cột hàng đầu. Điều này đã được cải thiện trong phiên bản 8.1. Các nhãn hiệu hiện tại cho Postgres 10 (cập nhật) giải thích:

Chỉ mục cây B nhiều màu có thể được sử dụng với các điều kiện truy vấn liên quan đến bất kỳ tập hợp con nào của các cột của chỉ mục, nhưng chỉ mục này hiệu quả nhất khi có các ràng buộc trên các cột hàng đầu (ngoài cùng bên trái). Quy tắc chính xác là các ràng buộc bình đẳng trên các cột hàng đầu, cộng với bất kỳ ràng buộc bất bình đẳng nào trên cột đầu tiên không có ràng buộc đẳng thức, sẽ được sử dụng để giới hạn phần của chỉ mục được quét. Các ràng buộc trên các cột ở bên phải của các cột này được kiểm tra trong chỉ mục, vì vậy chúng lưu các lượt truy cập vào bảng thích hợp, nhưng chúng không làm giảm phần của chỉ mục phải được quét. Ví dụ: được cung cấp một chỉ mục trên (a, b, c)và một điều kiện truy vấn WHERE a = 5 AND b >= 42 AND c < 77, chỉ mục sẽ phải được quét từ mục nhập đầu tiên với a= 5 vàb= 42 trở lên cho đến mục cuối cùng với a= 5. Các mục chỉ mục có c> = 77 sẽ bị bỏ qua, nhưng chúng vẫn phải được quét qua. Về nguyên tắc, chỉ mục này có thể được sử dụng cho các truy vấn có ràng buộc bvà / hoặc ckhông có ràng buộc nào a- nhưng toàn bộ chỉ mục sẽ phải được quét, do đó, trong hầu hết các trường hợp, trình hoạch định sẽ thích quét bảng tuần tự hơn bằng cách sử dụng chỉ mục.

Nhấn mạnh mỏ. Tôi có thể xác nhận điều đó từ kinh nghiệm.
Cũng xem trường hợp thử nghiệm thêm câu trả lời sau của tôi ở đây .


11

Đây là câu trả lời cho câu trả lời của Jack , một bình luận sẽ không làm.

Không có chỉ mục bao trùm trong PostgreSQL trước phiên bản 9.2. Do mô hình MVCC, mọi bộ dữ liệu trong tập kết quả phải được truy cập để kiểm tra mức độ hiển thị. Bạn có thể đang nghĩ về Oracle.

Các nhà phát triển PostgreSQL nói về "quét chỉ mục" . Trên thực tế, tính năng này đã được phát hành với Postgres 9.2. Đọc thông điệp cam kết .
Depesz đã viết một bài viết trên Blog rất nhiều thông tin .

Các chỉ số bao phủ thực sự (cập nhật) được giới thiệu với INCLUDEmệnh đề với Postgres 11. Liên quan:

Đây là một chút tắt, quá:

nó phụ thuộc vào thực tế là 'quét toàn bộ' chỉ mục thường nhanh hơn 'quét toàn bộ' của bảng được lập chỉ mục do các cột bổ sung trong bảng không xuất hiện trong chỉ mục.

Như đã báo cáo trong các bình luận về câu trả lời khác của tôi, tôi cũng đã chạy thử nghiệm với một bảng gồm hai số nguyên và không có gì khác. Chỉ mục giữ các cột giống như bảng. Kích thước của một chỉ số btree là khoảng 2/3 so với bảng. Không đủ để giải thích sự tăng tốc của yếu tố 3. Tôi đã chạy thử nghiệm nhiều hơn, dựa trên thiết lập của bạn, được đơn giản hóa thành hai cột và với 100000 hàng. Trên bản cài đặt PostgreSQL 9.0 của tôi, kết quả rất phù hợp.

Nếu bảng có các cột bổ sung, việc tăng tốc với chỉ số trở nên đáng kể hơn, nhưng đó chắc chắn không phải là yếu tố duy nhất ở đây .

Để tổng hợp các điểm chính:

  • Các chỉ mục nhiều cột có thể được sử dụng với các truy vấn trên các cột không dẫn đầu, nhưng việc tăng tốc chỉ xoay quanh yếu tố 3 cho các tiêu chí chọn lọc (phần trăm nhỏ các hàng trong kết quả). Cao hơn cho các bộ dữ liệu lớn hơn, thấp hơn cho các phần lớn hơn của bảng trong tập kết quả.

  • Tạo một chỉ mục bổ sung trên các cột đó nếu hiệu suất là quan trọng.

  • Nếu tất cả các cột liên quan được bao gồm trong một chỉ mục (bao gồm chỉ mục) và tất cả các hàng liên quan (trên mỗi khối) hiển thị cho tất cả các giao dịch, bạn có thể nhận được "quét chỉ mục" trong pg 9.2 trở lên.


7
  1. Là những tương đương? Nếu không thì tại sao?

    Chỉ mục (user_id1, user_id2) và Index (user_id2, user_id1)

Đây không phải là tương đương và chỉ số nói chung (bar, baz) sẽ không hiệu quả cho các truy vấn của biểu mẫu select * from foo where baz=?

Erwin đã chứng minh rằng các chỉ mục như vậy thực sự có thể tăng tốc truy vấn nhưng hiệu ứng này bị hạn chế và không theo thứ tự như bạn thường mong muốn một chỉ mục cải thiện việc tra cứu - nó phụ thuộc vào thực tế là 'quét toàn bộ' chỉ mục nhanh hơn 'quét toàn bộ' bảng được lập chỉ mục do các cột bổ sung trong bảng không xuất hiện trong chỉ mục.

Tóm tắt: các chỉ mục có thể giúp truy vấn ngay cả trên các cột không dẫn đầu, nhưng theo một trong hai cách thứ yếu và tương đối nhỏ và không theo cách kịch tính mà bạn thường mong đợi một chỉ mục sẽ giúp do cấu trúc btree của nó

nb hai cách mà chỉ mục có thể giúp là nếu quét toàn bộ chỉ mục rẻ hơn đáng kể so với quét toàn bộ bảng và: 1. việc tra cứu bảng là rẻ (vì có một vài trong số chúng hoặc chúng được phân cụm), hoặc 2. chỉ số được bao phủ để không có tra cứu bảng nào cả , xin vui lòng xem các bình luận của Erwins tại đây

thử nghiệm:

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;

truy vấn 1 (không có chỉ mục, nhấn 74 bộ đệm ):

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

truy vấn 2 (có chỉ mục - trình tối ưu hóa bỏ qua chỉ mục - nhấn lại 74 bộ đệm ):

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

truy vấn 2 (có chỉ mục - và chúng tôi lừa trình tối ưu hóa để sử dụng nó):

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

Vì vậy, truy cập thông qua chỉ mục nhanh gấp đôi trong trường hợp này, đạt 30 bộ đệm - về mặt lập chỉ mục là 'nhanh hơn một chút'!, Và YMMV tùy thuộc vào kích thước tương đối của bảng và chỉ mục, cùng với số lượng hàng được lọc và đặc điểm phân cụm của dữ liệu trong bảng

Ngược lại, các truy vấn trên cột hàng đầu sử dụng cấu trúc btree của chỉ mục - trong trường hợp này nhấn 2 bộ đệm :

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
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.