Mysql
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Mysql

Phân biệt so với Nhóm theo

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 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ảng A ) 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ảng B )
  • 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ơn GROUP 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



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Tôi có thể giải quyết vấn đề này bằng mysql thuần túy không? (tham gia vào '' các giá trị được phân tách trong một cột)

  2. Chọn kết hợp 2 cột riêng biệt trong mysql

  3. Truy vấn MySQL sử dụng CASE để CHỌN nhiều cột

  4. Xóa các hàng trùng lặp khỏi bảng

  5. Trích xuất Ngày / Tháng / Năm từ Dấu thời gian trên MYSQL