Subkueri berkorelasi dieksekusi sekali untuk setiap baris yang dikembalikan oleh kueri luar. Jika kueri luar menghasilkan volume data besar dan subkueri tidak didukung indeks, eksekusinya akan sangat memakan waktu. Penguraian subkueri (subquery unnesting) menulis ulang subkueri berkorelasi menjadi pernyataan JOIN yang ekuivalen, sehingga pengoptimal menjalankan subkueri hanya sekali—bukan sekali per baris—dan dapat menerapkan optimasi tambahan seperti pengurutan ulang join.
PolarDB for MySQL mendukung dua strategi penguraian: fungsi jendela (window functions) dan klausa GROUP BY.
Prasyarat
Versi kluster: PolarDB for MySQL 8.0, revisi 8.0.2.2.1 atau lebih baru. Untuk memeriksa versi Anda, lihat Query the engine version.
Pilih strategi
| Strategi | Gunakan saat |
|---|---|
| Window function | Subkueri menggunakan fungsi agregasi dan kolom gabungan merupakan primary key atau unique key |
| GROUP BY | Subkueri menggunakan fungsi agregasi dan kolom gabungan memiliki banyak nilai duplikat |
Aktifkan penguraian subkueri
Kedua strategi dikendalikan oleh subparameter dari loose_polar_optimizer_switch. Konfigurasikan parameter ini pada tingkat global atau session. Untuk detailnya, lihat Configure cluster and node parameters.
| Sub-parameter | Default | Deskripsi |
|---|---|---|
unnest_use_window_function | ON | Menguraikan subkueri menggunakan window functions |
unnest_use_group_by | ON | Menguraikan subkueri menggunakan klausa GROUP BY (berbasis biaya) |
derived_merge_cost_based | OFF | Menerapkan derived merge berdasarkan optimasi berbasis biaya |
Menguraikan subkueri menggunakan window functions
Cara kerja
Gambar berikut menunjukkan struktur kueri yang berisi subkueri.

T1, T2, dan T3 masing-masing merepresentasikan kumpulan satu atau beberapa tabel dan tampilan. T2 (di dalam subkueri) bersarang dengan T3, seperti yang ditunjukkan oleh garis putus-putus. T1 berada di kueri luar dan tidak bersarang dengan T2.
Strategi window function berlaku jika semua kondisi berikut terpenuhi:
Subkueri skalar tidak mengandung klausa LIMIT atau DISTINCT, dan output-nya merupakan fungsi agregasi.
Tabel dalam subkueri merupakan subset dari tabel-tabel dalam kueri luar.
Subkueri dihubungkan ke kueri luar melalui equi join. Kueri luar berisi kondisi gabungan dengan semantik yang sama dan kondisi filter untuk tabel-tabel umum dalam subkueri.
Kolom gabungan dalam subkueri merupakan kolom primary key atau unique key.
Baik subkueri maupun kueri luar tidak mengandung fungsi kustom atau fungsi acak.
Setelah penguraian, window function menghitung agregasi dalam kelompok dan menyambungkan hasilnya ke setiap baris, sehingga menghilangkan kebutuhan untuk mengeksekusi ulang subkueri.

Contoh
Contoh berikut menggunakan TPC-H Q2 (Minimum Cost Supplier Query), yang mencari pemasok dengan harga terendah di antara semua pemasok di suatu wilayah yang menjual komponen dengan jenis dan ukuran tertentu.
Pada MySQL Community Edition, kueri luar pertama-tama mengambil semua pemasok yang sesuai. Subkueri kemudian dijalankan pada setiap baris untuk menemukan biaya pasok minimum. Untuk set data besar, ini berarti subkueri dieksekusi sekali per baris pemasok.
Kueri asli:
SELECT s_acctbal, s_name, n_name, p_partkey, p_mfgr,
s_address, s_phone, s_comment
FROM part, supplier, partsupp, nation, region
WHERE p_partkey = ps_partkey
AND s_suppkey = ps_suppkey
AND p_size = 30
AND p_type LIKE '%STEEL'
AND s_nationkey = n_nationkey
AND n_regionkey = r_regionkey
AND r_name = 'ASIA'
AND ps_supplycost = (
SELECT MIN(ps_supplycost)
FROM partsupp, supplier, nation, region
WHERE p_partkey = ps_partkey
AND s_suppkey = ps_suppkey
AND s_nationkey = n_nationkey
AND n_regionkey = r_regionkey
AND r_name = 'ASIA'
)
ORDER BY s_acctbal DESC, n_name, s_name, p_partkey
LIMIT 100;PolarDB menulis ulang kueri ini menggunakan MIN() OVER(PARTITION BY ps_partkey) untuk menghitung biaya minimum per komponen dalam satu kali pemindaian, lalu memfilter baris-baris di mana biaya pasok cocok dengan minimum kelompok tersebut.
Kueri yang ditulis ulang:
SELECT s_acctbal, s_name, n_name, p_partkey, p_mfgr,
s_address, s_phone, s_comment
FROM (
SELECT MIN(ps_supplycost) OVER(PARTITION BY ps_partkey) as win_min,
ps_partkey, ps_supplycost, s_acctbal, n_name, s_name, s_address,
s_phone, s_comment
FROM part, partsupp, supplier, nation, region
WHERE p_partkey = ps_partkey
AND s_suppkey = ps_suppkey
AND s_nationkey = n_nationkey
AND n_regionkey = r_regionkey
AND p_size = 30
AND p_type LIKE '%STEEL'
AND r_name = 'ASIA') as derived
WHERE ps_supplycost = derived.win_min
ORDER BY s_acctbal DESC, n_name, s_name, p_partkey
LIMIT 100;Perbaikan performa
Pada faktor skala TPC-H 10:
Q2: Peningkatan 1,54x
Q17: Peningkatan 4,91x

Menguraikan subkueri menggunakan klausa GROUP BY
Cara kerja
Gambar berikut menunjukkan struktur kueri yang berisi subkueri.

Strategi GROUP BY berlaku jika semua kondisi berikut terpenuhi:
Subkueri skalar tidak mengandung klausa GROUP BY atau LIMIT, dan output-nya merupakan fungsi agregasi.
Subkueri skalar muncul dalam kondisi JOIN, WHERE, atau SELECT.
Subkueri dihubungkan ke kueri luar melalui equi join, dengan kondisi yang digabungkan oleh AND.
Subkueri skalar tidak mengandung fungsi kustom atau fungsi acak.
Setelah penguraian, subkueri berubah menjadi tabel turunan yang melakukan pre-agregasi hasil yang dikelompokkan berdasarkan kunci gabungan. Kueri luar kemudian melakukan join terhadap tabel turunan ini hanya sekali, menggantikan eksekusi subkueri berulang.

Contoh
Contoh berikut mengambil pesanan di mana kuantitas melebihi 10% dari total jumlah pembelian untuk produk yang sama.
Kueri asli:
SELECT *
FROM sale_lineitem sl
WHERE sl.sl_quantity >
(SELECT 0.1 * SUM(pl.pl_quantity)
FROM purchase_lineitem pl
WHERE pl.pl_objectkey = sl.sl_objectkey);Tanpa penguraian, database melakukan iterasi pada setiap baris di sale_lineitem, membaca sl_objectkey, dan mengeksekusi ulang subkueri untuk nilai tersebut. Subkueri dijalankan sebanyak jumlah baris di sale_lineitem. Karena sl_objectkey biasanya berisi banyak nilai duplikat, agregasi yang sama pada purchase_lineitem diulang berkali-kali—bahkan ketika indeks tersedia pada pl_objectkey.
PolarDB menulis ulang kueri ini sebagai LEFT JOIN terhadap tabel turunan yang telah dipre-agregasi. Tabel purchase_lineitem hanya dipindai sekali.
Kueri yang ditulis ulang:
SELECT *
FROM sale_lineitem sl
LEFT JOIN
(SELECT (0.1 * sum(pl.pl_quantity)) AS Name_exp_1,
pl.pl_objectkey AS Name_exp_2
FROM purchase_lineitem pl
GROUP BY pl.pl_objectkey) derived ON derived.Name_exp_2 = sl.sl_objectkey
WHERE sl.sl_quantity > derived.name_exp_1;Pengoptimal dapat lebih meningkatkan eksekusi dengan mengurutkan ulang join berdasarkan estimasi biaya.
Timpa penguraian dengan petunjuk (hints)
Gunakan petunjuk UNNEST dan NO_UNNEST untuk mengontrol penguraian pada basis per kueri, terlepas dari pengaturan loose_polar_optimizer_switch.
Sintaks petunjuk:
UNNEST([@query_block_name] [strategy [, strategy] ...])
NO_UNNEST([@query_block_name] [strategy [, strategy] ...])strategy dapat berupa WINDOW_FUNCTION atau GROUP_BY.
Contoh:
-- Memaksa penguraian dengan window function
SELECT ... FROM ... WHERE ... = (SELECT /*+UNNEST(WINDOW_FUNCTION)*/ agg FROM ...)
SELECT /*+UNNEST(@`select#2` WINDOW_FUNCTION)*/ ... FROM ... WHERE ... = (SELECT agg FROM ...)
-- Menonaktifkan penguraian dengan window function
SELECT ... FROM ... WHERE ... = (SELECT /*+NO_UNNEST(WINDOW_FUNCTION)*/ agg FROM ...)
SELECT /*+NO_UNNEST(@`select#2` WINDOW_FUNCTION)*/ ... FROM ... WHERE ... = (SELECT agg FROM ...)
-- Memaksa penguraian dengan GROUP BY
SELECT ... FROM ... WHERE ... = (SELECT /*+UNNEST(GROUP_BY)*/ agg FROM ...)
SELECT /*+UNNEST(@`select#2` GROUP_BY)*/ ... FROM ... WHERE ... = (SELECT agg FROM ...)
-- Menonaktifkan penguraian dengan GROUP BY
SELECT ... FROM ... WHERE ... = (SELECT /*+NO_UNNEST(GROUP_BY)*/ agg FROM ...)
SELECT /*+NO_UNNEST(@`select#2` GROUP_BY)*/ ... FROM ... WHERE ... = (SELECT agg FROM ...)