MySQL: delete… where..in () против delete..from..join и заблокированные таблицы при удалении с помощью подвыбора

9

Отказ от ответственности: прошу прощения за отсутствие знаний о внутреннем устройстве базы данных. Здесь это идет:

Мы запускаем приложение (не написанное нами), которое имеет большую проблему с производительностью при периодической очистке базы данных. Запрос выглядит так:

delete from VARIABLE_SUBSTITUTION where BUILDRESULTSUMMARY_ID in (
       select BUILDRESULTSUMMARY_ID from BUILDRESULTSUMMARY
       where BUILDRESULTSUMMARY.BUILD_KEY = "BAM-1");

Прямой, удобный для чтения и стандартный SQL. Но, к сожалению, очень медленно. Объяснение запроса показывает, что существующий индекс VARIABLE_SUBSTITUTION.BUILDRESULTSUMMARY_IDне используется:

mysql> explain delete from VARIABLE_SUBSTITUTION where BUILDRESULTSUMMARY_ID in (
    ->        select BUILDRESULTSUMMARY_ID from BUILDRESULTSUMMARY
    ->        where BUILDRESULTSUMMARY.BUILD_KEY = "BAM-1");
| id | select_type        | table                 | type            | possible_keys                    | key     | key_len | ref  | rows    | Extra       |
+----+--------------------+-----------------------+-----------------+----------------------------------+---------+---------+------+---------+-------------+
|  1 | PRIMARY            | VARIABLE_SUBSTITUTION | ALL             | NULL                             | NULL    | NULL    | NULL | 7300039 | Using where |
|  2 | DEPENDENT SUBQUERY | BUILDRESULTSUMMARY    | unique_subquery | PRIMARY,key_number_results_index | PRIMARY | 8       | func |       1 | Using where |

Это делает его очень медленным (120 секунд и более). Кроме того, кажется, что он блокирует запросы, которые пытаются вставить в BUILDRESULTSUMMARYвывод show engine innodb status:

---TRANSACTION 68603695, ACTIVE 157 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s)
MySQL thread id 127964, OS thread handle 0x7facd0670700, query id 956555826 localhost 127.0.0.1 bamboosrv updating
update BUILDRESULTSUMMARY set CREATED_DATE='2015-06-18 09:22:05', UPDATED_DATE='2015-06-18 09:22:32', BUILD_KEY='BLA-RELEASE1-JOB1', BUILD_NUMBER=8, BUILD_STATE='Unknown', LIFE_CYCLE_STATE='InProgress', BUILD_DATE='2015-06-18 09:22:31.792', BUILD_CANCELLED_DATE=null, BUILD_COMPLETED_DATE='2015-06-18 09:52:02.483', DURATION=1770691, PROCESSING_DURATION=1770691, TIME_TO_FIX=null, TRIGGER_REASON='com.atlassian.bamboo.plugin.system.triggerReason:CodeChangedTriggerReason', DELTA_STATE=null, BUILD_AGENT_ID=199688199, STAGERESULT_ID=230943366, RESTART_COUNT=0, QUEUE_TIME='2015-06-18 09:22:04.52
------- TRX HAS BEEN WAITING 157 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 38 page no 30140 n bits 112 index `PRIMARY` of table `bamboong`.`BUILDRESULTSUMMARY` trx id 68603695 lock_mode X locks rec but not gap waiting
------------------
---TRANSACTION 68594818, ACTIVE 378 sec starting index read
mysql tables in use 2, locked 2
646590 lock struct(s), heap size 63993384, 3775190 row lock(s), undo log entries 117
MySQL thread id 127845, OS thread handle 0x7facc6bf8700, query id 956652201 localhost 127.0.0.1 bamboosrv preparing
delete from VARIABLE_SUBSTITUTION  where BUILDRESULTSUMMARY_ID in   (select BUILDRESULTSUMMARY_ID from BUILDRESULTSUMMARY where BUILDRESULTSUMMARY.BUILD_KEY = 'BLA-BLUBB10-SON')

Это замедляет работу системы и заставляет нас увеличиваться innodb_lock_wait_timeout.

Запустив MySQL, мы переписали запрос на удаление, чтобы использовать «delete from join»:

delete VARIABLE_SUBSTITUTION from VARIABLE_SUBSTITUTION join BUILDRESULTSUMMARY
   on VARIABLE_SUBSTITUTION.BUILDRESULTSUMMARY_ID = BUILDRESULTSUMMARY.BUILDRESULTSUMMARY_ID
   where BUILDRESULTSUMMARY.BUILD_KEY = "BAM-1";

Это немного проще для чтения, к сожалению, нет стандартного SQL (насколько мне удалось выяснить), но намного быстрее (0,02 секунды или около того), так как он использует индекс:

mysql> explain delete VARIABLE_SUBSTITUTION from VARIABLE_SUBSTITUTION join BUILDRESULTSUMMARY
    ->    on VARIABLE_SUBSTITUTION.BUILDRESULTSUMMARY_ID = BUILDRESULTSUMMARY.BUILDRESULTSUMMARY_ID
    ->    where BUILDRESULTSUMMARY.BUILD_KEY = "BAM-1";
| id | select_type | table                 | type | possible_keys                    | key                      | key_len | ref                                                    | rows | Extra                    |
+----+-------------+-----------------------+------+----------------------------------+--------------------------+---------+--------------------------------------------------------+------+--------------------------+
|  1 | SIMPLE      | BUILDRESULTSUMMARY    | ref  | PRIMARY,key_number_results_index | key_number_results_index | 768     | const                                                  |    1 | Using where; Using index |
|  1 | SIMPLE      | VARIABLE_SUBSTITUTION | ref  | var_subst_result_idx             | var_subst_result_idx     | 8       | bamboo_latest.BUILDRESULTSUMMARY.BUILDRESULTSUMMARY_ID |   26 | NULL                     |

Дополнительная информация:

mysql> SHOW CREATE TABLE VARIABLE_SUBSTITUTION;
| Table                 | Create Table |
| VARIABLE_SUBSTITUTION | CREATE TABLE `VARIABLE_SUBSTITUTION` (
  `VARIABLE_SUBSTITUTION_ID` bigint(20) NOT NULL,
  `VARIABLE_KEY` varchar(255) COLLATE utf8_bin NOT NULL,
  `VARIABLE_VALUE` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
  `VARIABLE_TYPE` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  `BUILDRESULTSUMMARY_ID` bigint(20) NOT NULL,
  PRIMARY KEY (`VARIABLE_SUBSTITUTION_ID`),
  KEY `var_subst_result_idx` (`BUILDRESULTSUMMARY_ID`),
  KEY `var_subst_type_idx` (`VARIABLE_TYPE`),
  CONSTRAINT `FK684A7BE0A958B29F` FOREIGN KEY (`BUILDRESULTSUMMARY_ID`) REFERENCES `BUILDRESULTSUMMARY` (`BUILDRESULTSUMMARY_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin |

mysql> SHOW CREATE TABLE BUILDRESULTSUMMARY;
| Table              | Create Table |
| BUILDRESULTSUMMARY | CREATE TABLE `BUILDRESULTSUMMARY` (
  `BUILDRESULTSUMMARY_ID` bigint(20) NOT NULL,
....
  `SKIPPED_TEST_COUNT` int(11) DEFAULT NULL,
  PRIMARY KEY (`BUILDRESULTSUMMARY_ID`),
  KEY `FK26506D3B9E6537B` (`CHAIN_RESULT`),
  KEY `FK26506D3BCCACF65` (`MERGERESULT_ID`),
  KEY `key_number_delta_state` (`DELTA_STATE`),
  KEY `brs_build_state_idx` (`BUILD_STATE`),
  KEY `brs_life_cycle_state_idx` (`LIFE_CYCLE_STATE`),
  KEY `brs_deletion_idx` (`MARKED_FOR_DELETION`),
  KEY `brs_stage_result_id_idx` (`STAGERESULT_ID`),
  KEY `key_number_results_index` (`BUILD_KEY`,`BUILD_NUMBER`),
  KEY `brs_agent_idx` (`BUILD_AGENT_ID`),
  KEY `rs_ctx_baseline_idx` (`VARIABLE_CONTEXT_BASELINE_ID`),
  KEY `brs_chain_result_summary_idx` (`CHAIN_RESULT`),
  KEY `brs_log_size_idx` (`LOG_SIZE`),
  CONSTRAINT `FK26506D3B9E6537B` FOREIGN KEY (`CHAIN_RESULT`) REFERENCES `BUILDRESULTSUMMARY` (`BUILDRESULTSUMMARY_ID`),
  CONSTRAINT `FK26506D3BCCACF65` FOREIGN KEY (`MERGERESULT_ID`) REFERENCES `MERGE_RESULT` (`MERGERESULT_ID`),
  CONSTRAINT `FK26506D3BCEDEEF5F` FOREIGN KEY (`STAGERESULT_ID`) REFERENCES `CHAIN_STAGE_RESULT` (`STAGERESULT_ID`),
  CONSTRAINT `FK26506D3BE3B5B062` FOREIGN KEY (`VARIABLE_CONTEXT_BASELINE_ID`) REFERENCES `VARIABLE_CONTEXT_BASELINE` (`VARIABLE_CONTEXT_BASELINE_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin |

(некоторые вещи опущены, это довольно широкий стол).

Итак, у меня есть несколько вопросов по этому поводу:

  • почему оптимизатор запросов не может использовать индекс для удаления, когда версия подзапроса, в то время как он использует версию соединения?
  • Есть ли (в идеале соответствие стандартам) способ обмануть его с помощью индекса? или
  • Есть ли портативный способ написать delete from join? Приложение поддерживает PostgreSQL, MySQL, Oracle и Microsoft SQL Server, используемые через jdbc и Hibernate.
  • почему удаление из VARIABLE_SUBSTITUTIONблокирующих вставок в BUILDRESULTSUMMARY, которое используется только в подвыбор?
0x89
источник
Percona Server 5.6.24-72.2-1.jessie или 5.6.24-72.2-1.wheezy (в тестовой системе).
0x89
Да, вся база данных использует innodb.
0x89
Таким образом, кажется, что 5.6 не уделял много внимания улучшению оптимизатора. Вам придется подождать 5.7 (но попробуйте MariaDB, если сможете. Их улучшения оптимизатора были сделаны в версиях 5.3 и 5.5.)
ypercubeᵀᴹ
@ypercube AFAIK no fork не имеет улучшения, позволяющего оптимизировать подзапрос удаления, ни 5.7. Удаляет оптимизацию иначе, чем операторы SELECT.
Морган Токер

Ответы:

7
  • почему оптимизатор запросов не может использовать индекс для удаления, когда версия подзапроса, в то время как он использует версию соединения?

Потому что оптимизатор был / был немного глуп в этом отношении. Не только DELETEи , UPDATEно и для SELECTзаявлений , а также, ничего подобного WHERE column IN (SELECT ...)не было полностью оптимизировано. План выполнения обычно включал запуск подзапроса для каждой строки внешней таблицы ( VARIABLE_SUBSTITUTIONв данном случае). Если эта таблица маленькая, все в порядке. Если оно большое, надежды нет. В более старых версиях INподзапрос с INвложенным подзапросом мог бы даже EXPLAINзапускаться целую вечность.

Что вы можете сделать - если хотите сохранить этот запрос - использовать последние версии, в которых реализовано несколько оптимизаций, и протестировать снова. Последние версии означают: MySQL 5.6 (и 5.7, когда он выйдет из бета-версии) и MariaDB 5.5 / 10.0

(обновление) Вы уже используете 5.6, в котором есть улучшения оптимизации, и этот уместен: Оптимизация подзапросов с преобразованиями полусоединения
Я предлагаю добавить индекс (BUILD_KEY)отдельно. Есть составной, но он не очень полезен для этого запроса.

  • Есть ли (в идеале соответствие стандартам) способ обмануть его с помощью индекса?

Ни о чем я не могу думать. На мой взгляд, пытаться использовать стандартный SQL не стоит. Существует так много различий и незначительных изысков, которые есть у каждой СУБД ( UPDATEа DELETEоператоры являются хорошими примерами таких различий), что когда вы пытаетесь использовать что-то, что работает везде, результатом является очень ограниченное подмножество SQL.

  • Есть ли портативный способ записи удаления из объединения? Приложение поддерживает PostgreSQL, MySQL, Oracle и Microsoft SQL Server, используемые через jdbc и Hibernate.

Тот же ответ, что и на предыдущий вопрос.

  • почему удаление из VARIABLE_SUBSTITUTION блокирует вставки в BUILDRESULTSUMMARY, который используется только в подвыборах?

Не уверен на 100%, но я думаю, что это связано с многократным запуском подзапроса и тем, какие блокировки он берет на стол.

ypercubeᵀᴹ
источник
«3775190 блокировка строки (s)» из innodb_status (из транзакции удаления) очень показательна. Но также "MySQL таблицы в использовании 2, заблокирован 2" не выглядит слишком хорошо для меня ..
0x89
2

вот ответы на два ваших вопроса

  • Оптимизатор не может использовать индекс, потому что предложение where изменяется для каждой строки. Оператор удаления будет выглядеть примерно так после прохождения оптимизатора

    delete from VARIABLE_SUBSTITUTION where EXISTS (
    select BUILDRESULTSUMMARY_ID from BUILDRESULTSUMMARY
    where BUILDRESULTSUMMARY.BUILD_KEY = BUILDRESULTSUMMARY_ID AND BUILDRESULTSUMMARY.BUILD_KEY = "BAM-1");

но когда вы выполняете соединение, сервер может идентифицировать строки, подлежащие удалению.

  • хитрость заключается в том, чтобы использовать переменную для хранения BUILDRESULTSUMMARY_IDи использовать переменную вместо запроса. Обратите внимание, что инициализация переменной и запрос на удаление должны выполняться в течение сеанса. Что-то вроде этого.

    SET @ids = (SELECT GROUP_CONCAT(BUILDRESULTSUMMARY_ID) 
            from BUILDRESULTSUMMARY where BUILDRESULTSUMMARY.BUILD_KEY = "BAM-1" ); 
    delete from VARIABLE_SUBSTITUTION where FIND_IN_SET(BUILDRESULTSUMMARY_ID,@ids) > 0;

    Вы можете столкнуться с проблемой, если запрос возвращает слишком много идентификаторов, а это не стандартный способ. Это просто обходной путь.

    И у меня нет ответа на два других вопроса :)

Масуд
источник
Хорошо, ты упустил мою точку зрения. Я думаю , что вы не считали, что оба VARIABLE_SUBSTITUTION и BUILDRESULTSUMMARY есть столбец с именем BUILDRESULTSUMMARY_ID, поэтому он должен быть: «удалить из VARIABLE_SUBSTITUTION где СУЩЕСТВУЕТ (выберите BUILDRESULTSUMMARY_ID из BUILDRESULTSUMMARY где BUILDRESULTSUMMARY.BUILDRESULTSUMMARY_ID = VARIABLE_SUBSTITUTION.BUILDRESULTSUMMARY_ID И BUILDRESULTSUMMARY.BUILD_KEY =«BAM -1" );». Тогда это имеет смысл, и оба запроса делают то же самое.
0x89
1
да, я просто пропустил ссылку на внешнюю таблицу. Но это не главное. Это просто иллюстрация того, как это будет обрабатываться в оптимизаторе.
Масуд
С небольшой разницей, что оптимизатор выдаст эквивалентный запрос.
0x89