Biasanya disarankan untuk menggunakan DISTINCT
bukannya GROUP BY
, karena itulah yang sebenarnya Anda inginkan, dan biarkan pengoptimal memilih rencana eksekusi "terbaik". Namun - tidak ada pengoptimal yang sempurna. Menggunakan DISTINCT
pengoptimal dapat memiliki lebih banyak opsi untuk rencana eksekusi. Namun itu juga berarti bahwa ia memiliki lebih banyak opsi untuk memilih paket yang buruk .
Anda menulis bahwa DISTINCT
kueri "lambat", tetapi Anda tidak memberi tahu angka apa pun. Dalam pengujian saya (dengan 10 kali lebih banyak baris di MariaDB 10.0.19 dan 10.3.13 ) DISTINCT
kueri seperti (hanya) 25% lebih lambat (562ms/453ms). EXPLAIN
hasilnya tidak membantu sama sekali. Itu bahkan "berbohong". Dengan LIMIT 100, 30
itu perlu membaca setidaknya 130 baris (itulah yang saya EXPLAIN
sebenarnya menampilkan GROUP BY
), tetapi ini menunjukkan kepada Anda 65.
Saya tidak dapat menjelaskan perbedaan 25% dalam waktu eksekusi, tetapi tampaknya mesin melakukan pemindaian tabel/indeks lengkap dalam hal apa pun, dan mengurutkan hasilnya sebelum dapat melewati 100 dan memilih 30 baris.
Rencana terbaik mungkin adalah:
- Baca baris dari
idx_reg_date
indeks (tabelA
) satu per satu dalam urutan menurun - Lihat apakah ada kecocokan di
idx_order_id
indeks (tabelB
) - Lewati 100 baris yang cocok
- Kirim 30 baris yang cocok
- Keluar
Jika ada seperti 10% dari baris di A
yang tidak memiliki kecocokan di B
, rencana ini akan membaca kira-kira 143 baris dari A
.
Hal terbaik yang bisa saya lakukan untuk memaksakan rencana ini adalah:
SELECT A.id
FROM `order` A
WHERE EXISTS (SELECT * FROM order_detail_products B WHERE A.id = B.order_id)
ORDER BY A.reg_date DESC
LIMIT 30
OFFSET 100
Kueri ini mengembalikan hasil yang sama dalam 156 mdtk (3 kali lebih cepat dari GROUP BY
). Tapi itu masih terlalu lambat. Dan mungkin masih membaca semua baris dalam tabel A
.
Kami dapat membuktikan bahwa rencana yang lebih baik dapat eksis dengan trik subquery "kecil":
SELECT A.id
FROM (
SELECT id, reg_date
FROM `order`
ORDER BY reg_date DESC
LIMIT 1000
) A
WHERE EXISTS (SELECT * FROM order_detail_products B WHERE A.id = B.order_id)
ORDER BY A.reg_date DESC
LIMIT 30
OFFSET 100
Kueri ini dijalankan dalam "tidak ada waktu" (~ 0 ms) dan mengembalikan hasil yang sama pada data pengujian saya. Dan meskipun tidak 100% dapat diandalkan, ini menunjukkan bahwa pengoptimal tidak bekerja dengan baik.
Jadi apa kesimpulan saya:
- Pengoptimal tidak selalu melakukan pekerjaan terbaik dan terkadang membutuhkan bantuan
- Bahkan ketika kita tahu "rencana terbaik", kita tidak selalu bisa menjalankannya
DISTINCT
tidak selalu lebih cepat dariGROUP BY
- Bila tidak ada indeks yang dapat digunakan untuk semua klausa - semuanya menjadi sangat rumit
Skema pengujian dan data dummy:
drop table if exists `order`;
CREATE TABLE `order` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`reg_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_reg_date` (`reg_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
insert into `order`(reg_date)
select from_unixtime(floor(rand(1) * 1000000000)) as reg_date
from information_schema.COLUMNS a
, information_schema.COLUMNS b
limit 218860;
drop table if exists `order_detail_products`;
CREATE TABLE `order_detail_products` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`order_id` bigint(20) unsigned NOT NULL,
`order_detail_id` int(11) NOT NULL,
`prod_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_order_detail_id` (`order_detail_id`,`prod_id`),
KEY `idx_order_id` (`order_id`,`order_detail_id`,`prod_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
insert into order_detail_products(id, order_id, order_detail_id, prod_id)
select null as id
, floor(rand(2)*218860)+1 as order_id
, 0 as order_detail_id
, 0 as prod_id
from information_schema.COLUMNS a
, information_schema.COLUMNS b
limit 437320;
Permintaan:
SELECT DISTINCT A.id
FROM `order` A
JOIN order_detail_products B ON A.id = B.order_id
ORDER BY A.reg_date DESC
LIMIT 30 OFFSET 100;
-- 562 ms
SELECT A.id
FROM `order` A
JOIN order_detail_products B ON A.id = B.order_id
GROUP BY A.id
ORDER BY A.reg_date DESC
LIMIT 30 OFFSET 100;
-- 453 ms
SELECT A.id
FROM `order` A
WHERE EXISTS (SELECT * FROM order_detail_products B WHERE A.id = B.order_id)
ORDER BY A.reg_date DESC
LIMIT 30 OFFSET 100;
-- 156 ms
SELECT A.id
FROM (
SELECT id, reg_date
FROM `order`
ORDER BY reg_date DESC
LIMIT 1000
) A
WHERE EXISTS (SELECT * FROM order_detail_products B WHERE A.id = B.order_id)
ORDER BY A.reg_date DESC
LIMIT 30 OFFSET 100;
-- ~ 0 ms