MySQL / InnoDB: обновление большой таблицы с более мелкой, критически важной таблицы без блокировки маленькой таблицы - PullRequest
0 голосов
/ 16 марта 2019

В MySQL у меня есть две таблицы innodb, небольшая критическая таблица, которая должна быть всегда доступна для чтения / записи. Назовите это mission_critical. У меня есть большая таблица (> 10 миллионов строк), называемая big_table. Мне нужно обновить big_table, например:

update mission_critical c, big_table b
set
b.something = c.something_else
where b.refID=c.id

Этот запрос может занять более часа, но это создает блокировку записи в таблице mission_critical. Есть ли способ, которым я могу сказать mysql: «Я не хочу блокировать mission_critical», чтобы в эту таблицу можно было записать?

Я понимаю, что это не идеально с точки зрения транзакций. Единственный обходной путь, о котором я могу подумать сейчас, - это сделать копию маленькой таблицы mission_critical и выполнить обновление из нее (которая меня не интересует, заблокирована), но я бы предпочел этого не делать, если есть способ сделать MySQL изначально справляется с этим более изящно.

Редактировать : некоторые уточнения

Блокируется не таблица, а все записи в mission_critical, которые блокируются, поскольку все они в основном сканируются обновлением. Я не предполагаю этого; Симптом заключается в том, что когда пользователь входит в онлайн-систему, он пытается обновить столбец datetime в mission_critical, чтобы обновить последний раз, когда они вошли в систему. Эти запросы умирают из-за ошибки ожидания превышения времени ожидания блокировки во время выполнения вышеуказанного запроса. Если я убью приведенный выше запрос, все ожидающие запросы будут выполнены немедленно.

mission_critical.id и big_table.refID оба проиндексированы.

Соответствующие части операторов создания для каждой таблицы:

mission_critical:

CREATE TABLE `mission_critical` (
`intID` int(11) NOT NULL AUTO_INCREMENT,
`id` smallint(6) DEFAULT NULL,
`something_else` varchar(50) NOT NULL,
`lastLoginDate` datetime DEFAULT NULL,
PRIMARY KEY (`intID`),
UNIQUE KEY `id` (`id`),
UNIQUE KEY `something_else` (`something_else`),
) ENGINE=InnoDB AUTO_INCREMENT=1432 DEFAULT CHARSET=latin1

big_table:

CREATE TABLE `big_table` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`postDate` date DEFAULT NULL,
`postTime` int(11) DEFAULT NULL,
`refID` smallint(6) DEFAULT NULL,
`something` varchar(50) NOT NULL,
`change` decimal(20,2) NOT NULL
PRIMARY KEY (`id`),
KEY `refID` (`refID`),
KEY `postDate` (`postDate`),
) ENGINE=InnoDB AUTO_INCREMENT=138139125 DEFAULT CHARSET=latin1

Объяснение запроса:

+----+-------------+------------------+------------+------+---------------+-------+---------+------------------------------------+------+----------+-------------+
| id | select_type |      table       | partitions | type | possible_keys |  key  | key_len |                ref                 | rows | filtered |    Extra    |
+----+-------------+------------------+------------+------+---------------+-------+---------+------------------------------------+------+----------+-------------+
|  1 | SIMPLE      | mission_critical |            | ALL  | id            |       |         |                                    |  774 |      100 | Using where |
|  1 | UPDATE      | big_table        |            | ref  | refID         | refID |       3 | db.mission_critical.something_else | 7475 |      100 |             |
+----+-------------+------------------+------------+------+---------------+-------+---------+------------------------------------+------+----------+-------------+

Ответы [ 3 ]

1 голос
/ 19 марта 2019

Сначала я предложил обходной путь с подзапросом, чтобы создать копию во внутренней временной таблице. Но в моем тесте маленькая таблица все еще была заблокирована для записи. Поэтому я думаю, что вам лучше всего сделать копию вручную.

Причина блокировки описана в этом сообщении об ошибке: https://bugs.mysql.com/bug.php?id=72005

Вот что Синиса Миливоевич написал в ответе:

update table t1,t2 ....

любое UPDATE с объединением считается обновлением нескольких таблиц. В этом В этом случае ссылочная таблица должна быть заблокирована для чтения, потому что строки не должны изменяться в ссылочной таблице в течение UPDATE до тех пор, пока законченный. Не может быть одновременных изменений строк, ни DELETE строк, и, тем более, не дает никаких гарантий DDL на упомянутые Таблица. Цель проста, чтобы все таблицы соответствовали содержимое по окончании UPDATE, особенно с учетом нескольких таблиц UPDATE может быть выполнен за несколько проходов.

Короче говоря, это поведение по уважительной причине.

Попробуйте написать триггеры INSERT и UPDATE, которые обновят big_table на лету. Это задержит запись в таблицу mission_critical. Но это может быть достаточно быстрым для вас, и вам больше не понадобится массовый запрос на обновление.

Также проверьте, не лучше ли будет использовать char(50) вместо varchar(50). Я не уверен, но возможно, что это улучшит производительность обновления, потому что размер строки не нужно будет менять. Я мог бы улучшить производительность обновления примерно на 50% в тесте.

0 голосов
/ 19 марта 2019

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

update big_table b
set b.something = (select c.something_else from mission_critical c where b.refID = c.id)
0 голосов
/ 16 марта 2019

UPDATE заблокирует строки, которые нужно изменить.Это может также заблокировать «пробелы» после этих строк.

Вы можете использовать транзакции MySQL в цикле. Обновлять только 100 строк одновременно

BEGIN;ВЫБРАТЬ ... ДЛЯ ОБНОВЛЕНИЯ;- договориться, чтобы этот выбор включал 100 строк ОБНОВЛЕНИЕ ...;- обновить 100 строк COMMIT;

...