PostgreSQL - Làm việc với hàng ngàn phần tử


8

Tôi đang tìm cách chọn các hàng dựa trên việc một cột có được chứa trong một danh sách lớn các giá trị mà tôi chuyển dưới dạng một mảng số nguyên hay không.

Đây là truy vấn tôi hiện đang sử dụng:

SELECT item_id, other_stuff, ...
FROM (
    SELECT
        -- Partitioned row number as we only want N rows per id
        ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY start_date) AS r,
        item_id, other_stuff, ...
    FROM mytable
    WHERE
        item_id = ANY ($1) -- Integer array
        AND end_date > $2
    ORDER BY item_id ASC, start_date ASC, allowed ASC
) x
WHERE x.r <= 12

Bảng này có cấu trúc như vậy:

    Column     |            Type             | Collation | Nullable | Default 
---------------+-----------------------------+-----------+----------+---------
 item_id       | integer                     |           | not null | 
 allowed       | boolean                     |           | not null | 
 start_date    | timestamp without time zone |           | not null | 
 end_date      | timestamp without time zone |           | not null | 
 ...


 Indexes:
    "idx_dtr_query" btree (item_id, start_date, allowed, end_date)
    ...

Tôi đã đưa ra chỉ số này sau khi thử các chỉ số khác nhau và chạy EXPLAINtrên truy vấn. Đây là cách hiệu quả nhất cho cả truy vấn và sắp xếp. Dưới đây là phân tích giải thích của truy vấn:

Subquery Scan on x  (cost=0.56..368945.41 rows=302230 width=73) (actual time=0.021..276.476 rows=168395 loops=1)
  Filter: (x.r <= 12)
  Rows Removed by Filter: 90275
  ->  WindowAgg  (cost=0.56..357611.80 rows=906689 width=73) (actual time=0.019..248.267 rows=258670 loops=1)
        ->  Index Scan using idx_dtr_query on mytable  (cost=0.56..339478.02 rows=906689 width=73) (actual time=0.013..130.362 rows=258670 loops=1)
              Index Cond: ((item_id = ANY ('{/* 15,000 integers */}'::integer[])) AND (end_date > '2018-03-30 12:08:00'::timestamp without time zone))
Planning time: 30.349 ms
Execution time: 284.619 ms

Vấn đề là mảng int có thể chứa tới 15.000 phần tử hoặc hơn và truy vấn trở nên khá chậm trong trường hợp này (khoảng 800ms trên máy tính xách tay của tôi, một Dell XPS gần đây).

Tôi nghĩ rằng việc truyền mảng int là một tham số có thể chậm, và xem xét danh sách id có thể được lưu trữ trước trong cơ sở dữ liệu tôi đã thử làm điều này. Tôi lưu trữ chúng trong một mảng trong một bảng khác và được sử dụng item_id = ANY (SELECT UNNEST(item_ids) FROM ...), tốc độ này chậm hơn so với cách tiếp cận hiện tại của tôi. Tôi cũng đã thử lưu trữ chúng theo từng hàng và sử dụng item_id IN (SELECT item_id FROM ...), thậm chí còn chậm hơn, thậm chí chỉ với các hàng có liên quan đến trường hợp thử nghiệm của tôi trong bảng.

Có cách nào tốt hơn để làm điều này?

Cập nhật: theo ý kiến ​​của Evan , một cách tiếp cận khác mà tôi đã thử: mỗi mục là một phần của một số nhóm, vì vậy thay vì chuyển id mục của nhóm, tôi đã thử thêm id nhóm trong mytable:

    Column     |            Type             | Collation | Nullable | Default 
---------------+-----------------------------+-----------+----------+---------
 item_id       | integer                     |           | not null | 
 allowed       | boolean                     |           | not null | 
 start_date    | timestamp without time zone |           | not null | 
 end_date      | timestamp without time zone |           | not null | 
 group_ids     | integer[]                   |           | not null | 
 ...

 Indexes:
    "idx_dtr_query" btree (item_id, start_date, allowed, end_date)
    "idx_dtr_group_ids" gin (group_ids)
    ...

Truy vấn mới ($ 1 là id nhóm được nhắm mục tiêu):

SELECT item_id, other_stuff, ...
FROM (
    SELECT
        -- Partitioned row number as we only want N rows per id
        ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY start_date) AS r,
        item_id, other_stuff, ...
    FROM mytable
    WHERE
        $1 = ANY (group_ids)
        AND end_date > $2
    ORDER BY item_id ASC, start_date ASC, allowed ASC
) x
WHERE x.r <= 12

Giải thích phân tích:

Subquery Scan on x  (cost=123356.60..137112.58 rows=131009 width=74) (actual time=811.337..1087.880 rows=172023 loops=1)
  Filter: (x.r <= 12)
  Rows Removed by Filter: 219726
  ->  WindowAgg  (cost=123356.60..132199.73 rows=393028 width=74) (actual time=811.330..1040.121 rows=391749 loops=1)
        ->  Sort  (cost=123356.60..124339.17 rows=393028 width=74) (actual time=811.311..868.127 rows=391749 loops=1)
              Sort Key: item_id, start_date, allowed
              Sort Method: external sort  Disk: 29176kB
              ->  Seq Scan on mytable (cost=0.00..69370.90 rows=393028 width=74) (actual time=0.105..464.126 rows=391749 loops=1)
                    Filter: ((end_date > '2018-04-06 12:00:00'::timestamp without time zone) AND (2928 = ANY (group_ids)))
                    Rows Removed by Filter: 1482567
Planning time: 0.756 ms
Execution time: 1098.348 ms

Có thể có chỗ để cải thiện với các chỉ mục nhưng tôi gặp khó khăn trong việc hiểu cách thức các postgres sử dụng chúng, vì vậy tôi không chắc chắn nên thay đổi điều gì.


Có bao nhiêu hàng trong "mytable"? Có bao nhiêu giá trị "item_id" khác nhau ở đó?
Nick

Ngoài ra, bạn không nên có ràng buộc về tính duy nhất (có thể là chỉ mục duy nhất chưa được xác định) trong item_id trong mytable? ... Đã chỉnh sửa: oh, tôi thấy "PHẦN THAM GIA CỦA item_id", vì vậy câu hỏi này chuyển thành "Khóa thực, tự nhiên cho dữ liệu của bạn là gì? Điều gì sẽ tạo thành chỉ mục duy nhất ở đó?"
Nick

Khoảng 12 triệu hàng trong mytable, với khoảng 500k khác nhau item_id. Không có khóa duy nhất tự nhiên thực sự cho bảng này, đó là dữ liệu được tạo tự động để lặp lại các sự kiện. Tôi đoán item_id+ start_date+ name(trường không được hiển thị ở đây) có thể tạo thành một loại khóa.
Jukurrpa

Bạn có thể đăng kế hoạch thực hiện bạn đang nhận được?
Colin 't Hart

Chắc chắn, thêm các phân tích giải thích cho câu hỏi.
Jukurrpa

Câu trả lời:


1

Có cách nào tốt hơn để làm điều này?

Có, sử dụng bảng tạm thời. Không có gì sai khi tạo bảng tạm thời được lập chỉ mục khi truy vấn của bạn là điên rồ.

BEGIN;
  CREATE TEMP TABLE myitems ( item_id int PRIMARY KEY );
  INSERT INTO myitems(item_id) VALUES (1), (2); -- and on and on
  CREATE INDEX ON myitems(item_id);
COMMIT;

ANALYZE myitems;

SELECT item_id, other_stuff, ...
FROM (
  SELECT
      -- Partitioned row number as we only want N rows per id
      ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY start_date) AS r,
      item_id, other_stuff, ...
  FROM mytable
  INNER JOIN myitems USING (item_id)
  WHERE end_date > $2
  ORDER BY item_id ASC, start_date ASC, allowed ASC
) x
WHERE x.r <= 12;

Nhưng thậm chí còn tốt hơn thế ...

"500k item_id khác nhau" ... "mảng int có thể chứa tới 15.000 phần tử"

Bạn đang chọn 3% cơ sở dữ liệu của riêng bạn. Tôi phải tự hỏi nếu bạn không tốt hơn khi tạo các nhóm / thẻ, v.v. trong chính lược đồ. Cá nhân tôi chưa bao giờ phải gửi 15.000 ID khác nhau vào một truy vấn.


Chỉ cần thử sử dụng bảng tạm thời và nó chậm hơn, ít nhất là trong trường hợp 15.000 id. Đối với việc tạo các nhóm trong chính lược đồ, bạn có nghĩa là một bảng với các id tôi chuyển qua làm đối số không? Tôi đã thử một cái gì đó như thế này nhưng hiệu suất tương tự hoặc tệ hơn so với phương pháp hiện tại của tôi. Tôi sẽ cập nhật câu hỏi với nhiều chi tiết hơn
Jukurrpa

Không, ý tôi là Nếu bạn có 15.000 id thông thường, bạn đang lưu trữ một cái gì đó trong ID, như vật phẩm đó có phải là sản phẩm nhà bếp hay không, thay vì lưu trữ nhóm_id tương ứng với "sản phẩm nhà bếp", bạn đang cố gắng tìm tất cả các sản phẩm nhà bếp bởi id của họ. (điều này không tốt cho mọi lý do) 15.000 id đó đại diện cho cái gì? Tại sao nó không được lưu trữ trên hàng?
Evan Carroll

Mỗi mục thuộc về nhiều nhóm (thường là 15-20 nhóm), vì vậy tôi đã cố lưu trữ chúng dưới dạng một mảng int trong mytable nhưng không thể tìm ra cách lập chỉ mục này đúng. Tôi cập nhật câu hỏi với tất cả các chi tiết.
Jukurrpa
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.