Lọc trên văn bản mảng [] và sắp xếp trên dấu thời gian


7

Sự miêu tả

PostgreQuery 9.6 trên Linux, kích thước của tags_tmpbảng ~ 30 GB (10 triệu hàng), tagslà một text[]và chỉ có 6 giá trị.

tags_tmp(id int, tags text[], maker_date timestamp, value text)
id  tags        maker_date      value
1   {a,b,c}     2016-11-09      This is test 
2   {a}         2016-11-08      This is test 
3   {b,c}       2016-11-07      This is test 
4   {c}         2016-11-06      This is test 
5   {d}         2016-11-05      This is test 

Tôi cần lấy dữ liệu với bộ lọc trên tagscũng như order bytrên maker_date desc. Tôi có thể tạo một chỉ mục trên cả hai tags & maker_date desccột không?

Nếu không, bạn có thể đề xuất ý tưởng khác?

Ví dụ truy vấn

select id, tags, maker_date, value
from tags_tmp
where  tags && array['a','b']
order by maker_date desc
limit 5 offset 0

Mã SQL:

create index idx1 on tags_tmp using gin (tags);
create index idx2 on tags_tmp using btree(maker_date desc);

explain (analyse on, costs on, verbose)
select id, tags, maker_date, value
from tags_tmp
where tags && array['funny','inspiration']
order by maker_date desc
limit 5 offset 0 ;

Giải thích kết quả:

Limit  (cost=233469.63..233469.65 rows=5 width=116) (actual time=801.482..801.483 rows=5 loops=1)
  Output: id, tags, maker_date, value
  ->  Sort  (cost=233469.63..234714.22 rows=497833 width=116) (actual time=801.481..801.481 rows=5 loops=1)
        Output: id, tags, maker_date, value
        Sort Key: tags_tmp.maker_date DESC
        Sort Method: top-N heapsort  Memory: 25kB
        ->  Bitmap Heap Scan on public.tags_tmp  (cost=6486.58..225200.81 rows=497833 width=116) (actual time=212.982..696.650 rows=366392 loops=1)
              Output: id, tags, maker_date, value
              Recheck Cond: (tags_tmp.tags && '{funny,inspiration}'::text[])
              Heap Blocks: exact=120034
              ->  Bitmap Index Scan on idx1  (cost=0.00..6362.12 rows=497882 width=0) (actual time=171.742..171.742 rows=722612 loops=1)
                    Index Cond: (tags_tmp.tags && '{funny,inspiration}'::text[])
Planning time: 0.185 ms
Execution time: 802.128 ms

Thêm thông tin

Tôi đã thử nghiệm với việc sử dụng chỉ mục một phần cho chỉ một thẻ, tất nhiên, nó nhanh hơn. Nhưng tôi có nhiều thẻ , ví dụ : create index idx_tmp on tags_tmp using btree (maker_date desc) where (tags && array['tag1') or tags && array['tag2'] or ... or tags && array['tag6']. Và tôi đã thử nghiệm giữa tags && array['tag1']'tag1' = any(tags), hiệu suất là như nhau.

  1. text[]chỉ có 6 giá trị = a, b, c, d, e, f. Ví dụ: tags={a,b,c}, tags={a}, tags={a,c}, tags={a,b,c,d,e,f}, tags={b,f}vân vân. Nhưng nó không thể có giá trị g->z, A-Zvà vv

  2. create table tags_tmp(id int primary key not null, tags text[] not null, maker_date timestamp not null, value text)

  3. Về các distinctgiá trị mảng, tagscái chứa a20% hàng của bảng where 'a' = any(tags), b = 20% where 'b' = any(tags), c = 20% where 'c' = any(tags), d = 20% where 'd' = any(tags), e = 10% where 'e' = any(tags), f = 10% where 'f' = any(tags).

  4. Ngoài ra, (tags, maker_date)không phải là duy nhất.

  5. Bảng này không chỉ đọc.

  6. Đó là sort on timestamp, nhưng ví dụ của tôi cho thấy ngày, xin lỗi về điều đó.

Tình hình hiện tại: tags = 'a' or tags = 'b' or tags = 'c'và nhiều hơn nữa

(1) Với GIN indexhoặc chuyển đổi text[] to int[]cũng như chuyển đổi text[] to intvà hơn thế nữa, nó sẽ sử dụng chỉ mục bitmap trên nhiều thẻ. Cuối cùng, sau khi thử nghiệm, tôi quyết định sử dụng giải pháp cũ, thay đổi ORthành nhiều UNIONmệnh đề, mỗi mệnh đề UNIONsẽ giới hạn số lượng dữ liệu. Tất nhiên, tôi sẽ tạo partial indexcho từng giá trị thẻ cũng như tôi có thể kết hợp với (1) ở trên. Về mặt OFFSET, nó sẽ sử dụng một hoặc nhiều điều kiện trong WHEREmệnh đề thay thế.

Thí dụ

EXPLAIN (ANALYSE ON, costs ON, VERBOSE)
SELECT rs.*
FROM (
        (SELECT tags,
                id,
                maker_date
         FROM tags_tmp
         WHERE 'a' = any(tags)
           AND maker_date <= '2016-03-28 05:43:57.779528'::TIMESTAMP
         ORDER BY maker_date DESC LIMIT 5)
      UNION
        (SELECT tags,
                id,
                maker_date
         FROM tags_tmp
         WHERE 'b' = any(tags)
           AND maker_date <= '2016-03-28 05:43:57.779528'::TIMESTAMP
         ORDER BY maker_date DESC LIMIT 5)
      UNION
        (SELECT tags,
                id,
                maker_date
         FROM tags_tmp
         WHERE 'c' = any(tags)
           AND maker_date <= '2016-03-28 05:43:57.779528'::TIMESTAMP
         ORDER BY maker_date DESC LIMIT 5)) rs
ORDER BY rs.maker_date DESC LIMIT 5 ;

1
Tôi đã đấu tranh với vấn đề tương tự cho ngày và tôi đã tìm thấy các giải pháp đơn giản nhất là nên có một b-tree với nhiều phím cho mỗi bản ghi: ví dụ như b-tree sẽ có a:2016-11-09, b:2016-11-09, c:2016-11-09 như các nút cây và tất cả trong số họ bao gồm một con trỏ để hàng #1. MongoDB thực sự hỗ trợ các chỉ mục multikey ghép ... Thật không may, PostgreSQL không có, và điều này rất khó chịu. Bạn sẽ phải tạo một bảng riêng biệt id_ref | tag | date để tạo một cây b tương tự.
collimarco

Câu trả lời:


5

Xem xét chung

Tối ưu hóa chỉ số luôn phụ thuộc vào bức tranh hoàn chỉnh . Kích thước bảng, kích thước hàng, số lượng, tần số giá trị, độ chọn lọc của các truy vấn điển hình, phiên bản Postgres, mẫu truy cập điển hình, v.v.

Trường hợp của bạn đặc biệt khó khăn vì hai lý do:

  1. Các cột khác nhau được sử dụng trong WHEREORDER BY.
  2. Bộ lọc trên mảng hiệu quả nhất với chỉ mục GIN hoặc GiST, nhưng không loại chỉ mục nào tạo ra đầu ra được sắp xếp. Hướng dẫn sử dụng:

    Trong số các loại chỉ mục hiện đang được PostgreSQL hỗ trợ, chỉ cây B có thể tạo đầu ra được sắp xếp - các loại chỉ mục khác trả về các hàng khớp với thứ tự không xác định, phụ thuộc vào việc triển khai.

Bạn có thể tạo chỉ mục GIN nhiều màu trên (tags, maker_date)hoặc thậm chí nhiều cột hơn (thứ tự các cột chỉ mục không liên quan đến chỉ mục GIN). Nhưng bạn cần phải btree_gincài đặt mô-đun bổ sung . Hướng dẫn:

Và nó sẽ không giúp gì cho ORDER BYthành phần của vấn đề của bạn.

Thêm một điều rõ ràng: OFFSET m LIMIT nthường đắt gần như LIMIT m+n.

Giải pháp cho thông số kỹ thuật được thêm vào

Bạn đã làm rõ: chỉ có 6 thẻ riêng biệt có thể. Điều đó rất quan trọng.

Bảng của bạn là lớn và định nghĩa bảng của bạn để lại những cải tiến. Kích thước quan trọng cho các bảng lớn. Số của bạn (30 GB, 10 triệu hàng) cũng gợi ý một avg lớn. kích thước hàng ~ 3 KB. Hoặc bạn có nhiều cột hơn bạn hiển thị hoặc phình bảng và cần VACUUM FULLchạy (hoặc tương tự) hoặc valuecột của bạn lớn và HẤP DẪN, điều này sẽ giúp cải thiện của tôi thậm chí hiệu quả hơn vì mối quan hệ chính được giảm xuống một nửa hoặc ít hơn với điều này :

CREATE TABLE tags_tmp (
  id         int PRIMARY KEY -- assuming PK
, tags       int NOT NULL    -- also assuming NOT NULL
, value      text
, maker_date timestamp NOT NULL  -- NOT NULL!
);

Thứ tự của các cột có liên quan vì đệm liên kết. Chi tiết:

Quan trọng hơn, điều này : tags int. Tại sao?

Mảng có tổng chi phí khá lớn là 24 byte (tương tự như một hàng), cộng với các mục thực tế.

Vì vậy, text[]với 1-6 mục như bạn thể hiện ('hài hước', 'cảm hứng', ...) chiếm ~ 56 byte trên avg . Và 6 giá trị riêng biệt có thể được biểu thị chỉ bằng 6 bit thông tin (giả sử rằng thứ tự sắp xếp của mảng là không liên quan). Chúng tôi có thể nén nhiều hơn, nhưng tôi đã chọn integerloại thuận tiện (chiếm 4 byte ), cung cấp không gian cho tối đa 31 thẻ khác nhau. Điều đó sẽ chừa chỗ cho các bổ sung sau này mà không thay đổi lược đồ bảng. Lý do chi tiết:

Thẻ của bạn ánh xạ tới các bit trong một bitmap, 'a'là bit có trọng số thấp nhất (bên phải):

tag:       a | b | c | d |  e |  f
position:  0 | 1 | 2 | 3 |  4 |  5
int value: 1 | 2 | 4 | 8 | 16 | 32

Vì vậy, các mảng thẻ '{a,d,f}'ánh xạ tới 41. Bạn có thể sử dụng bất kỳ chuỗi tùy ý nào thay vì 'a' - 'f', không thành vấn đề.

Để gói gọn logic tôi đề xuất hai hàm phụ trợ , có thể dễ dàng mở rộng:

thẻ -> số nguyên:

CREATE OR REPLACE FUNCTION f_tags2int(text[])
  RETURNS int AS
$func$
SELECT bit_or(CASE x
            WHEN 'a' THEN  1
            WHEN 'b' THEN  2
            WHEN 'c' THEN  4
            WHEN 'd' THEN  8
            WHEN 'e' THEN 16
            WHEN 'f' THEN 32
            -- more?
           END)
FROM    unnest ($1) x
$func$  LANGUAGE SQL IMMUTABLE;

số nguyên -> thẻ:

CREATE OR REPLACE FUNCTION f_int2tags(int)
  RETURNS text[] AS
$func$
SELECT array_remove(ARRAY [CASE WHEN $1 &  1 > 0 THEN 'a' END
                         , CASE WHEN $1 &  2 > 0 THEN 'b' END
                         , CASE WHEN $1 &  4 > 0 THEN 'c' END
                         , CASE WHEN $1 &  8 > 0 THEN 'd' END
                         , CASE WHEN $1 & 16 > 0 THEN 'e' END
                         , CASE WHEN $1 & 32 > 0 THEN 'f' END], NULL)
                         -- more? 
$func$  LANGUAGE SQL IMMUTABLE;

Khái niệm cơ bản ở đây:

Để thuận tiện, bạn có thể thêm chế độ xem để hiển thị thẻ dưới dạng mảng văn bản như bạn đã có:

CREATE VIEW tags_tmp_pretty AS
SELECT id, tags
     , f_int2tags(tags) AS tags_pretty
     , maker_date, value
FROM   tags_tmp;

Bây giờ truy vấn cơ bản của bạn có thể là:

SELECT id, tags, maker_date, value
FROM   tags_tmp
WHERE  tags & f_tags2int('{a,b}') > 0  -- any of the tags matched
ORDER  by maker_date DESC
LIMIT  5;

Sử dụng toán tử AND nhị phân& . Có nhiều toán tử để thao tác cột. get_bit()set_bit()từ các toán tử chuỗi nhị phân cũng thuận tiện.

Truy vấn trên phải nhanh hơn, chỉ với các toán tử kích thước nhỏ hơn và rẻ hơn, nhưng chưa có gì mang tính cách mạng. Để làm cho nó nhanh, chúng ta cần các chỉ số và ở trên không thể sử dụng một chỉ mục.

Một chỉ mục một phần cho mỗi thẻ:

CREATE INDEX foo_tag_a ON tags_tmp(maker_date DESC) WHERE tags & 1 > 0;
CREATE INDEX foo_tag_b ON tags_tmp(maker_date DESC) WHERE tags & 2 > 0;
...
CREATE INDEX foo_tag_f ON tags_tmp(maker_date DESC) WHERE tags & 32 > 0;

Truy vấn này tương đương với ở trên, nhưng có thể sử dụng các chỉ mục:

SELECT *
FROM   tags_tmp_pretty
WHERE (tags & f_tags2int('{a}') > 0   -- same as tags & 1
    OR tags & f_tags2int('{e}') > 0)  -- same as tags & 32
ORDER  BY maker_date DESC
LIMIT  10;

Postgres có thể kết hợp một số lần quét chỉ mục bitmap trong một BitmapOrbước rất hiệu quả - như đã trình bày trong SQL Fiddle này .

Bạn có thể thêm một điều kiện chỉ mục khác để giới hạn chỉ mục ở maker_date> một số dấu thời gian không đổi (và lặp lại điều kiện nguyên văn trong các truy vấn) để cắt giảm kích thước của chúng (ồ ạt). Ví dụ liên quan:

Tinh vi hơn:

Các câu trả lời liên quan khác:

Hoặc chỉ 6 booleancột ...

Đơn giản chỉ cần 6 cột boolean có thể là một lựa chọn thậm chí tốt hơn. Có một số ưu và nhược điểm đối với một trong hai giải pháp ...

CREATE TABLE tags_tmp (
  id         int PRIMARY KEY -- assuming PK
, tag_a      bool 
, tag_b      bool 
  ...
, tag_f      bool 
, value      text
, maker_date timestamp NOT NULL  -- NOT NULL!
);

Bạn có thể xác định các cờ NOT NULL, tùy thuộc vào trường hợp sử dụng hoàn chỉnh của bạn.

Xem xét:

Chỉ mục một phần đơn giản:

CREATE INDEX foo_tag_a ON tags_tmp(maker_date DESC) WHERE tag_a;
CREATE INDEX foo_tag_b ON tags_tmp(maker_date DESC) WHERE tag_b;

Vân vân.

Thay thế cho trường hợp đặc biệt của bạn

Suy nghĩ thêm, vì tất cả các thẻ của bạn rất phổ biến và việc kết hợp nhiều thẻ với OR thậm chí ít được chọn hơn, sẽ nhanh nhất là chỉ có chỉ số btree trên maker_date DESC. Postgres có thể duyệt qua chỉ mục và lọc các hàng đủ điều kiện trên thẻ. Điều này sẽ hoạt động kết hợp với các cột boolean riêng thay vì mảng hoặc số nguyên được mã hóa, bởi vì Postgres có số liệu thống kê cột hữu ích hơn cho các cột riêng biệt.

CREATE INDEX tags_tmp_date ON tags_tmp(maker_date DESC);

Và sau đó:

SELECT *
FROM   tags_tmp_pretty
WHERE  tag_a
   OR  tag_b
ORDER  BY maker_date DESC
LIMIT  10;

Phân trang

Bạn cần một thứ tự sắp xếp rõ ràng cho các tập kết quả, để làm việc phân trang. Tôi không bận tâm trong câu trả lời này, đã quá lâu rồi. Thông thường, bạn sẽ thêm nhiều cột vào ORDER BY. Làm thế nào để phân trang hoạt động hiệu quả với điều đó:


1

Nhiều vấn đề với trường hợp thử nghiệm của bạn:

  1. idint8bây giờ. Bạn đã tuyên bố nó như inttrong câu hỏi ban đầu của bạn Không phải là một sự khác biệt lớn, nhưng tại sao sự nhầm lẫn bắt đầu với? Nó quan trọng đối với kích thước hàng và đệm liên kết. Hãy nhớ khai báo định nghĩa bảng thực tế, chính xác và đầy đủ của bạn trong các câu hỏi.

  2. Phân phối dữ liệu trong dữ liệu thử nghiệm của bạn là không thực tế. Bạn chỉ có 6 kết hợp thẻ riêng biệt và * tất cả các hàng đều có thẻ '1'. Tôi giả sử, bạn có tất cả 63 kết hợp có thể có trong bảng trực tiếp của mình, với các thẻ được phân phối như bạn đã thêm trong câu hỏi.

  3. Bảng kiểm tra của bạn bao gồm các cột thẻ cũ mới, điều này phủ nhận ảnh hưởng đến kích thước lưu trữ mà tôi có sau đó. Bây giờ kích thước hàng thậm chí còn lớn hơn. Kích thước hàng của bạn là 124 - 164 byte, so với chỉ 68 byte trong thử nghiệm của tôi (bao gồm định danh đệm & mục). Hơn hai lần kích thước làm cho một sự khác biệt.

  4. Bạn viết kích thước = 4163 MB . Cỡ nào?

  5. Bạn có order by random()dữ liệu thử nghiệm. Là bảng năng suất của bạn thực sự là ngẫu nhiên? Thông thường, bạn sẽ có dữ liệu được sắp xếp theo dấu thời gian. Tình hình thực tế của bạn là gì?

  6. Để xem gói nào sẽ được chọn, chỉ kiểm tra EXPLAINđể xem gói truy vấn trước khi thực sự chạy truy vấn. Tiết kiệm rất nhiều thời gian với bàn lớn. Nhưng luôn luôn cung cấp đầu ra của EXPLAIN (ANALYZE, BUFFERS)đây. Trong câu trả lời của bạn (trái ngược với câu hỏi), cost=ước tính bị thiếu. Điều đó làm cho nó khó đoán vấn đề.

Nhưng không có vấn đề nào trong số này có thể giải thích tại sao bạn thấy quét tuần tự ngay cả với enable_seqscan = off; Một thử nghiệm nhanh trên máy tính xách tay của tôi với Postgres 9.5 hoạt động . Điều tương tự cũng đúng với pg 9.6.

CREATE TABLE tags_tmp(
   id         bigserial PRIMARY KEY, 
   maker_date timestamp NOT NULL,
   tags       int NOT NULL,
   value      text
);

INSERT INTO tags_tmp (tags, maker_date, value)
SELECT EXTRACT('minute' FROM ts)::int    -- int between 1 and 60 (no 61,62,63), pretty good.
     , ts + random() * interval '5 min'  -- some limited randomness
     , 'This is test on ' || EXTRACT('minute' FROM ts)
FROM   generate_series(timestamp '2016-01-01 00:00'
                     , timestamp '2016-01-13 00:00', '10 second') ts;
-- 103681 rows affected, 836 msec execution time.

-- create adapted function f_tags2int
-- create adapted function f_int2tags

CREATE INDEX tags_tmp_1 ON tags_tmp(maker_date DESC) WHERE tags &  1 > 0;
CREATE INDEX tags_tmp_2 ON tags_tmp(maker_date DESC) WHERE tags &  2 > 0;
CREATE INDEX tags_tmp_3 ON tags_tmp(maker_date DESC) WHERE tags &  4 > 0;
CREATE INDEX tags_tmp_4 ON tags_tmp(maker_date DESC) WHERE tags &  8 > 0;
CREATE INDEX tags_tmp_5 ON tags_tmp(maker_date DESC) WHERE tags & 16 > 0;
CREATE INDEX tags_tmp_6 ON tags_tmp(maker_date DESC) WHERE tags & 32 > 0;

SELECT id, tags, maker_date, value
FROM   tags_tmp
WHERE (tags & f_tags2int(array['5']) > 0 OR
       tags & f_tags2int(array['6']) > 0)
ORDER  BY maker_date DESC
LIMIT  5;
QUERY PLAN
Limit  (cost=3811.93..3811.94 rows=5 width=38) (actual time=46.586..46.586 rows=5 loops=1)
  Buffers: shared hit=1132
  ->  Sort  (cost=3811.93..3955.93 rows=57601 width=38) (actual time=46.584..46.585 rows=5 loops=1)
        Sort Key: maker_date DESC
        Sort Method: top-N heapsort  Memory: 25kB
        Buffers: shared hit=1132
        ->  Bitmap Heap Scan on tags_tmp  (cost=607.78..2855.20 rows=57601 width=38) (actual time=13.699..27.674 rows=76032 loops=1)
              Recheck Cond: (((tags & 16) > 0) OR ((tags & 32) > 0))
              Heap Blocks: exact=864
              Buffers: shared hit=1132
              ->  BitmapOr  (cost=607.78..607.78 rows=69121 width=0) (actual time=13.549..13.549 rows=0 loops=1)
                    Buffers: shared hit=268
                    ->  Bitmap Index Scan on tags_tmp_5 cost=0.00..289.49 rows=34560 width=0) (actual time=8.745..8.745 rows=48384 loops=1)
                          Buffers: shared hit=134
                    ->  Bitmap Index Scan on tags_tmp_6 (cost=0.00..289.49 rows=34560 width=0) (actual time=4.800..4.800 rows=48384 loops=1)
                          Buffers: shared hit=134
Planning time: 3.976 ms
Execution time: 46.653 ms

Giống như tôi đã chứng minh trong SQL Fiddle .

Bạn có chắc chắn rằng bạn đã tạo tất cả các chỉ mục đúng cách?

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.