Какой запрос следует использовать? Вывод из MySQL Объясните - PullRequest
1 голос
/ 16 июня 2019

Объяснение главы MySQL Explain в книге по оптимизации статистики SQL, в конце которой есть этот вопрос.

Ниже приведен пример бизнес-потребности, которая возвращает потерянные родительские записи в отношениях родитель / потомок.Этот SQL-запрос может быть написан тремя различными способами.В то время как выходные данные дают одинаковые результаты, QEP показывает три различных пути.

mysql> EXPLAIN SELECT p.*
    -> FROM parent p
    -> WHERE p.id NOT IN (SELECT c.parent_id FROM child c)\G
*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: p
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 160
        Extra: Using where
*************************** 2. row ***************************
           id: 2
  select_type: DEPENDENT SUBQUERY
        table: c
         type: index_subquery
possible_keys: parent_id
          key: parent_id
      key_len: 4
          ref: func
         rows: 1
        Extra: Using index
2 rows in set (0.00 sec)



mysql> EXPLAIN SELECT p.*
    -> FROM parent p
    -> LEFT JOIN child c ON p.id = c.parent_id
    -> WHERE c.child_id IS NULL\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: p
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 160
        Extra:
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: c
         type: ref
possible_keys: parent_id
          key: parent_id
      key_len: 4
          ref: test.p.id
         rows: 1
        Extra: Using where; Using index; Not exists
2 rows in set (0.00 sec)



mysql> EXPLAIN SELECT p.*
    -> FROM parent p
    -> WHERE NOT EXISTS
    -> SELECT parent_id FROM child c WHERE c.parent_id = p.id)\G
*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: p
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 160
        Extra: Using where
*************************** 2. row ***************************
           id: 2
  select_type: DEPENDENT SUBQUERY
        table: c
         type: ref
possible_keys: parent_id
          key: parent_id
      key_len: 4
          ref: test.p.id
         rows: 1
        Extra: Using index
2 rows in set (0.00 sec)

Что лучше?Приведет ли рост данных с течением времени к тому, что другой QEP будет работать лучше?

Насколько мне известно, в книге или Интернете нет ответа.

Ответы [ 2 ]

2 голосов
/ 17 июня 2019

Это зависит от того, какую версию MySQL вы используете.В старых версиях IN ( SELECT ...) работал ужасно.В последней версии это часто так же хорошо, как и другие варианты.Кроме того, MariaDB имеет некоторые различия в оптимизации, вероятно, в этой области.

EXISTS( SELECT 1 ... ), пожалуй, является самым ясным в формулировке намерения .И, возможно, оно всегда (как только оно появилось) было быстрым.

NOT IN и NOT EXISTS - это разные животные.

Некоторые вещи в вашем Вопросе, которые могут оказать влияние: func и index_subquery.В похожих запросах вы можете их не видеть, и это различие может привести к различиям в производительности.

Или, повторюсь:

"С 2009 года в оптимизаторе произошел ряд улучшений.

"Автору (Quassnoi): Пожалуйста, перезапустите тесты и укажите, с какой версией они запускаются.Также обратите внимание, что MySQL и MariaDB могут давать разные результаты.

«Читателю: сами протестируйте варианты, не доверяйте слепо выводам в этом блоге».

2 голосов
/ 16 июня 2019

Существует старая статья 2009 года , которую я видел связанной с stackoverflow много раз. Тест показывает, что запрос NOT EXISTS на 27% (на самом деле это 26%) медленнее, чем два других запроса (LEFT JOIN и NOT IN).

Однако оптимизатор был улучшен от версии к версии. И идеальный оптимизатор создаст один и тот же план выполнения для всех трех запросов. Но пока оптимизатор не совершенен, ответ на вопрос "Какой запрос быстрее?" может зависеть от фактической настройки (которая включает версию, настройки и данные).

В прошлом я проводил подобные тесты, и все, что я помню, это то, что LEFT JOIN никогда не был значительно медленнее, чем любой другой метод. Но из любопытства я только что создал новый тест для MariaDB 10.3.13 портативной версии Windows с настройками по умолчанию.

Фиктивные данные:

set @parents = 1000;

drop table if exists parent;
create table parent(
    parent_id mediumint unsigned primary key
);
insert into parent(parent_id)
    select seq
    from seq_1_to_1000000
    where seq <= @parents
;

drop table if exists child;
create table child(
    child_id mediumint unsigned primary key,
    parent_id mediumint unsigned not null,
    index (parent_id)
);
insert into child(child_id, parent_id)
    select seq as child_id
    , floor(rand(1)*@parents)+1 as parent_id
    from seq_1_to_1000000
;

НЕ В:

set @start = TIME(SYSDATE(6));

select count(*) into @cnt
from parent p
where p.parent_id not in (select parent_id from child c);

select @cnt, TIMEDIFF(TIME(SYSDATE(6)), @start);

LEFT JOIN:

set @start = TIME(SYSDATE(6));

select count(*) into @cnt
from parent p
left join child c on c.parent_id = p.parent_id
where c.parent_id is null;

select @cnt, TIMEDIFF(TIME(SYSDATE(6)), @start);

НЕ СУЩЕСТВУЕТ:

set @start = TIME(SYSDATE(6));

select count(*) into @cnt
from parent p
where not exists (
    select *
    from child c
    where c.parent_id = p.parent_id
);

select @cnt, TIMEDIFF(TIME(SYSDATE(6)), @start);

Время выполнения в миллисекундах:

@parents   | 1000 | 10000 | 100000 | 1000000
-----------|------|-------|--------|--------
NOT IN     |   21 |    38 |    175 |    4459
LEFT JOIN  |   24 |    40 |    183 |    1508
NOT EXISTS |   26 |    44 |    180 |    4463

Я выполнил запросы несколько раз и принял наименьшее значение времени. И SYSDATE, вероятно, не лучший метод для измерения времени выполнения - так что не принимайте эти цифры как точные. Тем не менее, мы видим, что до 100K родительских строк разница невелика, а метод NOT IN немного быстрее. Но с 1М родительскими строками LEFT JOIN в три раза быстрее.

Заключение

Так, каков ответ? Я мог бы просто сказать: «LEFT JOIN» побеждает. Но правда в том, что этот тест ничего не доказывает. И ответ (так много раз): «Это зависит». Когда производительность имеет значение, лучшее, что вы можете сделать, это запустить свои собственные тесты с реальными запросами к реальным данным. Если у вас нет реальных данных (пока), вам следует создать фиктивные данные с объемом и распределением, которые вы ожидаете получить в будущем.

...