Tại sao một chỉ số gin trên cột jsonb làm chậm truy vấn của tôi và tôi có thể làm gì về nó?


10

Khởi tạo dữ liệu thử nghiệm:

CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE TABLE docs (data JSONB NOT NULL DEFAULT '{}');
-- generate 200k documents, ~half with type: "type1" and another half with type: "type2", unique incremented index and random uuid per each row
INSERT INTO docs (data)
SELECT json_build_object('id', gen_random_uuid(), 'type', (CASE WHEN random() > 0.5 THEN 'type1' ELSE 'type2' END) ,'index', n)::JSONB
FROM generate_series(1, 200000) n;
-- inset one more row with explicit uuid to query by it later
INSERT INTO docs (data) VALUES (json_build_object('id', '30e84646-c5c5-492d-b7f7-c884d77d1e0a', 'type', 'type1' ,'index', 200001)::JSONB);

Truy vấn đầu tiên - lọc theo dữ liệu-> loại và giới hạn:

-- FAST ~19ms
EXPLAIN ANALYZE
SELECT * FROM docs
WHERE data @> '{"type": "type1"}'::JSONB
LIMIT 25;
/* "Limit  (cost=0.00..697.12 rows=25 width=90) (actual time=0.029..0.070 rows=25 loops=1)"
   "  ->  Seq Scan on docs  (cost=0.00..5577.00 rows=200 width=90) (actual time=0.028..0.061 rows=25 loops=1)"
   "        Filter: (data @> '{"type": "type1"}'::jsonb)"
   "        Rows Removed by Filter: 17"
   "Planning time: 0.069 ms"
   "Execution time: 0.098 ms" 
*/

Truy vấn thứ hai - lọc theo dữ liệu-> loại, thứ tự theo dữ liệu-> chỉ mục và giới hạn

-- SLOW ~250ms
EXPLAIN ANALYZE
SELECT * FROM docs
WHERE data @> '{"type": "type1"}'::JSONB
ORDER BY data->'index' -- added ORDER BY
LIMIT 25;

/* "Limit  (cost=5583.14..5583.21 rows=25 width=90) (actual time=236.750..236.754 rows=25 loops=1)"
   "  ->  Sort  (cost=5583.14..5583.64 rows=200 width=90) (actual time=236.750..236.750 rows=25 loops=1)"
   "        Sort Key: ((data -> 'index'::text))"
   "        Sort Method: top-N heapsort  Memory: 28kB"
   "        ->  Seq Scan on docs  (cost=0.00..5577.50 rows=200 width=90) (actual time=0.020..170.797 rows=100158 loops=1)"
   "              Filter: (data @> '{"type": "type1"}'::jsonb)"
   "              Rows Removed by Filter: 99842"
   "Planning time: 0.075 ms"
   "Execution time: 236.785 ms"
*/

Truy vấn thứ ba - giống như Thứ hai (trước đó) nhưng với chỉ mục btree trên dữ liệu-> chỉ mục:

CREATE INDEX docs_data_index_idx ON docs ((data->'index'));

-- FAST ~19ms
EXPLAIN ANALYZE
SELECT * FROM docs
WHERE data @> '{"type": "type1"}'::JSONB
ORDER BY data->'index' -- added BTREE index on this field
LIMIT 25;
/* "Limit  (cost=0.42..2473.98 rows=25 width=90) (actual time=0.040..0.125 rows=25 loops=1)"
   "  ->  Index Scan using docs_data_index_idx on docs  (cost=0.42..19788.92 rows=200 width=90) (actual time=0.038..0.119 rows=25 loops=1)"
   "        Filter: (data @> '{"type": "type1"}'::jsonb)"
   "        Rows Removed by Filter: 17"
   "Planning time: 0.127 ms"
   "Execution time: 0.159 ms"
*/

Truy vấn thứ tư - hiện lọc theo dữ liệu-> id và giới hạn = 1:

-- SLOW ~116ms
EXPLAIN ANALYZE
SELECT * FROM docs
WHERE data @> ('{"id": "30e84646-c5c5-492d-b7f7-c884d77d1e0a"}')::JSONB -- querying by "id" field now
LIMIT 1;
/* "Limit  (cost=0.00..27.89 rows=1 width=90) (actual time=97.990..97.990 rows=1 loops=1)"
   "  ->  Seq Scan on docs  (cost=0.00..5577.00 rows=200 width=90) (actual time=97.989..97.989 rows=1 loops=1)"
   "        Filter: (data @> '{"id": "30e84646-c5c5-492d-b7f7-c884d77d1e0a"}'::jsonb)"
   "        Rows Removed by Filter: 189999"
   "Planning time: 0.064 ms"
   "Execution time: 98.012 ms"
*/ 

Truy vấn thứ năm - giống như chỉ mục thứ tư nhưng có chỉ số gin (json_path_ops) trên dữ liệu:

CREATE INDEX docs_data_idx ON docs USING GIN (data jsonb_path_ops);

-- FAST ~17ms
EXPLAIN ANALYZE
SELECT * FROM docs
WHERE data @> '{"id": "30e84646-c5c5-492d-b7f7-c884d77d1e0a"}'::JSONB -- added gin index with json_path_ops
LIMIT 1;
/* "Limit  (cost=17.55..20.71 rows=1 width=90) (actual time=0.027..0.027 rows=1 loops=1)"
   "  ->  Bitmap Heap Scan on docs  (cost=17.55..649.91 rows=200 width=90) (actual time=0.026..0.026 rows=1 loops=1)"
   "        Recheck Cond: (data @> '{"id": "30e84646-c5c5-492d-b7f7-c884d77d1e0a"}'::jsonb)"
   "        Heap Blocks: exact=1"
   "        ->  Bitmap Index Scan on docs_data_idx  (cost=0.00..17.50 rows=200 width=0) (actual time=0.016..0.016 rows=1 loops=1)"
   "              Index Cond: (data @> '{"id": "30e84646-c5c5-492d-b7f7-c884d77d1e0a"}'::jsonb)"
   "Planning time: 0.095 ms"
   "Execution time: 0.055 ms"
*/

Truy vấn thứ sáu (và cuối cùng) - giống như truy vấn thứ ba (truy vấn theo dữ liệu-> loại, thứ tự theo dữ liệu-> chỉ mục, giới hạn):

-- SLOW AGAIN! ~224ms
EXPLAIN ANALYZE
SELECT * FROM docs
WHERE data @> '{"type": "type1"}'::JSONB
ORDER BY data->'index'
LIMIT 25;
/* "Limit  (cost=656.06..656.12 rows=25 width=90) (actual time=215.927..215.932 rows=25 loops=1)"
   "  ->  Sort  (cost=656.06..656.56 rows=200 width=90) (actual time=215.925..215.925 rows=25 loops=1)"
   "        Sort Key: ((data -> 'index'::text))"
   "        Sort Method: top-N heapsort  Memory: 28kB"
   "        ->  Bitmap Heap Scan on docs  (cost=17.55..650.41 rows=200 width=90) (actual time=33.134..152.618 rows=100158 loops=1)"
   "              Recheck Cond: (data @> '{"type": "type1"}'::jsonb)"
   "              Heap Blocks: exact=3077"
   "              ->  Bitmap Index Scan on docs_data_idx  (cost=0.00..17.50 rows=200 width=0) (actual time=32.468..32.468 rows=100158 loops=1)"
   "                    Index Cond: (data @> '{"type": "type1"}'::jsonb)"
   "Planning time: 0.157 ms"
   "Execution time: 215.992 ms"
*/

Vì vậy, có vẻ như truy vấn Sixth (giống như Thứ ba) chậm hơn nhiều khi có chỉ số gin trên cột dữ liệu. Có lẽ vì không có nhiều giá trị riêng biệt cho trường dữ liệu-> loại (chỉ "type1" hoặc "type2")? Tôi có thể làm gì về nó? Tôi cần chỉ số gin để thực hiện các truy vấn khác có lợi ích của nó ...

Câu trả lời:


5

Có vẻ như bạn đã gặp phải vấn đề là jsonbcác cột có tỷ lệ thống kê 1% không đổi, như được báo cáo ở đây Làm việc xung quanh việc thiếu số liệu thống kê của jsonb? . Nhìn vào các kế hoạch truy vấn của bạn, sự khác biệt giữa các ước tính và thực thi thực tế là rất lớn. Các ước tính cho biết có thể có 200 hàng và 100158 hàng trả lại thực tế, điều này khiến người lập kế hoạch ủng hộ các chiến lược nhất định hơn các chiến lược khác.

Vì sự lựa chọn trong truy vấn thứ sáu dường như có lợi cho việc quét chỉ mục bitmap qua quét chỉ mục, bạn có thể yêu cầu người lập kế hoạch cùng với SET enable_bitmapscan=offthử và làm cho nó trở lại hành vi bạn có trong ví dụ thứ ba.

Đây là cách nó làm việc với tôi:

postgres@[local]:5432:postgres:=# EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM docs
WHERE data @> '{"type": "type1"}'::JSONB
ORDER BY data->'index'
LIMIT 25;
                                                                QUERY PLAN                                                                 
-------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=656.06..656.12 rows=25 width=90) (actual time=117.338..117.343 rows=25 loops=1)
   Buffers: shared hit=3096
   ->  Sort  (cost=656.06..656.56 rows=200 width=90) (actual time=117.336..117.338 rows=25 loops=1)
         Sort Key: ((data -> 'index'::text))
         Sort Method: top-N heapsort  Memory: 28kB
         Buffers: shared hit=3096
         ->  Bitmap Heap Scan on docs  (cost=17.55..650.41 rows=200 width=90) (actual time=12.838..80.584 rows=99973 loops=1)
               Recheck Cond: (data @> '{"type": "type1"}'::jsonb)
               Heap Blocks: exact=3077
               Buffers: shared hit=3096
               ->  Bitmap Index Scan on docs_data_idx  (cost=0.00..17.50 rows=200 width=0) (actual time=12.469..12.469 rows=99973 loops=1)
                     Index Cond: (data @> '{"type": "type1"}'::jsonb)
                     Buffers: shared hit=19
 Planning time: 0.088 ms
 Execution time: 117.405 ms
(15 rows)

Time: 117.813 ms
postgres@[local]:5432:postgres:=# SET enable_bitmapscan = off;
SET
Time: 0.130 ms
postgres@[local]:5432:postgres:=# EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM docs
WHERE data @> '{"type": "type1"}'::JSONB
ORDER BY data->'index'
LIMIT 25;
                                                               QUERY PLAN                                                               
----------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.42..1320.48 rows=25 width=90) (actual time=0.017..0.050 rows=25 loops=1)
   Buffers: shared hit=4
   ->  Index Scan using docs_data_index_idx on docs  (cost=0.42..10560.94 rows=200 width=90) (actual time=0.015..0.045 rows=25 loops=1)
         Filter: (data @> '{"type": "type1"}'::jsonb)
         Rows Removed by Filter: 27
         Buffers: shared hit=4
 Planning time: 0.083 ms
 Execution time: 0.071 ms
(8 rows)

Time: 0.402 ms
postgres@[local]:5432:postgres:=#

Nếu bạn đang tìm cách đi theo tuyến đường này, hãy chắc chắn chỉ tắt chức năng quét đó đối với các truy vấn hiển thị hành vi như thế này, nếu không, bạn cũng sẽ có hành vi xấu đối với các gói truy vấn khác. Làm một cái gì đó như thế này sẽ làm việc tốt:

BEGIN;
SET enable_bitmapscan=off;
SELECT * FROM docs
WHERE data @> '{"type": "type1"}'::JSONB
ORDER BY data->'index'
LIMIT 25;
SET enable_bitmapscan=on;
COMMIT;

Mong rằng sẽ giúp =)


Tôi không chắc là tôi có hiểu đúng về bạn không (tôi không quen thuộc với PG bên trong) - hành vi này được gây ra bởi số lượng thẻ thấp trên trường "loại" trong cột jsonb (và gây ra bởi tỷ lệ thống kê phẳng), phải không? Và điều đó cũng có nghĩa là, nếu tôi muốn tối ưu hóa truy vấn của mình, tôi phải biết tính chính xác của trường jsonb Tôi đang truy vấn để quyết định xem tôi có nên enable_bitmapscan hay không, phải không?
dùng606521

1
Vâng, bạn dường như hiểu điều này trên cả hai tính. Độ chọn lọc cơ bản 1% đang ủng hộ việc xem xét trường trong WHEREmệnh đề trong chỉ mục gin bởi vì nó tin rằng nó sẽ trả về ít hàng hơn, điều này không đúng. Vì bạn có thể ước tính số lượng hàng tốt hơn, bạn có thể thấy rằng, vì bạn đang thực hiện ORDER BY data->'index' LIMIT 25, việc quét một vài mục đầu tiên của chỉ mục khác (50 hoặc hơn, với các hàng bị ném đi) sẽ dẫn đến số lượng hàng ít hơn, do đó, việc nói trình lập kế hoạch thực sự không nên cố gắng sử dụng kết quả bitmapscan trong kế hoạch truy vấn nhanh hơn đang được sử dụng. Hi vọng mọi thứ đều bị xóa sạch. =)
Kassandry

1
Có thêm thông tin làm rõ ở đây: cơ sở dữ liệu.com / 2015/01 / tag-all-things-part -.html và trong bài trình bày này, thebuild.com/presentations/json2015-pgconfus.pdf cũng sẽ giúp đỡ.
Kassandry

1
Công việc duy nhất tôi biết là từ Oleg Bartunov, Tedor Sigaev và Alexander Kotorov trên phần mở rộng JsQuery và các cải tiến chọn lọc của nó. Với bất kỳ may mắn nào, nó biến nó thành lõi PostgreSQL trong 9.6 trở lên.
Kassandry

1
Tôi đã trích dẫn con số 1% từ email trong câu trả lời của tôi từ Josh Berkus, một thành viên của Nhóm cốt lõi PostgreQuery. Điều đó xuất phát từ đòi hỏi một sự hiểu biết sâu sắc hơn nhiều về nội bộ so với hiện tại tôi đang sở hữu, xin lỗi. = (Bạn có thể thử trả lời pgsql-performance@postgresql.orghoặc kiểm tra trên IRC freenode #postgresqlxem chính xác con số đó đến từ đâu.
Kassandry
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.