Mysql
 sql >> база данни >  >> RDS >> Mysql

Отделно срещу групиране по

Обикновено се препоръчва да използвате DISTINCT вместо GROUP BY , тъй като това всъщност искате и оставете оптимизатора да избере "най-добрия" план за изпълнение. Въпреки това - нито един оптимизатор не е перфектен. Използване на DISTINCT оптимизаторът може да има повече опции за план за изпълнение. Но това също означава, че има повечеопции за избор на лош план .

Пишете, че DISTINCT заявката е "бавна", но не казвате никакви числа. В моя тест (с 10 пъти повече реда на MariaDB 10.0.19 и 10.3.13 ) DISTINCT заявката е като (само) 25% по-бавна (562ms/453ms). EXPLAIN резултатът изобщо не помага. Дори е "лъже". С LIMIT 100, 30 ще трябва да прочете поне 130 реда (това е моето EXPLAIN всъщност показва за GROUP BY ), но ви показва 65.

Не мога да обясня разликата от 25% във времето за изпълнение, но изглежда, че машината прави пълно сканиране на таблица/индекс във всеки случай и сортира резултата, преди да може да пропусне 100 и да избере 30 реда.

Най-добрият план вероятно би бил:

  • Четене на редове от idx_reg_date индекс (таблица A ) един по един в низходящ ред
  • Вижте дали има съвпадение в idx_order_id индекс (таблица B )
  • Пропуснете 100 съвпадащи реда
  • Изпратете 30 съвпадащи реда
  • Изход

Ако има около 10% редове в A които нямат съвпадение в B , този план ще прочете нещо като 143 реда от A .

Най-доброто, което мога да направя, за да наложа по някакъв начин този план, е:

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 (3 пъти по-бързо от GROUP BY ). Но това все още е твърде бавно. И вероятно все още чете всички редове в таблица A .

Можем да докажем, че може да съществува по-добър план с „малък“ трик с подзаявка:

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) и връща същия резултат за моите тестови данни. И въпреки че не е 100% надежден, това показва, че оптимизаторът не върши добре работата.

И така, какви са моите заключения:

  • Оптимизаторът не винаги върши най-добрата работа и понякога има нужда от помощ
  • Дори когато знаем „най-добрия план“, не винаги можем да го приложим
  • DISTINCT не винаги е по-бърз от GROUP BY
  • Когато не може да се използва индекс за всички клаузи - нещата стават доста трудни

Тестова схема и фиктивни данни:

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;

Запитвания:

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. MySQL Utilities - файл с опции ~/.my.cnf

  2. Как да влезете автоматично в MySQL от шел скрипт?

  3. Промяна на големи MySQL InnoDB таблици

  4. MySQL проверява дали два периода от време се припокриват с въвеждането

  5. Разбиране на дневника за одит на ProxySQL