Tối ưu hóa truy vấn trên một loạt dấu thời gian (hai cột)


96

Tôi sử dụng PostgreSQL 9.1 trên Ubuntu 12.04.

Tôi cần chọn các bản ghi trong một khoảng thời gian: bảng của tôi time_limitscó hai timestamptrường và một thuộc integertính. Có các cột bổ sung trong bảng thực tế của tôi không liên quan đến truy vấn này.

create table (
   start_date_time timestamp,
   end_date_time timestamp, 
   id_phi integer, 
   primary key(start_date_time, end_date_time,id_phi);

Bảng này chứa khoảng 2 triệu bản ghi.

Các câu hỏi như sau mất rất nhiều thời gian:

select * from time_limits as t 
where t.id_phi=0 
and t.start_date_time <= timestamp'2010-08-08 00:00:00'
and t.end_date_time   >= timestamp'2010-08-08 00:05:00';

Vì vậy, tôi đã thử thêm một chỉ mục khác - nghịch đảo của PK:

create index idx_inversed on time_limits(id_phi, start_date_time, end_date_time);

Tôi có ấn tượng rằng hiệu suất được cải thiện: Thời gian truy cập các bản ghi ở giữa bảng có vẻ hợp lý hơn: ở đâu đó trong khoảng từ 40 đến 90 giây.

Nhưng vẫn còn vài chục giây cho các giá trị ở giữa phạm vi thời gian. Và hai lần nữa khi nhắm mục tiêu cuối bảng (nói theo trình tự thời gian).

Tôi đã thử explain analyzelần đầu tiên để có được kế hoạch truy vấn này:

 Bitmap Heap Scan on time_limits  (cost=4730.38..22465.32 rows=62682 width=36) (actual time=44.446..44.446 rows=0 loops=1)
   Recheck Cond: ((id_phi = 0) AND (start_date_time <= '2011-08-08 00:00:00'::timestamp without time zone) AND (end_date_time >= '2011-08-08 00:05:00'::timestamp without time zone))
   ->  Bitmap Index Scan on idx_time_limits_phi_start_end  (cost=0.00..4714.71 rows=62682 width=0) (actual time=44.437..44.437 rows=0 loops=1)
         Index Cond: ((id_phi = 0) AND (start_date_time <= '2011-08-08 00:00:00'::timestamp without time zone) AND (end_date_time >= '2011-08-08 00:05:00'::timestamp without time zone))
 Total runtime: 44.507 ms

Xem kết quả trên depesz.com.

Tôi có thể làm gì để tối ưu hóa tìm kiếm? Bạn có thể thấy tất cả thời gian được dành để quét hai cột dấu thời gian một lần id_phiđược đặt thành 0. Và tôi không hiểu bản quét lớn (60K hàng!) Trên dấu thời gian. Họ không được lập chỉ mục bởi khóa chính và idx_inversedtôi đã thêm?

Tôi có nên thay đổi từ loại dấu thời gian sang thứ khác không?

Tôi đã đọc một chút về các chỉ số GIST và GIN. Tôi tập hợp họ có thể hiệu quả hơn trong các điều kiện nhất định cho các loại tùy chỉnh. Nó có phải là một lựa chọn khả thi cho trường hợp sử dụng của tôi không?


1
cũng là 45s. Tôi không biết tại sao nó nói 45ms. Tôi thậm chí sẽ không bắt đầu phàn nàn nếu nó nhanh đến 45ms ... :-) Có thể một lỗi trong đầu ra của phân tích giải thích. Hoặc có thể đó là thời gian của phân tích để thực hiện. Không biết. Nhưng 40/50 giây là những gì tôi đo được.
Stephane Rolland

2
Thời gian được báo cáo trong explain analyzeđầu ra là thời gian truy vấn cần thiết trên máy chủ . Nếu truy vấn của bạn mất 45 giây, thì thời gian bổ sung được dành để chuyển dữ liệu từ cơ sở dữ liệu sang chương trình chạy truy vấn Sau tất cả các hàng là 62682 và nếu mỗi hàng lớn (ví dụ: có hàng dài varcharhoặc textcột), điều này có thể ảnh hưởng đến thời gian chuyển quyết liệt
a_horse_with_no_name

@a_horse_with_no_name: rows=62682 rowsước tính của người lập kế hoạch . Truy vấn trả về 0 hàng. (actual time=44.446..44.446 rows=0 loops=1)
Erwin Brandstetter

@ErwinBrandstetter: à, đúng rồi. Tôi bỏ qua điều đó. Nhưng tôi vẫn chưa bao giờ thấy đầu ra của giải thích phân tích nói dối về thời gian thực hiện.
a_horse_with_no_name

Câu trả lời:


162

Đối với Postgres 9.1 trở lên:

CREATE INDEX idx_time_limits_ts_inverse
ON time_limits (id_phi, start_date_time, end_date_time DESC);

Trong hầu hết các trường hợp, thứ tự sắp xếp của một chỉ mục hầu như không liên quan. Postgres có thể quét ngược thực tế nhanh như vậy. Nhưng đối với các truy vấn phạm vi trên nhiều cột, nó có thể tạo ra sự khác biệt lớn . Quan hệ gần gũi:

Xem xét truy vấn của bạn:

SELECT *
FROM   time_limits
WHERE  id_phi = 0
AND    start_date_time <= '2010-08-08 00:00'
AND    end_date_time   >= '2010-08-08 00:05';

Thứ tự sắp xếp của cột đầu tiên id_phitrong chỉ mục là không liên quan. Vì nó đã được kiểm tra cho đẳng thức ( =), nên nó sẽ được ưu tiên. Bạn đã đúng Thêm trong câu trả lời liên quan này:

Postgres có thể nhảy tới id_phi = 0bên cạnh mà không mất thời gian và xem xét hai cột sau của chỉ mục phù hợp. Chúng được truy vấn với các điều kiện phạm vi của thứ tự sắp xếp ngược ( <=, >=). Trong chỉ mục của tôi, hàng đủ điều kiện đến đầu tiên. Nên là cách nhanh nhất có thể với chỉ số B-Tree 1 :

  • Bạn muốn start_date_time <= something: chỉ mục có dấu thời gian sớm nhất trước tiên.
    • Nếu nó đủ điều kiện, cũng kiểm tra cột 3.
      Lặp lại cho đến khi hàng đầu tiên không đủ điều kiện (siêu nhanh).
  • Bạn muốn end_date_time >= something: chỉ mục có dấu thời gian mới nhất đầu tiên.
    • Nếu nó đủ điều kiện, hãy tiếp tục tìm nạp các hàng cho đến khi hàng đầu tiên không (siêu nhanh).
      Tiếp tục với giá trị tiếp theo cho cột 2 ..

Postgres có thể quét tiến hoặc lùi. Cách bạn có chỉ mục, nó phải đọc tất cả các hàng khớp trên hai cột đầu tiên và sau đó lọc vào cột thứ ba. Hãy chắc chắn đọc các Chỉ mụcORDER BY chương trong hướng dẫn. Nó phù hợp với câu hỏi của bạn khá tốt.

Có bao nhiêu hàng khớp với hai cột đầu tiên?
Chỉ một số start_date_timegần với thời điểm bắt đầu của phạm vi thời gian của bảng. Nhưng hầu như tất cả các hàng với id_phi = 0ở cuối thời gian của bảng! Vì vậy, hiệu suất suy giảm với thời gian bắt đầu sau.

Dự toán kế hoạch

Các kế hoạch ước tính rows=62682cho truy vấn ví dụ của bạn. Trong số đó, không ai đủ điều kiện ( rows=0). Bạn có thể nhận được ước tính tốt hơn nếu bạn tăng mục tiêu thống kê cho bảng. Đối với 2.000.000 hàng ...

ALTER TABLE time_limits ALTER start_date_time SET STATISTICS 1000;
ALTER TABLE time_limits ALTER end_date_time   SET STATISTICS 1000;

... Có thể trả tiền. Hoặc thậm chí cao hơn. Thêm trong câu trả lời liên quan này:

Tôi đoán bạn không cần điều đó cho id_phi(chỉ một vài giá trị riêng biệt, phân bố đều), nhưng đối với dấu thời gian (rất nhiều giá trị riêng biệt, phân bố không đều).
Tôi cũng không nghĩ nó quan trọng với chỉ số được cải thiện.

CLUSTER / pg numpack

Nếu bạn muốn nó nhanh hơn, tuy nhiên, bạn có thể sắp xếp thứ tự vật lý của các hàng trong bảng của mình. Nếu bạn có thể đủ khả năng khóa bảng của mình trong một khoảng thời gian ngắn (ví dụ vào giờ nghỉ) để viết lại bảng của bạn và sắp xếp các hàng theo chỉ mục:

ALTER TABLE time_limits CLUSTER ON idx_time_limits_inversed;

Với quyền truy cập đồng thời, hãy xem xét pg numpack , có thể làm tương tự mà không cần khóa độc quyền.

Dù bằng cách nào, hiệu quả là ít khối hơn cần được đọc từ bảng và mọi thứ đều được sắp xếp trước. Đó là hiệu ứng một lần xấu đi theo thời gian với việc ghi trên bảng phân chia thứ tự sắp xếp vật lý.

Chỉ số GiST trong Postgres 9.2+

1 Với pg 9.2+, có một tùy chọn khác, có thể nhanh hơn: chỉ số GiST cho cột phạm vi.

  • Có các loại phạm vi tích hợp cho timestamptimestamp with time zone: tsrange,tstzrange . Một chỉ số btree thường nhanh hơn cho một integercột bổ sung như id_phi. Nhỏ hơn và rẻ hơn để duy trì, quá. Nhưng truy vấn có thể vẫn sẽ nhanh hơn tổng thể với chỉ mục kết hợp.

  • Thay đổi định nghĩa bảng của bạn hoặc sử dụng một chỉ mục biểu thức .

  • Đối với chỉ mục GiST nhiều màu trong tay, bạn cũng cần btree_gistcài đặt mô-đun bổ sung (một lần cho mỗi cơ sở dữ liệu) cung cấp các lớp toán tử để bao gồm một integer.

Bộ ba! Một chỉ số GiST chức năng nhiều màu :

CREATE EXTENSION IF NOT EXISTS btree_gist;  -- if not installed, yet

CREATE INDEX idx_time_limits_funky ON time_limits USING gist
(id_phi, tsrange(start_date_time, end_date_time, '[]'));

Sử dụng toán tử "chứa phạm vi"@> trong truy vấn của bạn ngay bây giờ:

SELECT *
FROM   time_limits
WHERE  id_phi = 0
AND    tsrange(start_date_time, end_date_time, '[]')
    @> tsrange('2010-08-08 00:00', '2010-08-08 00:05', '[]')

Chỉ số SP-GiST trong Postgres 9.3+

Một chỉ mục SP-GiST có thể còn nhanh hơn cho loại truy vấn này - ngoại trừ việc trích dẫn hướng dẫn :

Hiện tại, chỉ có các loại chỉ mục B-tree, GiST, GIN và BRIN hỗ trợ các chỉ mục nhiều màu.

Vẫn đúng trong Postgres 12.
Bạn sẽ phải kết hợp một spgistchỉ mục trên chỉ (tsrange(...))với một btreechỉ mục thứ hai trên (id_phi). Với chi phí bổ sung, tôi không chắc điều này có thể cạnh tranh.
Câu trả lời liên quan với điểm chuẩn cho chỉ một tsrangecột:


78
Tôi nên nói điều này ít nhất một lần, rằng mỗi câu trả lời của bạn về SO và DBA đều có giá trị gia tăng / chứng minh thực sự cao và hầu hết thời gian là đầy đủ nhất. Chỉ cần nói một lần: Tôn trọng!.
Stephane Rolland

1
Merci bien! :) Vì vậy, bạn đã nhận được kết quả nhanh hơn?
Erwin Brandstetter

Tôi phải hoàn thành bản sao số lượng lớn được tạo ra từ truy vấn cực kỳ khó xử của tôi, vì vậy làm cho quá trình thực sự chậm, nó đã quay trong nhiều giờ trước khi tôi đặt câu hỏi. Nhưng tôi đã tính toán và tôi quyết định để nó quay cho đến sáng mai, nó sẽ hoàn thành và bảng mới đã sẵn sàng để được lấp đầy vào ngày mai. Tôi đã cố gắng tạo chỉ mục của bạn đồng thời trong công việc, nhưng do truy cập quá nhiều (tôi nghĩ), việc tạo chỉ mục nên bị khóa. Tôi sẽ lặp lại cùng thời gian thử nghiệm này một lần nữa vào ngày mai với giải pháp của bạn. Tôi cũng đã xem xét cách nâng cấp lên 9.2 ;-) cho debian / ubfox.
Stephane Rolland

2
@StephaneRolland: vẫn rất thú vị tại sao đầu ra phân tích giải thích hiển thị 45 mili giây trong khi bạn thấy truy vấn mất hơn 40 giây.
a_horse_with_no_name

1
@ John: Postgres có thể đi qua một chỉ mục tiến hoặc lùi, nhưng nó không thể thay đổi hướng trong cùng một lần quét. Lý tưởng nhất là bạn có tất cả các hàng đủ điều kiện cho mỗi nút trước (hoặc cuối cùng), nhưng nó phải được căn chỉnh giống nhau (các vị từ truy vấn phù hợp) cho tất cả các cột để có kết quả tốt nhất.
Erwin Brandstetter

5

Tuy nhiên, câu trả lời của Erwin đã rất toàn diện:

Các loại phạm vi cho dấu thời gian có sẵn trong PostgreQuery 9.1 với phần mở rộng Tạm thời từ Jeff Davis: https://github.com/jeff-davis/PostgreQuery-Temporal

Lưu ý: có các tính năng hạn chế (sử dụng Timestamptz và bạn chỉ có thể có kiểu chồng chéo '[)' afaik). Ngoài ra, có rất nhiều lý do tuyệt vời khác để nâng cấp lên PostgreSQL 9.2.


3

Bạn có thể thử tạo chỉ mục nhiều màu theo một thứ tự khác:

primary key(id_phi, start_date_time,end_date_time);

Tôi đã đăng một lần một câu hỏi tương tự cũng liên quan đến việc sắp xếp các chỉ mục trên một chỉ mục nhiều màu. Điều quan trọng là cố gắng sử dụng các điều kiện hạn chế nhất trước tiên để giảm không gian tìm kiếm.

Chỉnh sửa : Lỗi của tôi. Bây giờ tôi thấy rằng bạn đã có chỉ số này được xác định.


Tôi đã có cả hai chỉ số. Ngoại trừ khóa chính là khóa khác, nhưng chỉ mục bạn đề xuất đã tồn tại và là chỉ số được sử dụng nếu bạn xem phần giải thích:Bitmap Index Scan on idx_time_limits_phi_start_end
Stephane Rolland

1

Tôi đã xoay sở để tăng nhanh (từ 1 giây lên 70ms)

Tôi có một bảng với tổng hợp của nhiều phép đo và nhiều cấp độ ( lcột) (30 giây, 1m, 1h, v.v.) có hai cột giới hạn phạm vi: $sbắt đầu và $ekết thúc.

Tôi đã tạo hai chỉ mục nhiều màu: một cho bắt đầu và một cho cuối.

Tôi đã điều chỉnh truy vấn chọn: chọn các phạm vi trong đó giới hạn bắt đầu của chúng nằm trong phạm vi đã cho. ngoài ra chọn phạm vi trong đó giới hạn cuối của chúng nằm trong phạm vi nhất định.

Giải thích cho thấy hai luồng hàng sử dụng chỉ mục của chúng tôi một cách hiệu quả.

Chỉ mục:

drop index if exists agg_search_a;
CREATE INDEX agg_search_a
ON agg (measurement_id, l, "$s");

drop index if exists agg_search_b;
CREATE INDEX agg_search_b
ON agg (measurement_id, l, "$e");

Chọn truy vấn:

select "$s", "$e", a, t, b, c from agg
where 
    measurement_id=0 
    and l =  '30s'
    and (
        (
            "$s" > '2013-05-01 02:05:05'
            and "$s" < '2013-05-01 02:18:15'
        )
        or 
        (
             "$e" > '2013-05-01 02:00:05'
            and "$e" < '2013-05-01 02:18:05'
        )
    )

;

Giải thích:

[
  {
    "Execution Time": 0.058,
    "Planning Time": 0.112,
    "Plan": {
      "Startup Cost": 10.18,
      "Rows Removed by Index Recheck": 0,
      "Actual Rows": 37,
      "Plans": [
    {
      "Startup Cost": 10.18,
      "Actual Rows": 0,
      "Plans": [
        {
          "Startup Cost": 0,
          "Plan Width": 0,
          "Actual Rows": 26,
          "Node Type": "Bitmap Index Scan",
          "Index Cond": "((measurement_id = 0) AND ((l)::text = '30s'::text) AND (\"$s\" > '2013-05-01 02:05:05'::timestamp without time zone) AND (\"$s\" < '2013-05-01 02:18:15'::timestamp without time zone))",
          "Plan Rows": 29,
          "Parallel Aware": false,
          "Actual Total Time": 0.016,
          "Parent Relationship": "Member",
          "Actual Startup Time": 0.016,
          "Total Cost": 5,
          "Actual Loops": 1,
          "Index Name": "agg_search_a"
        },
        {
          "Startup Cost": 0,
          "Plan Width": 0,
          "Actual Rows": 36,
          "Node Type": "Bitmap Index Scan",
          "Index Cond": "((measurement_id = 0) AND ((l)::text = '30s'::text) AND (\"$e\" > '2013-05-01 02:00:05'::timestamp without time zone) AND (\"$e\" < '2013-05-01 02:18:05'::timestamp without time zone))",
          "Plan Rows": 39,
          "Parallel Aware": false,
          "Actual Total Time": 0.011,
          "Parent Relationship": "Member",
          "Actual Startup Time": 0.011,
          "Total Cost": 5.15,
          "Actual Loops": 1,
          "Index Name": "agg_search_b"
        }
      ],
      "Node Type": "BitmapOr",
      "Plan Rows": 68,
      "Parallel Aware": false,
      "Actual Total Time": 0.027,
      "Parent Relationship": "Outer",
      "Actual Startup Time": 0.027,
      "Plan Width": 0,
      "Actual Loops": 1,
      "Total Cost": 10.18
    }
      ],
      "Exact Heap Blocks": 1,
      "Node Type": "Bitmap Heap Scan",
      "Plan Rows": 68,
      "Relation Name": "agg",
      "Alias": "agg",
      "Parallel Aware": false,
      "Actual Total Time": 0.037,
      "Recheck Cond": "(((measurement_id = 0) AND ((l)::text = '30s'::text) AND (\"$s\" > '2013-05-01 02:05:05'::timestamp without time zone) AND (\"$s\" < '2013-05-01 02:18:15'::timestamp without time zone)) OR ((measurement_id = 0) AND ((l)::text = '30s'::text) AND (\"$e\" > '2013-05-01 02:00:05'::timestamp without time zone) AND (\"$e\" < '2013-05-01 02:18:05'::timestamp without time zone)))",
      "Lossy Heap Blocks": 0,
      "Actual Startup Time": 0.033,
      "Plan Width": 44,
      "Actual Loops": 1,
      "Total Cost": 280.95
    },
    "Triggers": []
  }
]

Bí quyết là các nút kế hoạch của bạn chỉ chứa các hàng mong muốn. Trước đây chúng ta có hàng ngàn hàng trong nút kế hoạch vì nó được chọn all points from some point in time to the very end, sau đó nút tiếp theo đã loại bỏ các hàng không cần thiết.

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.