ОБНОВЛЕНИЕ с ЛЕВЫМ СОЕДИНЕНИЕМ и НУЛЕВЫМ значением приводит к чрезвычайно низкой производительности

#mysql #sql

Вопрос:

Сегодня у нас была серьезная проблема с производительностью, вызванная этим точным запросом (с разными именами, конечно).:

 UPDATE foo LEFT JOIN bar  ON bar.foo_id = foo.id SET foo.flag = 1 WHERE bar.foo_id IS NULL;  

Программное обеспечение является сторонним, и мы не писали код или схему базы данных.

foo имеет 42 тыс. строк, bar имеет 130 тыс. Частность foo.id происходила очень часто в bar.foo_id . ( bar является таблицей отношений, реализующей отношение m:n между foo и qux . Почти каждая строка qux будет иметь отношение к определенной foo строке, а затем к нескольким другим, уникальным для нее. Там может быть больше дубликатов, скорее всего, не важных.) Существует указатель на foo.id . bar.foo_id является частью индекса, также содержащего bar.qux_id и не связанного bar.text .

Запрос выполнялся в течение 55 минут на 100% процессоре db.t3.medium экземпляра, использующего Aurora MySQL 5.7. В slowlog указано Rows_examined: 5719954827 .

Может ли кто-нибудь объяснить, в чем именно причина этого числа и, как следствие, низкой производительности? 42k * 130k близко к этому, так что, вероятно, это как-то связано с этим.

В нашем случае мы могли бы просто GROUP BY foo_id и затем присоединиться к результату, вместо bar того, чтобы решать проблему. Однако это будет невозможно при любых обстоятельствах — и я не думаю, что создание индекса bar.foo_id должно иметь эффект, поскольку NULL значения могут возникать только из-за JOIN оператора.

EXPLAIN Результат выглядит следующим образом:

 id|select_type|table |partitions|type |possible_keys|key |key_len|ref|rows |filtered|Extra | -- ----------- ----------------- ---------- ----- ------------- --------------------- ------- --- ------ -------- ------------------------   1|UPDATE |foo | |ALL | | | | | 42921| 100.0| |  1|SIMPLE |bar | |index| |uidx_qux_foo |780 | |129465| 10.0|Using where; Using index|  

Насколько мне известно, EXPLAIN PLAN он недоступен в MySQL.

Комментарии:

1. Ваш запрос имеет запах. В баре должен быть пк, поэтому вы должны проверять bar.pk или любой другой ненулевой столбец.

2. Меня конкретно интересует причина, а не решение — как я уже сказал, мы не писали код, и он уже улучшен без изменения схемы. И в баре действительно есть ПК — я не вижу, как это здесь поможет. Цель запроса-пометить строки в foo, на которые нет ссылок в qux, поэтому, глядя на эти несколько битов кода, они должны были просто использоваться ПРИ УДАЛЕНИИ.

3. Это можно было бы написать лучше, используя not exists

4. Посмотрите (и напишите в своем вопросе) ПЛАН ОБЪЯСНЕНИЙ, который должен показать, что происходило

5. Как вы заметили, очень большое количество проверенных строк приближается к произведению ваших двух таблиц. Это связано с полным сканированием таблицы, вызванным отсутствием индекса на bar.foo_id

Ответ №1:

Вы записываете поля только в одну таблицу с подходящим названием foo , поэтому напишите свой запрос , чтобы искать/сканировать только один раз в этой таблице:

 update foo set flag = 1 where ...  

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

И как только вы исправите свою общую логику, у вас останется наиболее типичный шаблон доступа к базе данных: предоставление правильных индексов для вашего select оператора (т. Е. части после where выше). Простое explain plan должно показать вам, в чем именно заключается проблема, если таковая имеется. Ваш пост здесь совершенно не поможет.

Ответ №2:

Rows_examined: 5719954827 это сильный показатель.

Измените запрос на это «полу-соединение». Ему не нужно будет повторно устанавливать foo.флаг, но он будет выполнять int только один раз за строку в foo.

 UPDATE foo  SET foo.flag = 1  WHERE NOT EXISTS ( SELECT 1 FROM bar  WHERE bar.foo_id = foo.id );  

Это, вероятно, даст вам что-то вроде «проверено 84 тысячи строк» (84 тысячи = 2*42 тысячи).

И bar действительно нуждается INDEX в том, чтобы начать foo_id с.

Зачем вам нужен этот флаг? Зачем вам нужно менять сразу много строк? Разве вы не можете разработать приложение каким-то другим способом?

Для дальнейшего обсуждения, пожалуйста, предоставьте SHOW CREATE TABLE .