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 EXPLAIN
trê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ì.
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.