Bạn thường nên sử dụng DISTINCT
thay vì GROUP BY
, vì đó là những gì bạn thực sự muốn và hãy để trình tối ưu hóa chọn kế hoạch thực thi "tốt nhất". Tuy nhiên - không có trình tối ưu hóa nào là hoàn hảo. Sử dụng DISTINCT
trình tối ưu hóa có thể có nhiều tùy chọn hơn cho một kế hoạch thực thi. Nhưng điều đó cũng có nghĩa là nó có nhiều tùy chọn để chọn một kế hoạch tồi hơn .
Bạn viết rằng DISTINCT
truy vấn "chậm", nhưng bạn không cho biết bất kỳ con số nào. Trong thử nghiệm của tôi (với số hàng gấp 10 lần trên MariaDB 10.0.19 và 10.3.13 ) DISTINCT
truy vấn giống như (chỉ) chậm hơn 25% (562ms / 453ms). EXPLAIN
kết quả là không giúp được gì cả. Nó thậm chí là "nói dối". Với LIMIT 100, 30
nó sẽ cần đọc ít nhất 130 hàng (đó là những gì EXPLAIN
của tôi thực sự tìm kiếm cho GROUP BY
), nhưng nó hiển thị cho bạn 65.
Tôi không thể giải thích sự khác biệt 25% về thời gian thực thi, nhưng có vẻ như công cụ đang thực hiện quét toàn bộ bảng / chỉ mục trong mọi trường hợp và sắp xếp kết quả trước khi nó có thể bỏ qua 100 và chọn 30 hàng.
Kế hoạch tốt nhất có lẽ sẽ là:
- Đọc các hàng từ
idx_reg_date
chỉ mục (bảngA
) từng cái một theo thứ tự giảm dần - Xem có khớp nào trong
idx_order_id
không chỉ mục (bảngB
) - Bỏ qua 100 hàng phù hợp
- Gửi 30 hàng phù hợp
- Thoát
Nếu có 10% hàng trong A
không khớp trong B
, kế hoạch này sẽ đọc một cái gì đó giống như 143 hàng từ A
.
Tốt nhất tôi có thể làm bằng cách nào đó để bắt buộc kế hoạch này là:
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
Truy vấn này trả về cùng một kết quả trong 156 mili giây (nhanh hơn 3 lần so với GROUP BY
). Nhưng điều đó vẫn còn quá chậm. Và nó vẫn đọc tất cả các hàng trong bảng A
.
Chúng tôi có thể chứng minh rằng một kế hoạch tốt hơn có thể tồn tại bằng một thủ thuật truy vấn con "nhỏ":
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
Truy vấn này thực hiện trong "không thời gian" (~ 0 ms) và trả về cùng một kết quả trên dữ liệu thử nghiệm của tôi. Và mặc dù nó không đáng tin cậy 100%, nhưng nó cho thấy rằng trình tối ưu hóa đang hoạt động không tốt.
Vậy kết luận của tôi là gì:
- Trình tối ưu hóa không phải lúc nào cũng hoạt động tốt nhất và đôi khi cần trợ giúp
- Ngay cả khi biết "kế hoạch tốt nhất", không phải lúc nào chúng ta cũng có thể thực thi nó
-
DISTINCT
không phải lúc nào cũng nhanh hơnGROUP BY
- Khi không thể sử dụng chỉ mục nào cho tất cả các mệnh đề - mọi thứ đang trở nên khá phức tạp
Lược đồ thử nghiệm và dữ liệu giả:
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;
Truy vấn:
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