Saat kueri berjalan lambat atau mengonsumsi memori berlebihan, rencana eksekusi menunjukkan secara tepat di mana waktu dan sumber daya digunakan. EXPLAIN menampilkan jalur yang direncanakan oleh pengoptimal untuk suatu kueri sebelum dieksekusi. EXPLAIN ANALYZE menjalankan kueri dan merekam biaya eksekusi terdistribusi yang sebenarnya—durasi tingkat operator, penggunaan memori puncak, serta jumlah baris input/output—sehingga Anda dapat membandingkan estimasi dengan realitas dan mengidentifikasi bottleneck.
Prasyarat
Sebelum memulai, pastikan bahwa:
Kluster AnalyticDB for MySQL Anda menggunakan versi V3.1.3 atau lebih baru. Untuk memeriksa versi, lihat Bagaimana cara melihat versi kluster AnalyticDB for MySQL?. Untuk melakukan upgrade, Submit a ticket.
EXPLAIN
EXPLAIN mengevaluasi jalur eksekusi yang direncanakan untuk kueri SQL. Output-nya merupakan estimasi dari pengoptimal dan mungkin tidak sesuai dengan hasil eksekusi aktual.
Sintaksis
EXPLAIN (format text) <SELECT statement>;Tambahkan (format text) untuk kueri non-kompleks guna meningkatkan keterbacaan hierarki pohon dalam output.
Contoh
EXPLAIN (format text)
SELECT count(*)
FROM nation, region, customer
WHERE c_nationkey = n_nationkey
AND n_regionkey = r_regionkey
AND r_name = 'ASIA';Output-nya berupa pohon di mana setiap node adalah sebuah operator. Bidang Outputs pada setiap node menunjukkan kolom yang dihasilkan oleh operator tersebut, sedangkan Estimates menampilkan perkiraan jumlah baris dan volume data dari pengoptimal, yang digunakan untuk menentukan urutan join dan strategi pengacakan data.
Output[count(*)]
│ Outputs: [count:bigint]
│ Estimates: {rows: 1 (8B)}
│ count(*) := count
└─ Aggregate(FINAL)
│ Outputs: [count:bigint]
│ Estimates: {rows: 1 (8B)}
│ count := count(`count_1`)
└─ LocalExchange[SINGLE] ()
│ Outputs: [count_0_1:bigint]
│ Estimates: {rows: 1 (8B)}
└─ RemoteExchange[GATHER]
│ Outputs: [count_0_2:bigint]
│ Estimates: {rows: 1 (8B)}
└─ Aggregate(PARTIAL)
│ Outputs: [count_0_4:bigint]
│ Estimates: {rows: 1 (8B)}
│ count_4 := count(*)
└─ InnerJoin[(`c_nationkey` = `n_nationkey`)][$hashvalue, $hashvalue_0_6]
│ Outputs: []
│ Estimates: {rows: 302035 (4.61MB)}
│ Distribution: REPLICATED
├─ Project[]
│ │ Outputs: [c_nationkey:integer, $hashvalue:bigint]
│ │ Estimates: {rows: 1500000 (5.72MB)}
│ │ $hashvalue := `combine_hash`(BIGINT '0', COALESCE(`$operator$hash_code`(`c_nationkey`), 0))
│ └─ RuntimeFilter
│ │ Outputs: [c_nationkey:integer]
│ │ Estimates: {rows: 1500000 (5.72MB)}
│ ├─ TableScan[adb:AdbTableHandle{schema=tpch, tableName=customer, partitionColumnHandles=[c_custkey]}]
│ │ Outputs: [c_nationkey:integer]
│ │ Estimates: {rows: 1500000 (5.72MB)}
│ │ c_nationkey := AdbColumnHandle{columnName=c_nationkey, type=4, isIndexed=true}
│ └─ RuntimeCollect
│ │ Outputs: [n_nationkey:integer]
│ │ Estimates: {rows: 5 (60B)}
│ └─ LocalExchange[ROUND_ROBIN] ()
│ │ Outputs: [n_nationkey:integer]
│ │ Estimates: {rows: 5 (60B)}
│ └─ RuntimeScan
│ Outputs: [n_nationkey:integer]
│ Estimates: {rows: 5 (60B)}
└─ LocalExchange[HASH][$hashvalue_0_6] ("n_nationkey")
│ Outputs: [n_nationkey:integer, $hashvalue_0_6:bigint]
│ Estimates: {rows: 5 (60B)}
└─ Project[]
│ Outputs: [n_nationkey:integer, $hashvalue_0_10:bigint]
│ Estimates: {rows: 5 (60B)}
│ $hashvalue_10 := `combine_hash`(BIGINT '0', COALESCE(`$operator$hash_code`(`n_nationkey`), 0))
└─ RemoteExchange[REPLICATE]
│ Outputs: [n_nationkey:integer]
│ Estimates: {rows: 5 (60B)}
└─ InnerJoin[(`n_regionkey` = `r_regionkey`)][$hashvalue_0_7, $hashvalue_0_8]
│ Outputs: [n_nationkey:integer]
│ Estimates: {rows: 5 (60B)}
│ Distribution: REPLICATED
├─ Project[]
│ │ Outputs: [n_nationkey:integer, n_regionkey:integer, $hashvalue_0_7:bigint]
│ │ Estimates: {rows: 25 (200B)}
│ │ $hashvalue_7 := `combine_hash`(BIGINT '0', COALESCE(`$operator$hash_code`(`n_regionkey`), 0))
│ └─ RuntimeFilter
│ │ Outputs: [n_nationkey:integer, n_regionkey:integer]
│ │ Estimates: {rows: 25 (200B)}
│ ├─ TableScan[adb:AdbTableHandle{schema=tpch, tableName=nation, partitionColumnHandles=[]}]
│ │ Outputs: [n_nationkey:integer, n_regionkey:integer]
│ │ Estimates: {rows: 25 (200B)}
│ │ n_nationkey := AdbColumnHandle{columnName=n_nationkey, type=4, isIndexed=true}
│ │ n_regionkey := AdbColumnHandle{columnName=n_regionkey, type=4, isIndexed=true}
│ └─ RuntimeCollect
│ │ Outputs: [r_regionkey:integer]
│ │ Estimates: {rows: 1 (4B)}
│ └─ LocalExchange[ROUND_ROBIN] ()
│ │ Outputs: [r_regionkey:integer]
│ │ Estimates: {rows: 1 (4B)}
│ └─ RuntimeScan
│ Outputs: [r_regionkey:integer]
│ Estimates: {rows: 1 (4B)}
└─ LocalExchange[HASH][$hashvalue_0_8] ("r_regionkey")
│ Outputs: [r_regionkey:integer, $hashvalue_0_8:bigint]
│ Estimates: {rows: 1 (4B)}
└─ ScanProject[table = adb:AdbTableHandle{schema=tpch, tableName=region, partitionColumnHandles=[]}]
Outputs: [r_regionkey:integer, $hashvalue_0_9:bigint]
Estimates: {rows: 1 (4B)}/{rows: 1 (B)}
$hashvalue_9 := `combine_hash`(BIGINT '0', COALESCE(`$operator$hash_code`(`r_regionkey`), 0))
r_regionkey := AdbColumnHandle{columnName=r_regionkey, type=4, isIndexed=true}| Parameter | Deskripsi |
|---|---|
Outputs: [symbol:type] | Kolom output dan tipe data dari setiap operator. |
Estimates: {rows: %s (%sB)} | Perkiraan jumlah baris dan volume data dari pengoptimal untuk setiap operator. Pengoptimal menggunakan estimasi ini untuk menentukan urutan join dan strategi pengacakan data. |
EXPLAIN ANALYZE
EXPLAIN ANALYZE menjalankan kueri dan mengembalikan biaya eksekusi terdistribusi yang sebenarnya—durasi eksekusi, penggunaan memori puncak, serta jumlah baris input/output untuk setiap operator dan fragment.
Sintaksis
EXPLAIN ANALYZE <SELECT statement>;Contoh
EXPLAIN ANALYZE
SELECT count(*)
FROM nation, region, customer
WHERE c_nationkey = n_nationkey
AND n_regionkey = r_regionkey
AND r_name = 'ASIA';Output diorganisasikan berdasarkan fragment—setiap fragment berkorespondensi dengan satu tahap dari rencana eksekusi terdistribusi. Untuk setiap operator, Anda dapat melihat jumlah baris aktual, penggunaan memori, dan wall time bersama estimasi dari pengoptimal. Bandingkan Estimates dengan Output untuk mengidentifikasi di mana model pengoptimal menyimpang dari eksekusi nyata, dan gunakan PeakMemory serta WallTime untuk menemukan operator yang paling mahal.
Fragment 1 [SINGLE]
Output: 1 row (9B), PeakMemory: 178KB, WallTime: 1.00ns, Input: 32 rows (288B); per task: avg.: 32.00 std.dev.: 0.00
Output layout: [count]
Output partitioning: SINGLE []
Aggregate(FINAL)
│ Outputs: [count:bigint]
│ Estimates: {rows: 1 (8B)}
│ Output: 2 rows (18B), PeakMemory: 24B (0.00%), WallTime: 70.39us (0.03%)
│ count := count(`count_1`)
└─ LocalExchange[SINGLE] ()
│ Outputs: [count1:bigint]
│ Estimates: {rows: ? (?)}
│ Output: 64 rows (576B), PeakMemory: 8KB (0.07%), WallTime: 238.69us (0.10%)
└─ RemoteSource[2]
Outputs: [count2:bigint]
Estimates:
Output: 32 rows (288B), PeakMemory: 32KB (0.27%), WallTime: 182.82us (0.08%)
Input avg.: 4.00 rows, Input std.dev.: 264.58%
Fragment 2 [adb:AdbPartitioningHandle{schema=tpch, tableName=customer, dimTable=false, shards=32, tableEngineType=Cstore, partitionColumns=c_custkey, prunedBuckets= empty}]
Output: 32 rows (288B), PeakMemory: 6MB, WallTime: 164.00ns, Input: 1500015 rows (20.03MB); per task: avg.: 500005.00 std.dev.: 21941.36
Output layout: [count4]
Output partitioning: SINGLE []
Aggregate(PARTIAL)
│ Outputs: [count4:bigint]
│ Estimates: {rows: 1 (8B)}
│ Output: 64 rows (576B), PeakMemory: 336B (0.00%), WallTime: 1.01ms (0.42%)
│ count_4 := count(*)
└─ INNER Join[(`c_nationkey` = `n_nationkey`)][$hashvalue, $hashvalue6]
│ Outputs: []
│ Estimates: {rows: 302035 (4.61MB)}
│ Output: 300285 rows (210B), PeakMemory: 641KB (5.29%), WallTime: 99.08ms (41.45%)
│ Left (probe) Input avg.: 46875.00 rows, Input std.dev.: 311.24%
│ Right (build) Input avg.: 0.63 rows, Input std.dev.: 264.58%
│ Distribution: REPLICATED
├─ ScanProject[table = adb:AdbTableHandle{schema=tpch, tableName=customer, partitionColumnHandles=[c_custkey]}]
│ Outputs: [c_nationkey:integer, $hashvalue:bigint]
│ Estimates: {rows: 1500000 (5.72MB)}/{rows: 1500000 (5.72MB)}
│ Output: 1500000 rows (20.03MB), PeakMemory: 5MB (44.38%), WallTime: 68.29ms (28.57%)
│ $hashvalue := `combine_hash`(BIGINT '0', COALESCE(`$operator$hash_code`(`c_nationkey`), 0))
│ c_nationkey := AdbColumnHandle{columnName=c_nationkey, type=4, isIndexed=true}
│ Input: 1500000 rows (7.15MB), Filtered: 0.00%
└─ LocalExchange[HASH][$hashvalue6] ("n_nationkey")
│ Outputs: [n_nationkey:integer, $hashvalue6:bigint]
│ Estimates: {rows: 5 (60B)}
│ Output: 30 rows (420B), PeakMemory: 394KB (3.26%), WallTime: 455.03us (0.19%)
└─ Project[]
│ Outputs: [n_nationkey:integer, $hashvalue10:bigint]
│ Estimates: {rows: 5 (60B)}
│ Output: 15 rows (210B), PeakMemory: 24KB (0.20%), WallTime: 83.61us (0.03%)
│ Input avg.: 0.63 rows, Input std.dev.: 264.58%
│ $hashvalue_10 := `combine_hash`(BIGINT '0', COALESCE(`$operator$hash_code`(`n_nationkey`), 0))
└─ RemoteSource[3]
Outputs: [n_nationkey:integer]
Estimates:
Output: 15 rows (75B), PeakMemory: 24KB (0.20%), WallTime: 45.97us (0.02%)
Input avg.: 0.63 rows, Input std.dev.: 264.58%
Fragment 3 [adb:AdbPartitioningHandle{schema=tpch, tableName=nation, dimTable=true, shards=32, tableEngineType=Cstore, partitionColumns=, prunedBuckets= empty}]
Output: 5 rows (25B), PeakMemory: 185KB, WallTime: 1.00ns, Input: 26 rows (489B); per task: avg.: 26.00 std.dev.: 0.00
Output layout: [n_nationkey]
Output partitioning: BROADCAST []
INNER Join[(`n_regionkey` = `r_regionkey`)][$hashvalue7, $hashvalue8]
│ Outputs: [n_nationkey:integer]
│ Estimates: {rows: 5 (60B)}
│ Output: 11 rows (64B), PeakMemory: 152KB (1.26%), WallTime: 255.86us (0.11%)
│ Left (probe) Input avg.: 25.00 rows, Input std.dev.: 0.00%
│ Right (build) Input avg.: 0.13 rows, Input std.dev.: 264.58%
│ Distribution: REPLICATED
├─ ScanProject[table = adb:AdbTableHandle{schema=tpch, tableName=nation, partitionColumnHandles=[]}]
│ Outputs: [n_nationkey:integer, n_regionkey:integer, $hashvalue7:bigint]
│ Estimates: {rows: 25 (200B)}/{rows: 25 (200B)}
│ Output: 25 rows (475B), PeakMemory: 16KB (0.13%), WallTime: 178.81us (0.07%)
│ $hashvalue_7 := `combine_hash`(BIGINT '0', COALESCE(`$operator$hash_code`(`n_regionkey`), 0))
│ n_nationkey := AdbColumnHandle{columnName=n_nationkey, type=4, isIndexed=true}
│ n_regionkey := AdbColumnHandle{columnName=n_regionkey, type=4, isIndexed=true}
│ Input: 25 rows (250B), Filtered: 0.00%
└─ LocalExchange[HASH][$hashvalue8] ("r_regionkey")
│ Outputs: [r_regionkey:integer, $hashvalue8:bigint]
│ Estimates: {rows: 1 (4B)}
│ Output: 2 rows (28B), PeakMemory: 34KB (0.29%), WallTime: 57.41us (0.02%)
└─ ScanProject[table = adb:AdbTableHandle{schema=tpch, tableName=region, partitionColumnHandles=[]}]
Outputs: [r_regionkey:integer, $hashvalue9:bigint]
Estimates: {rows: 1 (4B)}/{rows: 1 (4B)}
Output: 1 row (14B), PeakMemory: 8KB (0.07%), WallTime: 308.99us (0.13%)
$hashvalue_9 := `combine_hash`(BIGINT '0', COALESCE(`$operator$hash_code`(`r_regionkey`), 0))
r_regionkey := AdbColumnHandle{columnName=r_regionkey, type=4, isIndexed=true}
Input: 1 row (5B), Filtered: 0.00%| Parameter | Apa yang ditunjukkannya |
|---|---|
Outputs: [symbol:type] | Kolom output dan tipe data dari setiap operator. |
Estimates: {rows: %s (%sB)} | Perkiraan jumlah baris dan volume data dari pengoptimal. Bandingkan dengan Output untuk melihat di mana estimasi menyimpang dari eksekusi aktual. |
PeakMemory: %s | Memori puncak yang digunakan oleh operator atau fragment. Gunakan ini untuk mengidentifikasi konsumen memori utama saat mendiagnosis bottleneck memori. |
WallTime: %s | Durasi eksekusi total operator. Durasi ini bukan durasi eksekusi aktual karena komputasi paralel. Gunakan untuk menganalisis penyebab bottleneck selama operasi komputasi. |
Input: %s rows (%sB) | Jumlah baris dan byte yang dibaca sebagai input. |
per task: avg.: %s std.dev.: %s | Rata-rata baris input per task dan deviasi standar di seluruh task. Deviasi standar yang tinggi menunjukkan kesenjangan data—beberapa task memproses jauh lebih banyak data daripada yang lain, sehingga mengurangi efisiensi paralel. |
Output: %s row (%sB) | Jumlah baris dan byte yang dihasilkan sebagai output. |
Diagnosis masalah umum
Filter tidak didorong ke bawah
Saat filter tidak dapat didorong ke lapisan penyimpanan, kueri memindai seluruh tabel lalu menerapkan filter di memori—secara signifikan meningkatkan penggunaan I/O dan memori.
Dua kueri berikut mengilustrasikan perbedaannya:
SQL 1 — filter pada nilai kolom mentah (dapat didorong ke bawah):
SELECT count(*) FROM test WHERE string_test = 'a';SQL 2 — filter dibungkus dalam fungsi (tidak dapat didorong ke bawah):
SELECT count(*) FROM test WHERE length(string_test) = 1;
Jalankan EXPLAIN ANALYZE pada kedua kueri dan bandingkan Fragment 2.
Output SQL 1 (filter didorong ke bawah): Fragment menggunakan operator TableScan dan Input avg. bernilai 0.00 rows, artinya lapisan penyimpanan telah memfilter semua baris sebelum mengembalikan data.
Fragment 2 [adb:AdbPartitioningHandle{schema=test4dmp, tableName=test, dimTable=false, shards=4, tableEngineType=Cstore, partitionColumns=id, prunedBuckets= empty}]
Output: 4 rows (36B), PeakMemory: 0B, WallTime: 6.00ns, Input: 0 rows (0B); per task: avg.: 0.00 std.dev.: 0.00
Output layout: [count_0_1]
Output partitioning: SINGLE []
Aggregate(PARTIAL)
│ Outputs: [count_0_1:bigint]
│ Estimates: {rows: 1 (8B)}
│ Output: 8 rows (72B), PeakMemory: 0B (0.00%), WallTime: 212.92us (3.99%)
│ count_0_1 := count(*)
└─ TableScan[adb:AdbTableHandle{schema=test4dmp, tableName=test, partitionColumnHandles=[id]}]
Outputs: []
Estimates: {rows: 4 (0B)}
Output: 0 rows (0B), PeakMemory: 0B (0.00%), WallTime: 4.76ms (89.12%)
Input avg.: 0.00 rows, Input std.dev.: ?%Output SQL 2 (filter tidak didorong ke bawah): Fragment menggunakan ScanFilterProject alih-alih TableScan, dan Input menunjukkan 9999 rows. Bidang filterPredicate mengonfirmasi bahwa filter diterapkan setelah pemindaian, bukan di lapisan penyimpanan.
Fragment 2 [adb:AdbPartitioningHandle{schema=test4dmp, tableName=test, dimTable=false, shards=4, tableEngineType=Cstore, partitionColumns=id, prunedBuckets= empty}]
Output: 4 rows (36B), PeakMemory: 0B, WallTime: 102.00ns, Input: 0 rows (0B); per task: avg.: 0.00 std.dev.: 0.00
Output layout: [count_0_1]
Output partitioning: SINGLE []
Aggregate(PARTIAL)
│ Outputs: [count_0_1:bigint]
│ Estimates: {rows: 1 (8B)}
│ Output: 8 rows (72B), PeakMemory: 0B (0.00%), WallTime: 252.23us (0.12%)
│ count_0_1 := count(*)
└─ ScanFilterProject[table = adb:AdbTableHandle{schema=test4dmp, tableName=test, partitionColumnHandles=[id]}, filterPredicate = (`test4dmp`.`length`(`string_test`) = BIGINT '1')]
Outputs: []
Estimates: {rows: 9999 (312.47kB)}/{rows: 9999 (312.47kB)}/{rows: ? (?)}
Output: 0 rows (0B), PeakMemory: 0B (0.00%), WallTime: 101.31ms (49.84%)
string_test := AdbColumnHandle{columnName=string_test, type=13, isIndexed=true}
Input: 9999 rows (110.32kB), Filtered: 100.00%Untuk mendiagnosis pola ini, cari ScanFilterProject dalam output. Jika Input menunjukkan jumlah baris besar dan Filtered mendekati 100%, kueri memindai jauh lebih banyak data daripada yang dibutuhkan. Tulis ulang filter agar menggunakan bentuk yang dapat didorong ke bawah, atau tambahkan kondisi yang mempersempit pemindaian.
Penggunaan memori tinggi
Gunakan PeakMemory pada tingkat fragment untuk mengidentifikasi tahap yang paling banyak mengonsumsi memori. Penyebab umum tingginya PeakMemory meliputi:
Join broadcast yang tidak disengaja sehingga mengakibatkan konsumsi memori tinggi
Hasil join antara yang besar akibat pembengkakan data
Tabel probe-side (kiri) yang besar dalam join
TableScanmembaca volume data yang besar
Setelah mengidentifikasi fragment tersebut, gunakan PeakMemory per operator untuk menentukan operator spesifik yang mendorong konsumsi. Tambahkan kondisi filter berdasarkan kebutuhan bisnis untuk membatasi volume data, atau restrukturisasi kueri guna mengurangi ukuran hasil antara.