Lập chỉ mục cho truy vấn SQL với điều kiện WHERE và NHÓM THEO


15

Tôi đang cố gắng xác định chỉ mục nào sẽ sử dụng cho truy vấn SQL với một WHEREđiều kiện và một GROUP BYchỉ mục hiện đang chạy rất chậm.

Sự truy vấn của tôi:

SELECT group_id
FROM counter
WHERE ts between timestamp '2014-03-02 00:00:00.0' and timestamp '2014-03-05 12:00:00.0'
GROUP BY group_id

Bảng hiện có 32.000.000 hàng. Thời gian thực hiện của truy vấn tăng rất nhiều khi tôi tăng khung thời gian.

Bảng trong câu hỏi trông như thế này:

CREATE TABLE counter (
    id bigserial PRIMARY KEY
  , ts timestamp NOT NULL
  , group_id bigint NOT NULL
);

Tôi hiện có các chỉ mục sau, nhưng hiệu suất vẫn chậm:

CREATE INDEX ts_index
  ON counter
  USING btree
  (ts);

CREATE INDEX group_id_index
  ON counter
  USING btree
  (group_id);

CREATE INDEX comp_1_index
  ON counter
  USING btree
  (ts, group_id);

CREATE INDEX comp_2_index
  ON counter
  USING btree
  (group_id, ts);

Chạy EXPLAIN trên truy vấn sẽ cho kết quả như sau:

"QUERY PLAN"
"HashAggregate  (cost=467958.16..467958.17 rows=1 width=4)"
"  ->  Index Scan using ts_index on counter  (cost=0.56..467470.93 rows=194892 width=4)"
"        Index Cond: ((ts >= '2014-02-26 00:00:00'::timestamp without time zone) AND (ts <= '2014-02-27 23:59:00'::timestamp without time zone))"

SQL Fiddle với dữ liệu mẫu: http://sqlfiddle.com/#!15/7492b/1

Câu hỏi

Hiệu năng của truy vấn này có thể được cải thiện bằng cách thêm các chỉ mục tốt hơn hay tôi phải tăng sức mạnh xử lý?

Chỉnh sửa 1

Phiên bản PostgreSQL 9.3.2 được sử dụng.

Chỉnh sửa 2

Tôi đã thử đề xuất của @Erwin với EXISTS:

SELECT group_id
FROM   groups g
WHERE  EXISTS (
   SELECT 1
   FROM   counter c
   WHERE  c.group_id = g.group_id
   AND    ts BETWEEN timestamp '2014-03-02 00:00:00'
                 AND timestamp '2014-03-05 12:00:00'
   );

Nhưng thật không may, điều này dường như không làm tăng hiệu suất. Kế hoạch truy vấn:

"QUERY PLAN"
"Nested Loop Semi Join  (cost=1607.18..371680.60 rows=113 width=4)"
"  ->  Seq Scan on groups g  (cost=0.00..2.33 rows=133 width=4)"
"  ->  Bitmap Heap Scan on counter c  (cost=1607.18..158895.53 rows=60641 width=4)"
"        Recheck Cond: ((group_id = g.id) AND (ts >= '2014-01-01 00:00:00'::timestamp without time zone) AND (ts <= '2014-03-05 12:00:00'::timestamp without time zone))"
"        ->  Bitmap Index Scan on comp_2_index  (cost=0.00..1592.02 rows=60641 width=0)"
"              Index Cond: ((group_id = g.id) AND (ts >= '2014-01-01 00:00:00'::timestamp without time zone) AND (ts <= '2014-03-05 12:00:00'::timestamp without time zone))"

Chỉnh sửa 3

Kế hoạch truy vấn cho truy vấn LATITH từ ypercube:

"QUERY PLAN"
"Nested Loop  (cost=8.98..1200.42 rows=133 width=20)"
"  ->  Seq Scan on groups g  (cost=0.00..2.33 rows=133 width=4)"
"  ->  Result  (cost=8.98..8.99 rows=1 width=0)"
"        One-Time Filter: ($1 IS NOT NULL)"
"        InitPlan 1 (returns $1)"
"          ->  Limit  (cost=0.56..4.49 rows=1 width=8)"
"                ->  Index Only Scan using comp_2_index on counter c  (cost=0.56..1098691.21 rows=279808 width=8)"
"                      Index Cond: ((group_id = $0) AND (ts IS NOT NULL) AND (ts >= '2010-03-02 00:00:00'::timestamp without time zone) AND (ts <= '2014-03-05 12:00:00'::timestamp without time zone))"
"        InitPlan 2 (returns $2)"
"          ->  Limit  (cost=0.56..4.49 rows=1 width=8)"
"                ->  Index Only Scan Backward using comp_2_index on counter c_1  (cost=0.56..1098691.21 rows=279808 width=8)"
"                      Index Cond: ((group_id = $0) AND (ts IS NOT NULL) AND (ts >= '2010-03-02 00:00:00'::timestamp without time zone) AND (ts <= '2014-03-05 12:00:00'::timestamp without time zone))"

Có bao nhiêu group_idgiá trị khác nhau trên bàn?
ypercubeᵀᴹ

Có 133 nhóm khác nhau.

Các dấu thời gian nằm trong khoảng từ 2011 đến 2014. Cả giây và mili giây đều được sử dụng.

Bạn chỉ quan tâm group_idvà không trong bất kỳ số lượng?
Erwin Brandstetter

@Erwin Chúng tôi quan tâm đến max () và (min) cũng như trên cột thứ tư không được hiển thị trong ví dụ.
uldall

Câu trả lời:


6

Một ý tưởng khác, cũng sử dụng groupsbảng và cấu trúc được gọi là LATERALtham gia (đối với người hâm mộ SQL-Server, điều này gần như giống hệt OUTER APPLY). Nó có lợi thế là tổng hợp có thể được tính trong truy vấn con:

SELECT group_id, min_ts, max_ts
FROM   groups g,                    -- notice the comma here, is required
  LATERAL 
       ( SELECT MIN(ts) AS min_ts,
                MAX(ts) AS max_ts
         FROM counter c
         WHERE c.group_id = g.group_id
           AND c.ts BETWEEN timestamp '2011-03-02 00:00:00'
                        AND timestamp '2013-03-05 12:00:00'
       ) x 
WHERE min_ts IS NOT NULL ;

Kiểm tra tại SQL-Fiddle cho thấy truy vấn không quét chỉ mục trên (group_id, ts)chỉ mục.

Các kế hoạch tương tự được tạo ra bằng cách sử dụng 2 phép nối bên, một cho tối thiểu và một cho tối đa và cũng có 2 truy vấn con tương quan nội tuyến. Chúng cũng có thể được sử dụng nếu bạn cần hiển thị toàn bộ countercác hàng bên cạnh ngày tối thiểu và tối đa:

SELECT group_id, 
       min_ts, min_ts_id, 
       max_ts, max_ts_id 
FROM   groups g
  , LATERAL 
       ( SELECT ts AS min_ts, c.id AS min_ts_id
         FROM counter c
         WHERE c.group_id = g.group_id
           AND c.ts BETWEEN timestamp '2012-03-02 00:00:00'
                        AND timestamp '2014-03-05 12:00:00'
         ORDER BY ts ASC
         LIMIT 1
       ) xmin
  , LATERAL 
       ( SELECT ts AS max_ts, c.id AS max_ts_id
         FROM counter c
         WHERE c.group_id = g.group_id
           AND c.ts BETWEEN timestamp '2012-03-02 00:00:00'
                        AND timestamp '2014-03-05 12:00:00'
         ORDER BY ts DESC 
         LIMIT 1
       ) xmax
WHERE min_ts IS NOT NULL ;

@ypercube Tôi đã thêm kế hoạch truy vấn cho truy vấn của bạn vào câu hỏi ban đầu. Truy vấn chạy trong dưới 50 ms ngay cả trên các khoảng thời gian lớn.
uldall

5

Vì bạn không có tổng hợp trong danh sách chọn, nên việc group bynày khá giống với việc đưa một distinctvào danh sách chọn, phải không?

Nếu đó là những gì bạn muốn, bạn có thể có được một tra cứu chỉ mục nhanh trên comp_2_index bằng cách viết lại điều này để sử dụng truy vấn đệ quy, như được mô tả trên wiki PostgreQuery .

Tạo một khung nhìn để trả về hiệu quả các nhóm_ids khác nhau:

create or replace view groups as
WITH RECURSIVE t AS (
             SELECT min(counter.group_id) AS group_id
               FROM counter
    UNION ALL
             SELECT ( SELECT min(counter.group_id) AS min
                       FROM counter
                      WHERE counter.group_id > t.group_id) AS min
               FROM t
              WHERE t.group_id IS NOT NULL
    )
     SELECT t.group_id
       FROM t
      WHERE t.group_id IS NOT NULL
UNION ALL
     SELECT NULL::bigint AS col
      WHERE (EXISTS ( SELECT counter.id,
                counter.ts,
                counter.group_id
               FROM counter
              WHERE counter.group_id IS NULL));

Và sau đó sử dụng chế độ xem đó thay cho bảng tra cứu trong phần existsbán tham gia của Erwin .


4

Vì chỉ có 133 different group_id's, bạn có thể sử dụng integer(hoặc thậm chí smallint) cho group_id. Tuy nhiên, nó sẽ không mua cho bạn nhiều, bởi vì việc đệm tới 8 byte sẽ ăn phần còn lại trong bảng của bạn và các chỉ mục nhiều màu có thể. integerMặc dù vậy, việc xử lý đồng bằng phải nhanh hơn một chút. Thêm vào intso vớiint2 .

CREATE TABLE counter (
    id bigserial PRIMARY KEY
  , ts timestamp NOT NULL
  , group_id int NOT NULL
);

@Leo: dấu thời gian được lưu trữ dưới dạng số nguyên 8 byte trong cài đặt hiện đại và có thể được xử lý hoàn toàn nhanh chóng. Chi tiết.

@ypercube: Chỉ mục trên (group_id, ts)không thể giúp đỡ, vì không có điều kiện group_idtrong truy vấn.

Vấn đề chính của bạn là lượng dữ liệu khổng lồ phải được xử lý:

Quét chỉ mục bằng cách sử dụng ts_index trên bộ đếm (chi phí = 0,56..467470.93 hàng = 194892 width = 4)

Tôi thấy bạn chỉ quan tâm đến sự tồn tại của a group_id, và không có số lượng thực tế. Ngoài ra, chỉ có 133 group_ids khác nhau . Do đó, truy vấn của bạn có thể được thỏa mãn với lần truy cập đầu tiên gorup_idtrong khung thời gian. Do đó, đề xuất này cho một truy vấn thay thế với một EXISTSnửa tham gia :

Giả sử bảng tra cứu cho các nhóm:

SELECT group_id
FROM   groups g
WHERE  EXISTS (
   SELECT 1
   FROM   counter c
   WHERE  c.group_id = g.group_id
   AND    ts BETWEEN timestamp '2014-03-02 00:00:00'
                 AND timestamp '2014-03-05 12:00:00'
   );

Chỉ số của bạn comp_2_indextrên (group_id, ts)trở thành công cụ bây giờ.

SQL Fiddle (xây dựng trên fiddle được cung cấp bởi @ypercube trong các bình luận)

Ở đây, truy vấn thích chỉ mục trên (ts, group_id), nhưng tôi nghĩ đó là do thiết lập thử nghiệm với dấu thời gian "cụm". Nếu bạn loại bỏ các chỉ mục bằng hàng đầu ts( thêm về điều đó ), trình lập kế hoạch cũng sẽ vui vẻ sử dụng chỉ mục trên (group_id, ts)- đặc biệt là trong Quét chỉ mục .

Nếu điều đó hoạt động, bạn có thể không cần cải tiến có thể khác này: Dữ liệu tổng hợp trước trong chế độ xem được cụ thể hóa để giảm đáng kể số lượng hàng. Điều này sẽ có ý nghĩa đặc biệt, nếu bạn cũng cần số lượng thực tế bổ sung. Sau đó, bạn có chi phí để xử lý nhiều hàng một lần khi cập nhật mv. Bạn thậm chí có thể kết hợp các tổng hợp hàng ngày và hàng giờ (hai bảng riêng biệt) và điều chỉnh truy vấn của bạn theo đó.

Là khung thời gian trong các truy vấn của bạn tùy ý? Hoặc chủ yếu là vào phút đầy đủ / giờ / ngày?

CREATE MATERIALIZED VIEW counter_mv AS
SELECT date_trunc('hour', ts) AS hour
     , group_id
     , count(*) AS ct
GROUP BY 1,2
ORDER BY 1,2;

Tạo (các) chỉ mục cần thiết trên counter_mvvà điều chỉnh truy vấn của bạn để làm việc với nó ...


1
Tôi đã thử một vài thứ tương tự trong SQL-Fiddle , với 10k hàng, nhưng tất cả đều cho thấy một số lần quét liên tiếp. Liệu sử dụng groupsbảng làm cho sự khác biệt?
ypercubeᵀᴹ

@ypercube: Mình nghĩ vậy. Ngoài ra, ANALYZElàm cho một sự khác biệt. Nhưng các chỉ mục trên counterthậm chí được sử dụng mà không cần ANALYZEngay khi tôi giới thiệu groupsbảng. Điểm là, nếu không có bảng đó, dù sao cũng cần một seqscan để xây dựng tập hợp các nhóm_id có thể. Tôi đã thêm nhiều hơn vào câu trả lời của tôi. Và cảm ơn cho fiddle của bạn!
Erwin Brandstetter

Thật ki quặc. Bạn nói rằng trình tối ưu hóa của Postgres sẽ không sử dụng chỉ mục trên group_idngay cả cho một SELECT DISTINCT group_id FROM t;truy vấn?
ypercubeᵀᴹ

1
@ErwinBrandstetter Đó cũng là những gì tôi nghĩ, và rất ngạc nhiên khi phát hiện ra điều khác. Nếu không có LIMIT 1, nó có thể chọn quét chỉ mục bitmap, không có lợi từ việc dừng sớm và mất nhiều thời gian hơn. (Nhưng nếu bảng được hút bụi mới, nó có thể thích quét theo chỉ mục hơn quét bitmap, do đó hành vi bạn nhìn thấy phụ thuộc vào trạng thái chân không của bảng).
jjanes

1
@uldall: Tập hợp hàng ngày sẽ giảm đáng kể số lượng hàng. Điều đó sẽ làm các trick. Nhưng hãy chắc chắn thử EXISTS-query. Nó có thể nhanh đáng ngạc nhiên. Không làm việc tối thiểu / tối đa. Tuy nhiên, tôi sẽ quan tâm đến hiệu suất kết quả, nếu bạn thật tử tế khi thả một dòng ở đâ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.