PostgreSQL
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> PostgreSQL

Về tính hữu dụng của các chỉ mục biểu thức

Khi giảng dạy các khóa đào tạo về PostgreSQL, cả về chủ đề cơ bản và nâng cao, tôi thường thấy rằng những người tham dự có rất ít ý tưởng về mức độ mạnh mẽ của các chỉ mục biểu thức (nếu họ có biết về chúng). Vì vậy, hãy để tôi cung cấp cho bạn một cái nhìn tổng quan ngắn gọn.

Vì vậy, giả sử chúng ta có một bảng, với một loạt các dấu thời gian (vâng, chúng ta có hàm create_series có thể tạo ngày tháng):

CREATE TABLE t AS
SELECT d, repeat(md5(d::text), 10) AS padding
  FROM generate_series(timestamp '1900-01-01',
                       timestamp '2100-01-01',
                       interval '1 day') s(d);
VACUUM ANALYZE t;

Bảng cũng bao gồm một cột đệm, để làm cho nó lớn hơn một chút. Bây giờ, hãy thực hiện một truy vấn phạm vi đơn giản, chỉ chọn một tháng từ ~ 200 năm có trong bảng. Nếu bạn giải thích về truy vấn, bạn sẽ thấy một cái gì đó như thế này:

EXPLAIN SELECT * FROM t WHERE d BETWEEN '2001-01-01' AND '2001-02-01';

                               QUERY PLAN
------------------------------------------------------------------------
 Seq Scan on t  (cost=0.00..4416.75 rows=32 width=332)
   Filter: ((d >= '2001-01-01 00:00:00'::timestamp without time zone)
        AND (d <= '2001-02-01 00:00:00'::timestamp without time zone))
(2 rows)

và trên máy tính xách tay của tôi, điều này chạy trong ~ 20ms. Không tệ, xem ra điều này phải đi qua toàn bộ bảng với ~ 75 nghìn hàng.

Nhưng hãy tạo chỉ mục trên cột dấu thời gian (tất cả các chỉ mục ở đây là loại mặc định, tức là btree, trừ khi được đề cập rõ ràng):

CREATE INDEX idx_t_d ON t (d);

Và bây giờ chúng ta hãy thử chạy lại truy vấn:

                               QUERY PLAN
------------------------------------------------------------------------
 Index Scan using idx_t_d on t  (cost=0.29..9.97 rows=34 width=332)
   Index Cond: ((d >= '2001-01-01 00:00:00'::timestamp without time zone)
            AND (d <= '2001-02-01 00:00:00'::timestamp without time zone))
(2 rows)

và điều này chạy trong 0,5 mili giây, nhanh hơn khoảng 40 lần. Nhưng đó tất nhiên là một chỉ mục đơn giản, được tạo trực tiếp trên cột, không phải chỉ mục biểu thức. Vì vậy, giả sử thay vào đó chúng ta cần chọn dữ liệu từ mỗi ngày đầu tiên của mỗi tháng, thực hiện một truy vấn như thế này

SELECT * FROM t WHERE EXTRACT(day FROM d) = 1;

Tuy nhiên, không thể sử dụng chỉ mục, vì nó cần đánh giá một biểu thức trên cột trong khi chỉ mục được tạo trên chính cột đó, như được hiển thị trên PHÂN TÍCH GIẢI THÍCH:

                               QUERY PLAN
------------------------------------------------------------------------
 Seq Scan on t  (cost=0.00..4416.75 rows=365 width=332)
                (actual time=0.045..40.601 rows=2401 loops=1)
   Filter: (date_part('day'::text, d) = '1'::double precision)
   Rows Removed by Filter: 70649
 Planning time: 0.209 ms
 Execution time: 43.018 ms
(5 rows)

Vì vậy, điều này không chỉ phải thực hiện quét tuần tự mà còn phải thực hiện đánh giá, tăng thời lượng truy vấn lên 43 mili giây.

Cơ sở dữ liệu không thể sử dụng chỉ mục vì nhiều lý do. Các chỉ mục (ít nhất là chỉ mục btree) dựa vào việc truy vấn dữ liệu được sắp xếp, được cung cấp bởi cấu trúc dạng cây và trong khi truy vấn phạm vi có thể được hưởng lợi từ điều đó, thì truy vấn thứ hai (với lệnh gọi `extract`) lại không thể.

Lưu ý:Một vấn đề khác là tập hợp các toán tử được hỗ trợ bởi các chỉ mục (tức là có thể được đánh giá trực tiếp trên các chỉ mục) là rất hạn chế. Và chức năng "trích xuất" không được hỗ trợ, vì vậy truy vấn không thể giải quyết vấn đề sắp xếp bằng cách sử dụng Quét chỉ mục bitmap.

Về lý thuyết, cơ sở dữ liệu có thể cố gắng chuyển đổi điều kiện thành điều kiện phạm vi, nhưng điều đó cực kỳ khó và cụ thể đối với biểu thức. Trong trường hợp này, chúng tôi phải tạo vô số phạm vi "mỗi ngày" như vậy, bởi vì người lập kế hoạch không thực sự biết dấu thời gian tối thiểu / tối đa trong bảng. Vì vậy, cơ sở dữ liệu thậm chí không thử.

Nhưng trong khi cơ sở dữ liệu không biết cách biến đổi các điều kiện, các nhà phát triển thường làm. Ví dụ với các điều kiện như

(column + 1) >= 1000

không khó để viết lại nó như thế này

column >= (1000 - 1)

hoạt động tốt với các chỉ mục.

Nhưng điều gì sẽ xảy ra nếu chuyển đổi như vậy không thể thực hiện được, chẳng hạn như đối với truy vấn mẫu

SELECT * FROM t WHERE EXTRACT(day FROM d) = 1;

Trong trường hợp này, nhà phát triển sẽ phải đối mặt với vấn đề tương tự với tối thiểu / tối đa không xác định cho cột d và thậm chí sau đó nó sẽ tạo ra rất nhiều phạm vi.

Chà, bài đăng trên blog này nói về chỉ mục biểu thức và cho đến nay chúng tôi chỉ sử dụng các chỉ mục thông thường, được xây dựng trực tiếp trên cột. Vì vậy, hãy tạo chỉ mục biểu thức đầu tiên:

CREATE INDEX idx_t_expr ON t ((extract(day FROM d)));
ANALYZE t;

sau đó cung cấp cho chúng tôi kế hoạch giải thích này

                               QUERY PLAN
------------------------------------------------------------------------
 Bitmap Heap Scan on t  (cost=47.35..3305.25 rows=2459 width=332)
                        (actual time=2.400..12.539 rows=2401 loops=1)
   Recheck Cond: (date_part('day'::text, d) = '1'::double precision)
   Heap Blocks: exact=2401
   ->  Bitmap Index Scan on idx_t_expr  (cost=0.00..46.73 rows=2459 width=0)
                                (actual time=1.243..1.243 rows=2401 loops=1)
         Index Cond: (date_part('day'::text, d) = '1'::double precision)
 Planning time: 0.374 ms
 Execution time: 17.136 ms
(7 rows)

Vì vậy, mặc dù điều này không cung cấp cho chúng ta tốc độ tăng gấp 40 lần như chỉ mục trong ví dụ đầu tiên, nhưng điều đó tương tự như mong đợi khi truy vấn này trả về nhiều bộ giá trị hơn (2401 so với 32). Hơn nữa, những thứ đó được trải qua toàn bộ bảng và không được bản địa hóa như trong ví dụ đầu tiên. Vì vậy, đó là một tốc độ tăng gấp đôi tuyệt vời và trong nhiều trường hợp thực tế, bạn sẽ thấy những cải tiến lớn hơn nhiều.

Nhưng khả năng sử dụng chỉ mục cho các điều kiện có biểu thức phức tạp không phải là thông tin thú vị nhất ở đây - đó là lý do tại sao mọi người tạo chỉ mục biểu thức. Nhưng đó không phải là lợi ích duy nhất.

Nếu bạn nhìn vào hai kế hoạch giải thích được trình bày ở trên (không có và có chỉ mục biểu thức), bạn có thể nhận thấy điều này:

                               QUERY PLAN
------------------------------------------------------------------------
 Seq Scan on t  (cost=0.00..4416.75 rows=365 width=332)
                (actual time=0.045..40.601 rows=2401 loops=1)
 ...
                               QUERY PLAN
------------------------------------------------------------------------
 Bitmap Heap Scan on t  (cost=47.35..3305.25 rows=2459 width=332)
                        (actual time=2.400..12.539 rows=2401 loops=1)
 ...

Đúng - tạo chỉ mục biểu thức đã cải thiện đáng kể các ước tính. Nếu không có chỉ mục, chúng ta chỉ có thống kê (MCV + biểu đồ) cho các cột trong bảng thô, do đó, cơ sở dữ liệu không biết cách ước tính biểu thức

EXTRACT(day FROM d) = 1

Vì vậy, thay vào đó, nó áp dụng ước tính mặc định cho các điều kiện bình đẳng, là 0,5% của tất cả các hàng - vì bảng có 73050 hàng, chúng tôi kết thúc với ước tính chỉ có 365 hàng. Chúng ta thường thấy các lỗi ước tính tồi tệ hơn nhiều trong các ứng dụng trong thế giới thực.

Tuy nhiên, với chỉ mục, cơ sở dữ liệu cũng thu thập số liệu thống kê trên các cột của chỉ mục và trong trường hợp này, cột chứa kết quả của biểu thức. Và trong khi lập kế hoạch, trình tối ưu hóa nhận thấy điều này và đưa ra ước tính tốt hơn nhiều.

Đây là một lợi ích to lớn và có thể giúp khắc phục một số trường hợp kế hoạch truy vấn kém do ước tính không chính xác gây ra. Tuy nhiên, hầu hết mọi người không biết về công cụ hữu ích này.

Và mức độ hữu ích của công cụ này chỉ tăng lên khi giới thiệu kiểu dữ liệu JSONB trong 9.4, vì đó là cách duy nhất để thu thập thống kê về nội dung của tài liệu JSONB.

Khi lập chỉ mục các tài liệu JSONB, tồn tại hai chiến lược lập chỉ mục cơ bản. Bạn có thể tạo chỉ mục GIN / GiST trên toàn bộ tài liệu, ví dụ:như thế này

CREATE INDEX ON t USING GIN (jsonb_column);

điều này cho phép bạn truy vấn các đường dẫn tùy ý trong cột JSONB, sử dụng toán tử chứa để khớp với các tài liệu con, v.v. Điều đó thật tuyệt, nhưng bạn vẫn chỉ có thống kê cơ bản cho mỗi cột, điều này không
hữu ích lắm như các tài liệu được coi là giá trị vô hướng (và không ai khớp với toàn bộ tài liệu hoặc sử dụng phạm vi tài liệu).

Chỉ mục biểu thức, ví dụ được tạo như thế này:

CREATE INDEX ON t ((jsonb_column->'id'));

sẽ chỉ hữu ích cho biểu thức cụ thể, tức là chỉ mục mới được tạo này sẽ hữu ích cho

SELECT * FROM t WHERE jsonb_column ->> 'id' = 123;

nhưng không dành cho các truy vấn truy cập các khóa JSON khác, chẳng hạn như 'giá trị'

SELECT * FROM t WHERE jsonb_column ->> 'value' = 'xxxx';

Điều này không có nghĩa là các chỉ mục GIN / GiST trên toàn bộ tài liệu là vô dụng, nhưng bạn phải lựa chọn. Bạn có thể tạo chỉ mục biểu thức tập trung, hữu ích khi truy vấn một khóa cụ thể và với lợi ích bổ sung của thống kê trên biểu thức. Hoặc bạn tạo chỉ mục GIN / GiST trên toàn bộ tài liệu, có thể xử lý các truy vấn trên các khóa tùy ý nhưng không có thống kê.

Tuy nhiên, bạn có thể ăn một chiếc bánh và ăn nó, trong trường hợp này, vì bạn có thể tạo cả hai chỉ mục cùng một lúc và cơ sở dữ liệu sẽ chọn cái nào trong số chúng để sử dụng cho các truy vấn riêng lẻ. Và bạn sẽ có số liệu thống kê chính xác, nhờ vào các chỉ mục biểu thức.

Đáng buồn là bạn không thể ăn hết chiếc bánh vì chỉ mục biểu thức và chỉ mục GIN / GiST sử dụng các điều kiện khác nhau

-- expression (btree)
SELECT * FROM t WHERE jsonb_column ->> 'id' = 123;

-- GIN/GiST
SELECT * FROM t WHERE jsonb_column @> '{"id" : 123}';

vì vậy người lập kế hoạch không thể sử dụng chúng cùng một lúc - chỉ mục biểu thức để ước tính và GIN / GiST để thực thi.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Sửa đổi giá trị bắt đầu Django AutoField

  2. Công bố Barman 1.0, Trình quản lý sao lưu và phục hồi cho PostgreSQL

  3. Làm cách nào để thêm nguồn dữ liệu PostgreSQL vào WildFly 9.0?

  4. Có thể cung cấp các tham số cho tên bảng hoặc cột trong Câu lệnh chuẩn bị hoặc QueryRunner.update () không?

  5. Thoát khỏi các tên cột giống từ khóa trong Postgres