PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Tentang kegunaan indeks ekspresi

Saat mengajar pelatihan PostgreSQL, baik pada topik dasar dan lanjutan, saya sering menemukan bahwa para peserta tidak tahu seberapa kuat indeks ekspresi itu (jika mereka mengetahuinya sama sekali). Jadi izinkan saya memberi Anda gambaran singkat.

Jadi, katakanlah kita memiliki tabel, dengan rentang cap waktu (ya, kita memiliki fungsi generate_series yang dapat menghasilkan tanggal):

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;

Tabel juga menyertakan kolom padding, untuk membuatnya sedikit lebih besar. Sekarang, mari lakukan kueri rentang sederhana, dengan memilih hanya satu bulan dari ~200 tahun yang disertakan dalam tabel. Jika Anda menjelaskan kueri, Anda akan melihat sesuatu seperti ini:

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)

dan di laptop saya, ini berjalan dalam ~ 20 ms. Lumayan, mengingat ini harus melewati seluruh tabel dengan ~75rb baris.

Tapi mari kita buat indeks pada kolom timestamp (semua indeks di sini adalah tipe default, yaitu btree, kecuali disebutkan secara eksplisit):

CREATE INDEX idx_t_d ON t (d);

Dan sekarang mari kita coba menjalankan kueri lagi:

                               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)

dan ini berjalan dalam 0,5 ms, jadi kira-kira 40x lebih cepat. Tapi itu tentu saja indeks sederhana, dibuat langsung di kolom, bukan indeks ekspresi. Jadi mari kita asumsikan kita perlu memilih data dari setiap hari pertama setiap bulan, melakukan kueri seperti ini

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

yang bagaimanapun tidak dapat menggunakan indeks, karena perlu mengevaluasi ekspresi pada kolom sementara indeks dibangun pada kolom itu sendiri, seperti yang ditunjukkan pada ANALISIS JELASKAN:

                               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)

Jadi tidak hanya ini harus melakukan pemindaian sekuensial, tetapi juga harus melakukan evaluasi, meningkatkan durasi kueri menjadi 43 md.

Basis data tidak dapat menggunakan indeks karena berbagai alasan. Indeks (setidaknya indeks btree) bergantung pada kueri data yang diurutkan, yang disediakan oleh struktur seperti pohon, dan sementara kueri rentang dapat memanfaatkannya, kueri kedua (dengan panggilan `extract`) tidak dapat melakukannya.

Catatan:Masalah lainnya adalah bahwa set operator yang didukung oleh indeks (yaitu yang dapat dievaluasi pada indeks secara langsung) sangat terbatas. Dan fungsi "ekstrak" tidak didukung, sehingga kueri tidak dapat mengatasi masalah pemesanan dengan menggunakan Pemindaian Indeks Bitmap.

Secara teori, database mungkin mencoba mengubah kondisi menjadi kondisi jangkauan, tetapi itu sangat sulit dan spesifik untuk diungkapkan. Dalam hal ini, kita harus membuat rentang "per-hari" dalam jumlah tak terbatas, karena perencana tidak benar-benar mengetahui cap waktu min/maks dalam tabel. Jadi database bahkan tidak mencoba.

Tapi sementara database tidak tahu bagaimana mengubah kondisi, pengembang sering melakukannya. Misalnya dengan kondisi seperti

(column + 1) >= 1000

tidak sulit untuk menulis ulang seperti ini

column >= (1000 - 1)

yang berfungsi baik dengan indeks.

Tetapi bagaimana jika transformasi tersebut tidak memungkinkan, seperti misalnya untuk contoh query

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

Dalam hal ini pengembang harus menghadapi masalah yang sama dengan min/maks yang tidak diketahui untuk kolom d, dan itupun akan menghasilkan banyak rentang.

Nah, posting blog ini tentang indeks ekspresi, dan sejauh ini kami hanya menggunakan indeks biasa, dibangun di atas kolom secara langsung. Jadi, mari kita buat indeks ekspresi pertama:

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

yang kemudian memberi kami rencana penjelasan ini

                               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)

Jadi sementara ini tidak memberi kita kecepatan 40x yang sama seperti indeks pada contoh pertama, itu agak diharapkan karena kueri ini mengembalikan jauh lebih banyak tupel (2401 vs. 32). Selain itu, itu tersebar di seluruh tabel dan tidak terlokalisasi seperti pada contoh pertama. Jadi ini adalah peningkatan 2x yang bagus, dan dalam banyak kasus di dunia nyata, Anda akan melihat peningkatan yang jauh lebih besar.

Tetapi kemampuan untuk menggunakan indeks untuk kondisi dengan ekspresi kompleks bukanlah informasi yang paling menarik di sini – itulah alasan mengapa orang membuat indeks ekspresi. Tapi itu bukan satu-satunya manfaat.

Jika Anda melihat dua penjelasan rencana yang disajikan di atas (tanpa dan dengan indeks ekspresi), Anda mungkin memperhatikan ini:

                               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)
 ...

Benar – membuat indeks ekspresi meningkatkan perkiraan secara signifikan. Tanpa indeks kami hanya memiliki statistik (MCV + histogram) untuk kolom tabel mentah, sehingga database tidak tahu cara memperkirakan ekspresi

EXTRACT(day FROM d) = 1

Jadi, alih-alih menerapkan perkiraan default untuk kondisi kesetaraan, yaitu 0,5% dari semua baris – karena tabel memiliki 73050 baris, kami berakhir dengan perkiraan hanya 365 baris. Adalah umum untuk melihat kesalahan estimasi yang jauh lebih buruk dalam aplikasi dunia nyata.

Dengan indeks, bagaimanapun, database juga mengumpulkan statistik pada kolom indeks, dan dalam hal ini kolom berisi hasil ekspresi. Dan saat merencanakan, pengoptimal memperhatikan hal ini dan menghasilkan perkiraan yang jauh lebih baik.

Ini adalah manfaat besar, dan dapat membantu memperbaiki beberapa kasus rencana kueri yang buruk yang disebabkan oleh perkiraan yang tidak akurat. Namun kebanyakan orang tidak menyadari alat yang berguna ini.

Dan kegunaan alat ini hanya meningkat dengan diperkenalkannya tipe data JSONB di 9.4, karena ini adalah satu-satunya cara untuk mengumpulkan statistik tentang isi dokumen JSONB.

Saat mengindeks dokumen JSONB, ada dua strategi pengindeksan dasar. Anda dapat membuat indeks GIN/GiST di seluruh dokumen, mis. seperti ini

CREATE INDEX ON t USING GIN (jsonb_column);

yang memungkinkan Anda untuk menanyakan jalur arbitrer di kolom JSONB, menggunakan operator penahanan untuk mencocokkan sub-dokumen, dll. Itu bagus, tetapi Anda masih hanya memiliki statistik dasar per kolom, yang
tidak terlalu berguna sebagai dokumen diperlakukan sebagai nilai skalar (dan tidak ada yang cocok dengan seluruh dokumen atau menggunakan rentang dokumen).

Indeks ekspresi, misalnya dibuat seperti ini:

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

hanya akan berguna untuk ekspresi tertentu, yaitu indeks yang baru dibuat ini akan berguna untuk

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

tetapi tidak untuk kueri yang mengakses kunci JSON lainnya, seperti 'nilai' misalnya

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

Ini bukan untuk mengatakan bahwa indeks GIN/GiST di seluruh dokumen tidak berguna, tetapi Anda harus memilih. Entah Anda membuat indeks ekspresi terfokus, berguna saat menanyakan kunci tertentu dan dengan manfaat tambahan statistik pada ekspresi. Atau Anda membuat indeks GIN/GiST di seluruh dokumen, mampu menangani kueri pada kunci arbitrer, tetapi tanpa statistik.

Namun Anda dapat memiliki kue dan memakannya juga, dalam hal ini, karena Anda dapat membuat kedua indeks pada saat yang sama, dan database akan memilih indeks mana yang akan digunakan untuk kueri individual. Dan Anda akan memiliki statistik yang akurat, berkat indeks ekspresi.

Sayangnya, Anda tidak dapat memakan keseluruhan kue, karena indeks ekspresi dan indeks GIN/GiST menggunakan kondisi yang berbeda

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

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

sehingga perencana tidak dapat menggunakannya secara bersamaan – indeks ekspresi untuk estimasi dan GIN/GiST untuk eksekusi.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Bagaimana cara menggunakan EXECUTE FORMAT ... MENGGUNAKAN dalam fungsi postgres

  2. SQL LIKE kondisi untuk memeriksa integer?

  3. Pengaturan Replikasi Slony-I Sederhana.

  4. AMD, Intel, dan PostgreSQL

  5. Menambahkan batasan satu-dari-dua bukan nol di postgresql