All Products
Search
Document Center

PolarDB:Mengonversi klausa OR menjadi UNION ALL

Last Updated:Jan 14, 2026

PolarDB for PostgreSQL dan mendukung fitur optimasi kueri yang mengonversi klausa OR ke format UNION ALL. Selama pembuatan rencana eksekusi, pengoptimal mencoba konversi ini dan memilih jalur berdasarkan biaya untuk menghasilkan rencana eksekusi yang lebih optimal.

Latar Belakang

Pengoptimal PostgreSQL memiliki kemampuan terbatas dalam mengoptimalkan kondisi filter OR pada Pernyataan SQL. Jika klausa OR hanya melibatkan satu tabel dan setiap kondisi filter memiliki indeks yang sesuai, pengoptimal akan menghasilkan BitmapOr Index Path. Contohnya:

EXPLAIN SELECT * FROM my_test WHERE (id = 123 OR name = '123' OR salary = 123.0);
                                      QUERY PLAN
---------------------------------------------------------------------------------------
 Bitmap Heap Scan on my_test  (cost=12.90..24.33 rows=3 width=15)
   Recheck Cond: ((id = 123) OR ((name)::text = '123'::text) OR (salary = 123.0))
   ->  BitmapOr  (cost=12.90..12.90 rows=3 width=0)
         ->  Bitmap Index Scan on my_test_id_idx  (cost=0.00..4.30 rows=1 width=0)
               Index Cond: (id = 123)
         ->  Bitmap Index Scan on my_test_name_idx  (cost=0.00..4.30 rows=1 width=0)
               Index Cond: ((name)::text = '123'::text)
         ->  Bitmap Index Scan on my_test_salary_idx  (cost=0.00..4.30 rows=1 width=0)
               Index Cond: (salary = 123.0)
(9 rows)

Jika klausa OR melibatkan beberapa tabel, pengoptimal hanya dapat memperlakukan klausa tersebut sebagai kondisi filter setelah join. Pendekatan ini dapat mengurangi efisiensi eksekusi SQL. Contohnya:

EXPLAIN ANALYZE SELECT * FROM t1 JOIN t2 ON t1.id = t2.id WHERE (t1.num = 1 OR t2.cnt = 2);
                                                    QUERY PLAN
-------------------------------------------------------------------------------------------------------------------
 Hash Join  (cost=299.00..660.50 rows=110 width=51) (actual time=5.992..15.673 rows=110 loops=1)
   Hash Cond: (t1.id = t2.id)
   Join Filter: ((t1.num = 1) OR (t2.cnt = 2))
   Rows Removed by Join Filter: 9890
   ->  Seq Scan on t1  (cost=0.00..174.00 rows=10000 width=25) (actual time=0.012..2.080 rows=10000 loops=1)
   ->  Hash  (cost=174.00..174.00 rows=10000 width=26) (actual time=5.855..5.857 rows=10000 loops=1)
         Buckets: 16384  Batches: 1  Memory Usage: 704kB
         ->  Seq Scan on t2  (cost=0.00..174.00 rows=10000 width=26) (actual time=0.007..1.779 rows=10000 loops=1)
 Planning Time: 1.237 ms
 Execution Time: 15.836 ms
(10 rows)

Pengoptimal memperlakukan klausa OR di atas sebagai satu kesatuan dan tidak dapat menggunakan indeks pada t1.num atau t2.cnt. Hal ini mengakibatkan pemindaian tabel penuh pada t1 dan t2. Secara logika, klausa OR dapat dikonversi menjadi kueri UNION ALL dengan dua atau lebih cabang. Sebagai contoh, pernyataan di atas dapat ditulis ulang sebagai berikut:

EXPLAIN ANALYZE
SELECT * FROM t1 JOIN t2 ON t1.id = t2.id WHERE t1.num = 1
UNION ALL
SELECT * FROM t1 JOIN t2 ON t1.id = t2.id WHERE t2.cnt = 2 AND (t1.num != 1 OR (t1.num = 1) IS NULL);
                                                               QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------
 Append  (cost=85.48..412.26 rows=110 width=51) (actual time=0.350..4.832 rows=110 loops=1)
   ->  Hash Join  (cost=85.48..297.98 rows=100 width=51) (actual time=0.349..4.653 rows=100 loops=1)
         Hash Cond: (t2.id = t1.id)
         ->  Seq Scan on t2  (cost=0.00..174.00 rows=10000 width=26) (actual time=0.009..1.719 rows=10000 loops=1)
         ->  Hash  (cost=84.23..84.23 rows=100 width=25) (actual time=0.318..0.320 rows=100 loops=1)
               Buckets: 1024  Batches: 1  Memory Usage: 14kB
               ->  Bitmap Heap Scan on t1  (cost=5.06..84.23 rows=100 width=25) (actual time=0.065..0.265 rows=100 loops=1)
                     Recheck Cond: (num = 1)
                     Heap Blocks: exact=73
                     ->  Bitmap Index Scan on t1_num_idx  (cost=0.00..5.04 rows=100 width=0) (actual time=0.037..0.037 rows=100 loops=1)
                           Index Cond: (num = 1)
   ->  Nested Loop  (cost=4.65..112.63 rows=10 width=51) (actual time=0.049..0.159 rows=10 loops=1)
         ->  Bitmap Heap Scan on t2 t2_1  (cost=4.36..33.46 rows=10 width=26) (actual time=0.026..0.045 rows=10 loops=1)
               Recheck Cond: (cnt = 2)
               Heap Blocks: exact=10
               ->  Bitmap Index Scan on t2_cnt_idx  (cost=0.00..4.36 rows=10 width=0) (actual time=0.017..0.018 rows=10 loops=1)
                     Index Cond: (cnt = 2)
         ->  Index Scan using t1_id_idx on t1 t1_1  (cost=0.29..7.91 rows=1 width=25) (actual time=0.009..0.009 rows=1 loops=10)
               Index Cond: (id = t2_1.id)
               Filter: ((num <> 1) OR ((num = 1) IS NULL))
 Planning Time: 1.150 ms
 Execution Time: 5.014 ms
(22 rows)

Setelah pernyataan ditulis ulang, pengoptimal dapat menggunakan indeks pada t1.num dan t2.cnt. Perubahan ini mengurangi jumlah data yang diproses dan meningkatkan performa eksekusi.

PolarDB for PostgreSQL dan dapat mencoba mengonversi klausa OR yang sesuai ke format UNION ALL selama pembuatan rencana eksekusi. Pengoptimal kemudian memilih jalur berdasarkan biaya untuk mendapatkan rencana eksekusi yang lebih optimal.

Penerapan

Fitur ini didukung pada versi berikut dari PolarDB for PostgreSQL: PostgreSQL 14 dengan versi mesin minor 2.0.14.13.27.0 atau lebih baru.

Catatan

Anda dapat melihat versi mesin minor di Konsol atau menjalankan pernyataan SHOW polardb_version;. Jika versi mesin minor tidak memenuhi persyaratan, Anda harus upgrade versi mesin minor.

Penggunaan

Fitur konversi OR-ke-UNION-ALL dikendalikan oleh parameter. Tabel berikut menjelaskan parameter dan fungsinya.

Nama Parameter

Deskripsi

polar_cbqt_cost_threshold

Mengontrol threshold biaya total dari rencana eksekusi untuk mencoba konversi OR-ke-UNION-ALL. Jika biaya total dari rencana eksekusi asli untuk suatu Pernyataan SQL tidak melebihi threshold ini, konversi tidak akan dicoba. Nilainya berkisar antara [0, +∞). Nilai default adalah 50000.

Nilai 0 berarti konversi dicoba untuk semua Pernyataan SQL. Kami tidak menyarankan mengatur parameter ini ke 0 karena dapat meningkatkan waktu perencanaan untuk semua Pernyataan SQL dan memengaruhi performa.

polar_cbqt_convert_or_to_union_all_mode

Mengontrol fitur konversi OR-ke-UNION-ALL. Nilai yang valid:

  • OFF (default): Menonaktifkan fitur.

  • ON: Mengonversi klausa OR menjadi UNION ALL.

  • FORCE: Mengaktifkan fitur dan memaksa pengoptimal memilih jalur hasil konversi jika tersedia. Jalur ini mungkin tidak memiliki biaya total terendah.

Catatan penggunaan

  • Mengatur polar_cbqt_cost_threshold ke 0 atau mengatur polar_cbqt_convert_or_to_union_all_mode ke FORCE akan memaksa optimasi ini untuk semua Pernyataan SQL. Untuk memaksa optimasi OR-ke-UNION-ALL pada Pernyataan SQL tertentu, kami menyarankan penggunaan HINT. Untuk informasi lebih lanjut, lihat Menggunakan HINT.

  • Jika kondisi dalam klausa OR hanya melibatkan satu tabel, pengoptimal tidak mencoba melakukan konversi.

  • Dalam mode FORCE, jika suatu Pernyataan SQL memiliki beberapa klausa OR yang sesuai, pengoptimal memilih jalur dengan biaya total terendah.

  • Jika klausa OR memiliki lebih dari enam argumen, pengoptimal tidak mencoba melakukan konversi.

  • Jika suatu Pernyataan SQL memiliki lebih dari enam klausa OR, pengoptimal hanya mempertimbangkan enam klausa pertama untuk konversi.

Contoh

Persiapkan data

CREATE TABLE t1(id int, num int, dsc text, log_date text);
CREATE TABLE t2(id int, cnt int, change text, op_date text);

INSERT INTO t1 SELECT i, i%100, 'test'||1, to_char('1990-10-10'::date + i, 'YYYY-MM-DD') FROM generate_series(1,10000)i;
INSERT INTO t2 SELECT i, i%1000, 'now'||i, to_char('1990-10-10'::date + i, 'YYYY-MM-DD') FROM generate_series(1,10000)i;

CREATE INDEX ON t1(id);
CREATE INDEX ON t1(num);
CREATE INDEX ON t2(id);
CREATE INDEX ON t2(cnt);

ANALYZE t1;
ANALYZE t2;

Fitur dasar

  • Nonaktifkan fitur konversi OR-ke-UNION-ALL.

    SET polar_cbqt_cost_threshold to 100;
    SET polar_cbqt_convert_or_to_union_all_mode to off;
    
    EXPLAIN ANALYZE SELECT * FROM t1 JOIN t2 ON t1.id = t2.id WHERE (t1.num = 1 OR t2.cnt = 2);

    Hasil berikut dikembalikan:

                                                        QUERY PLAN
    -------------------------------------------------------------------------------------------------------------------
     Hash Join  (cost=299.00..660.50 rows=110 width=51) (actual time=5.992..15.673 rows=110 loops=1)
       Hash Cond: (t1.id = t2.id)
       Join Filter: ((t1.num = 1) OR (t2.cnt = 2))
       Rows Removed by Join Filter: 9890
       ->  Seq Scan on t1  (cost=0.00..174.00 rows=10000 width=25) (actual time=0.012..2.080 rows=10000 loops=1)
       ->  Hash  (cost=174.00..174.00 rows=10000 width=26) (actual time=5.855..5.857 rows=10000 loops=1)
             Buckets: 16384  Batches: 1  Memory Usage: 704kB
             ->  Seq Scan on t2  (cost=0.00..174.00 rows=10000 width=26) (actual time=0.007..1.779 rows=10000 loops=1)
     Planning Time: 1.237 ms
     Execution Time: 15.836 ms
    (10 rows)
  • Aktifkan fitur konversi OR-ke-UNION-ALL.

    SET polar_cbqt_cost_threshold to 100;
    SET polar_cbqt_convert_or_to_union_all_mode to on;
    
    EXPLAIN ANALYZE SELECT * FROM t1 JOIN t2 ON t1.id = t2.id WHERE (t1.num = 1 OR t2.cnt = 2);

    Hasil berikut dikembalikan:

                                                                   QUERY PLAN
    -----------------------------------------------------------------------------------------------------------------------------------------
     Append  (cost=85.48..411.16 rows=110 width=51) (actual time=0.396..4.822 rows=110 loops=1)
       ->  Hash Join  (cost=85.48..297.98 rows=100 width=51) (actual time=0.395..4.639 rows=100 loops=1)
             Hash Cond: (t2.id = t1.id)
             ->  Seq Scan on t2  (cost=0.00..174.00 rows=10000 width=26) (actual time=0.010..1.750 rows=10000 loops=1)
             ->  Hash  (cost=84.23..84.23 rows=100 width=25) (actual time=0.333..0.335 rows=100 loops=1)
                   Buckets: 1024  Batches: 1  Memory Usage: 14kB
                   ->  Bitmap Heap Scan on t1  (cost=5.06..84.23 rows=100 width=25) (actual time=0.056..0.247 rows=100 loops=1)
                         Recheck Cond: (num = 1)
                         Heap Blocks: exact=73
                         ->  Bitmap Index Scan on t1_num_idx  (cost=0.00..5.04 rows=100 width=0) (actual time=0.028..0.028 rows=100 loops=1)
                               Index Cond: (num = 1)
       ->  Nested Loop  (cost=4.65..112.63 rows=10 width=51) (actual time=0.049..0.164 rows=10 loops=1)
             ->  Bitmap Heap Scan on t2  (cost=4.36..33.46 rows=10 width=26) (actual time=0.027..0.044 rows=10 loops=1)
                   Recheck Cond: (cnt = 2)
                   Heap Blocks: exact=10
                   ->  Bitmap Index Scan on t2_cnt_idx  (cost=0.00..4.36 rows=10 width=0) (actual time=0.019..0.019 rows=10 loops=1)
                         Index Cond: (cnt = 2)
             ->  Index Scan using t1_id_idx on t1  (cost=0.29..7.91 rows=1 width=25) (actual time=0.010..0.010 rows=1 loops=10)
                   Index Cond: (id = t2.id)
                   Filter: ((num <> 1) OR ((num = 1) IS NULL))
     Planning Time: 2.903 ms
     Execution Time: 4.980 ms
    (22 rows)

Setelah fitur konversi OR-ke-UNION-ALL diaktifkan, pengoptimal dapat menghasilkan rencana eksekusi yang menggunakan indeks pada t1.num dan t2.cnt, mencapai efek yang sama seperti menulis ulang kueri secara manual menggunakan UNION ALL.

Pemilihan paksa

  • Aktifkan fitur konversi OR-ke-UNION-ALL.

    SET polar_cbqt_cost_threshold to 100;
    SET polar_cbqt_convert_or_to_union_all_mode to on;
    
    EXPLAIN ANALYZE SELECT * FROM t1, t2 WHERE t1.dsc= t2.change AND (t1.log_date = '2024-01-01' OR t2.op_date = '2024-01-01');

    Hasil berikut dikembalikan:

                                                        QUERY PLAN
    -------------------------------------------------------------------------------------------------------------------
     Hash Join  (cost=299.00..660.50 rows=2 width=51) (actual time=14.321..14.325 rows=0 loops=1)
       Hash Cond: (t1.dsc = t2.change)
       Join Filter: ((t1.log_date = '2024-01-01'::text) OR (t2.op_date = '2024-01-01'::text))
       ->  Seq Scan on t1  (cost=0.00..174.00 rows=10000 width=25) (actual time=0.016..3.204 rows=10000 loops=1)
       ->  Hash  (cost=174.00..174.00 rows=10000 width=26) (actual time=6.506..6.508 rows=10000 loops=1)
             Buckets: 16384  Batches: 1  Memory Usage: 704kB
             ->  Seq Scan on t2  (cost=0.00..174.00 rows=10000 width=26) (actual time=0.006..1.755 rows=10000 loops=1)
     Planning Time: 0.932 ms
     Execution Time: 14.571 ms
    (9 rows)
  • Paksa konversi OR-ke-UNION-ALL.

    SET polar_cbqt_cost_threshold to 100;
    SET polar_cbqt_convert_or_to_union_all_mode to force;
    
    EXPLAIN ANALYZE SELECT * FROM t1, t2 WHERE t1.dsc= t2.change AND (t1.log_date = '2024-01-01' OR t2.op_date = '2024-01-01');

    Hasil berikut dikembalikan:

                                                       QUERY PLAN
    -----------------------------------------------------------------------------------------------------------------
     Append  (cost=199.01..871.05 rows=2 width=51) (actual time=9.915..9.923 rows=0 loops=1)
       ->  Hash Join  (cost=199.01..410.52 rows=1 width=51) (actual time=5.046..5.050 rows=0 loops=1)
             Hash Cond: (t2.change = t1.dsc)
             ->  Seq Scan on t2  (cost=0.00..174.00 rows=10000 width=26) (actual time=0.015..0.015 rows=1 loops=1)
             ->  Hash  (cost=199.00..199.00 rows=1 width=25) (actual time=5.014..5.016 rows=0 loops=1)
                   Buckets: 1024  Batches: 1  Memory Usage: 8kB
                   ->  Seq Scan on t1  (cost=0.00..199.00 rows=1 width=25) (actual time=5.013..5.013 rows=0 loops=1)
                         Filter: (log_date = '2024-01-01'::text)
                         Rows Removed by Filter: 10000
       ->  Hash Join  (cost=199.01..460.52 rows=1 width=51) (actual time=4.865..4.867 rows=0 loops=1)
             Hash Cond: (t1.dsc = t2.change)
             ->  Seq Scan on t1  (cost=0.00..224.00 rows=9999 width=25) (actual time=0.015..0.016 rows=1 loops=1)
                   Filter: ((log_date <> '2024-01-01'::text) OR ((log_date = '2024-01-01'::text) IS NULL))
             ->  Hash  (cost=199.00..199.00 rows=1 width=26) (actual time=4.828..4.829 rows=0 loops=1)
                   Buckets: 1024  Batches: 1  Memory Usage: 8kB
                   ->  Seq Scan on t2  (cost=0.00..199.00 rows=1 width=26) (actual time=4.827..4.827 rows=0 loops=1)
                         Filter: (op_date = '2024-01-01'::text)
                         Rows Removed by Filter: 10000
     Planning Time: 0.777 ms
     Execution Time: 10.088 ms
    (20 rows)

Perbandingan biaya total dari rencana eksekusi menunjukkan bahwa biaya lebih rendah tanpa konversi. Dalam mode non-paksa, pengoptimal mencoba konversi berdasarkan threshold biaya tetapi tidak selalu memilih jalur hasil konversi. Dalam mode FORCE, pengoptimal dipaksa memilih jalur hasil konversi. Mode FORCE berguna dalam skenario di mana pengoptimal tidak dapat memperkirakan biaya secara akurat dan Anda perlu memaksa rencana kueri menggunakan konversi OR-ke-UNION-ALL.

Menggunakan HINT

Untuk memaksa optimasi OR-ke-UNION-ALL pada Pernyataan SQL tertentu, Anda juga dapat menggunakan HINT untuk kontrol tingkat SQL.

SET polar_cbqt_convert_or_to_union_all_mode to off;

EXPLAIN ANALYZE /*+ Set(polar_cbqt_convert_or_to_union_all_mode force) Set(polar_cbqt_cost_threshold 0) */ SELECT * FROM t1, t2 WHERE t1.dsc= t2.change and (t1.log_date = '2024-01-01' or t2.op_date = '2024-01-01');

Hasil berikut dikembalikan:

                                                   QUERY PLAN
-----------------------------------------------------------------------------------------------------------------
 Append  (cost=199.01..871.05 rows=2 width=51) (actual time=9.684..9.691 rows=0 loops=1)
   ->  Hash Join  (cost=199.01..410.52 rows=1 width=51) (actual time=4.711..4.714 rows=0 loops=1)
         Hash Cond: (t2.change = t1.dsc)
         ->  Seq Scan on t2  (cost=0.00..174.00 rows=10000 width=26) (actual time=0.013..0.013 rows=1 loops=1)
         ->  Hash  (cost=199.00..199.00 rows=1 width=25) (actual time=4.682..4.684 rows=0 loops=1)
               Buckets: 1024  Batches: 1  Memory Usage: 8kB
               ->  Seq Scan on t1  (cost=0.00..199.00 rows=1 width=25) (actual time=4.681..4.681 rows=0 loops=1)
                     Filter: (log_date = '2024-01-01'::text                     Rows Removed by Filter: 10000
   ->  Hash Join  (cost=199.01..460.52 rows=1 width=51) (actual time=4.969..4.970 rows=0 loops=1)
         Hash Cond: (t1.dsc = t2.change)
         ->  Seq Scan on t1  (cost=0.00..224.00 rows=9999 width=25) (actual time=0.018..0.018 rows=1 loops=1)
               Filter: ((log_date <> '2024-01-01'::text) OR ((log_date = '2024-01-01'::text) IS NULL))
         ->  Hash  (cost=199.00..199.00 rows=1 width=26) (actual time=4.935..4.936 rows=0 loops=1)
               Buckets: 1024  Batches: 1  Memory Usage: 8kB
               ->  Seq Scan on t2  (cost=0.00..199.00 rows=1 width=26) (actual time=4.934..4.934 rows=0 loops=1)
                     Filter: (op_date = '2024-01-01'::text)
                     Rows Removed by Filter: 10000
 Planning Time: 0.798 ms
 Execution Time: 9.858 ms
(20 rows)

Ambang Batas Konversi

Dalam contoh Fitur dasar, total cost dari rencana eksekusi asli adalah 660,50. Atur threshold konversi ke nilai yang lebih tinggi dari 660,50 dan jalankan kembali kueri SQL yang sama:

SET polar_cbqt_cost_threshold to 1000; -- Atur threshold.
SET polar_cbqt_convert_or_to_union_all_mode to force; -- Paksa pemilihan jalur hasil konversi.

EXPLAIN ANALYZE SELECT * FROM t1 JOIN t2 ON t1.id = t2.id WHERE (t1.num = 1 OR t2.cnt = 2);

Hasil berikut dikembalikan:

                                                    QUERY PLAN
-------------------------------------------------------------------------------------------------------------------
 Hash Join  (cost=299.00..660.50 rows=110 width=51) (actual time=6.374..15.802 rows=110 loops=1)
   Hash Cond: (t1.id = t2.id)
   Join Filter: ((t1.num = 1) OR (t2.cnt = 2))
   Rows Removed by Join Filter: 9890
   ->  Seq Scan on t1  (cost=0.00..174.00 rows=10000 width=25) (actual time=0.011..2.038 rows=10000 loops=1)
   ->  Hash  (cost=174.00..174.00 rows=10000 width=26) (actual time=6.266..6.268 rows=10000 loops=1)
         Buckets: 16384  Batches: 1  Memory Usage: 704kB
         ->  Seq Scan on t2  (cost=0.00..174.00 rows=10000 width=26) (actual time=0.006..1.778 rows=10000 loops=1)
 Planning Time: 0.663 ms
 Execution Time: 16.036 ms
(10 rows)

Hasil menunjukkan bahwa pengoptimal tetap memilih jalur asli, bahkan dalam mode FORCE. Hal ini karena biaya total dari rencana asli tidak melebihi threshold, sehingga pengoptimal tidak mencoba melakukan konversi klausa OR.