MySQL: видалити… де..in () vs 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, який використовується лише в підселекції?

Percona Server 5.6.24-72.2-1.jessie resp 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% впевнений, але я думаю, що це стосується запуску підзапросу кілька разів і того, який тип блокування він бере на стіл.


"3775190 блокування рядків" з innodb_status (транзакції, що видаляється) вкрай напрошується. Але також "mysql таблиці, які використовуються 2, заблоковані 2", не дуже добре виглядає для мене ..
0x89

2

ось відповіді на два ваші запитання

  • Оптимізатор не в змозі використовувати індекс, тому що пункт де змінюється для кожного рядка. Оператор видалення буде виглядати приблизно так після проходження оптимізатора

    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
так, я просто пропускаю посилання на зовнішню таблицю. Але це не в цьому справа. Це лише ілюстрація того, як з цим поводитимуться в оптимізаторі.
Масуд

З невеликою різницею, що оптимізатор створить еквівалентний запит.
0х89,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.