Chọn top 10 từ trường được lập chỉ mục của một bảng lớn mất quá nhiều thời gian


7

Tôi có một bảng với các bản ghi 165M như thế này:

Performance
   id        integer
   installs  integer
   hour      timestamp without time zone

Tôi cũng có một chỉ số về giờ:

CREATE INDEX hour_idx
  ON performance
  USING btree
  (hour DESC NULLS LAST);

Tuy nhiên, chọn 10 bản ghi hàng đầu được sắp xếp theo giờ mất 6 phút!

EXPLAIN ANALYZE  select hour from performance order by hour desc limit 10

Trả về

Limit  (cost=7952135.23..7952135.25 rows=10 width=8) (actual time=376313.958..376313.964 rows=10 loops=1)
  ->  Sort  (cost=7952135.23..8368461.00 rows=166530310 width=8) (actual time=376313.957..376313.960 rows=10 loops=1)
        Sort Key: hour
        Sort Method: top-N heapsort  Memory: 25kB
        ->  Seq Scan on performance  (cost=0.00..4353475.10 rows=166530310 width=8) (actual time=0.006..327149.828 rows=192330557 loops=1)
Planning time: 0.070 ms
Execution time: 376330.573 ms

Tại sao nó mất nhiều thời gian? Nếu có một chỉ mục trên trường ngày desc - không nên lấy dữ liệu siêu nhanh?


2
Điều này vẫn giữ nguyên sau một VACUUM ANALYZE performance;?
dezso

Tôi đang chạy nó bây giờ. vẫn chạy sau 8 phút. Nhưng tôi đang lưu trữ cơ sở dữ liệu trên RDS để họ tự động VACUUM
Dejell

Không, nó không giúp được gì. Sau VACUUM, kết quả tương tự - rất chậm
Dejell

chỉ số BRIN sẽ giúp?
Dejell

3
Điều gì xảy ra nếu bạn chạy select hour from performance order by hour desc nulls last limit 10;?
ypercubeᵀᴹ

Câu trả lời:


17

Trong mã mẫu của bạn ở trên, chỉ mục được tạo rõ ràng NULLS LASTvà truy vấn đang chạy ngầm NULLS FIRST(là mặc định cho ORDER BY .. DESC) vì vậy PostgreQuery sẽ cần sắp xếp lại dữ liệu nếu sử dụng chỉ mục. Kết quả là chỉ mục thực sự sẽ làm cho truy vấn chậm hơn nhiều lần so với quét bảng (đã chậm).

rds-9.6.5 root@db1=> create table performance (id integer, installs integer, hour timestamp without time zone);
CREATE TABLE
Time: 28.100 ms

rds-9.6.5 root@db1=> with generator as (select generate_series(1,166530) i)
[more] - > insert into performance (
[more] ( >   select
[more] ( >     i id,
[more] ( >     (random()*1000)::integer installs,
[more] ( >     (now() - make_interval(secs => i))::timestamp installs
[more] ( >   from generator
[more] ( > );
INSERT 0 166530
Time: 244.872 ms

rds-9.6.5 root@db1=> create index hour_idx
[more] - > on performance
[more] - > using btree
[more] - > (hour desc nulls last);
CREATE INDEX
Time: 67.089 ms

rds-9.6.5 root@db1=> vacuum analyze performance;
VACUUM
Time: 43.552 ms

Chúng ta có thể thêm một WHEREmệnh đề trên cột giờ để sử dụng chỉ mục trở thành một ý tưởng hay - nhưng lưu ý cách chúng ta vẫn cần sắp xếp lại dữ liệu từ chỉ mục.

rds-9.6.5 root@db1=> explain select hour from performance where hour>now() order by hour desc limit 10;
                                         QUERY PLAN
---------------------------------------------------------------------------------------------
 Limit  (cost=4.45..4.46 rows=1 width=8)
   ->  Sort  (cost=4.45..4.46 rows=1 width=8)
         Sort Key: hour DESC
         ->  Index Only Scan using hour_idx on performance  (cost=0.42..4.44 rows=1 width=8)
               Index Cond: (hour > now())
(5 rows)

Time: 0.789 ms

Nếu chúng tôi thêm một tường minh NULLS LASTvào truy vấn của bạn thì nó sẽ sử dụng chỉ mục như mong đợi.

rds-9.6.5 root@db1=> explain select hour from performance order by hour desc NULLS LAST limit 10;
                                          QUERY PLAN
-----------------------------------------------------------------------------------------------
 Limit  (cost=0.42..0.68 rows=10 width=8)
   ->  Index Only Scan using hour_idx on performance  (cost=0.42..4334.37 rows=166530 width=8)
(2 rows)

Time: 0.526 ms

Ngoài ra, nếu chúng tôi bỏ (không mặc định) NULLS LASTkhỏi chỉ mục của bạn thì truy vấn sẽ sử dụng nó như mong đợi mà không cần sửa đổi.

rds-9.6.5 root@db1=> drop index hour_idx;
DROP INDEX
Time: 4.124 ms

rds-9.6.5 root@db1=> create index hour_idx
[more] - > on performance
[more] - > using btree
[more] - > (hour desc);
CREATE INDEX
Time: 69.220 ms

rds-9.6.5 root@db1=> explain select hour from performance order by hour desc limit 10;
                                          QUERY PLAN
-----------------------------------------------------------------------------------------------
 Limit  (cost=0.42..0.68 rows=10 width=8)
   ->  Index Only Scan using hour_idx on performance  (cost=0.42..4334.37 rows=166530 width=8)
(2 rows)

Time: 0.725 ms 

Lưu ý rằng bạn cũng có thể thả DESCtừ chỉ mục của bạn; PostgreSQL có thể quét các chỉ mục cả về phía trước và phía sau và trên các chỉ mục một cột thường không cần thiết để đảo ngược chúng. Bạn chỉ cần cẩn thận về việc có sự kết hợp đúng thứ tự và null trước / cuối.

rds-9.6.5 root@db1=> drop index hour_idx;
DROP INDEX
Time: 3.837 ms

rds-9.6.5 root@db1=> create index hour_idx
[more] - > on performance
[more] - > using btree
[more] - > (hour);
CREATE INDEX
Time: 94.815 ms

rds-9.6.5 root@db1=> explain select hour from performance order by hour desc limit 10;
                                               QUERY PLAN
--------------------------------------------------------------------------------------------------------
 Limit  (cost=0.42..0.68 rows=10 width=8)
   ->  Index Only Scan Backward using hour_idx on performance  (cost=0.42..4334.37 rows=166530 width=8)
(2 rows)

Time: 0.740 ms

2

Nếu hầu hết các truy vấn của bạn có ý định chọn các giá trị không phải NULL từ hourđó thì bạn nên xem xét việc xây dựng một chỉ mục một phần trên các giá trị đó, nghĩa là:

CREATE INDEX hour_not_null_idx ON performance (hour)
 WHERE hour IS NOT NULL;

trong đó, miễn là bạn truy vấn một giá trị cụ thể hour, như Jeremy đã thể hiện trong câu trả lời của mình hoặc thêm hour IS NOT NULLvào WHEREmệnh đề của bạn , sẽ cho bạn kết quả tương tự và cũng có thể giúp bạn tiết kiệm một ít không gian:

# explain select hour from performance where hour > now() order by hour desc limit 10;
 Limit  (cost=0.42..5.30 rows=10 width=8)
   ->  Index Only Scan Backward using hour_not_null_idx on performance  (cost=0.42..8.72 rows=17 width=8)
         Index Cond: (hour > now())

Nếu không có NULLgiá trị nào trong cột, bạn nên khai báo nó NOT NULL(Tôi sẽ giả sử bạn biết cách thực hiện điều này với ALTER TABLE; o)), sau đó tạo chỉ mục (không có NULLS LAST, vì dù sao nó không còn quan trọng nữa). Sau đó, bạn nhận được cùng một lợi ích:

william=# create index hour_idx on performance using btree ( hour );
CREATE INDEX
william=# explain select hour from performance order by hour desc limit 10;
                                           QUERY PLAN                                               
--------------------------------------------------------------------------------------------------------
 Limit  (cost=0.42..0.73 rows=10 width=8)
   ->  Index Only Scan Backward using hour_idx on performance  (cost=0.42..5238.37 rows=166530 width=8)
(2 rows)

Tôi không có bất kỳ giá trị null nào trong trường giờ, Will - nó có thay đổi câu trả lời của bạn không?
Dejell

Không, nó vẫn hữu ích. Ngay cả khi bạn khai báo cột KHÔNG NULL, NULLS FIRST / LAST ảnh hưởng đến việc bạn có quét chỉ mục hay một bước sắp xếp bổ sung (như Jeremy đã chỉ ra)
Will Crawford

Trên thực tế, có thể nó làm. Với cột được khai báo NOT NULL, nếu bạn tạo chỉ mục mà không có bất kỳ tham chiếu nào đến NULLSđầu tiên hoặc cuối cùng, và tương tự, không tạo tham chiếu nào trong truy vấn, bạn sẽ chỉ quét chỉ mục mỗi lần.
Will Crawford
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.