Вспомните волшебное расследование взаимоблокировок Mysql

Java задняя часть

задний план

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

Впервые появилась проблема

Однажды днем ​​система внезапно встревожилась, и было выдано исключение:

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

Сначала найдите Innodb Status в базе данных.Innodb Status запишет информацию о последней взаимоблокировке.Введите следующую команду:

SHOW ENGINE INNODB STATUS

Информация о взаимоблокировке выглядит следующим образом, а информация sql просто обрабатывается:

------------------------
LATEST DETECTED DEADLOCK
------------------------
2019-02-22 15:10:56 0x7eec2f468700
*** (1) TRANSACTION:
TRANSACTION 2660206487, ACTIVE 0 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 31261312, OS thread handle 139554322093824, query id 11624975750 10.23.134.92 erp_crm__6f73 updating
/*id:3637ba36*/UPDATE tenant_config SET
       open_card_point =  0
       where tenant_id = 123
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1322 page no 534 n bits 960 index uidx_tenant of table `erp_crm_member_plan`.`tenant_config` trx id 2660206487 lock_mode X locks rec but not gap waiting

*** (2) TRANSACTION:
TRANSACTION 2660206486, ACTIVE 0 sec starting index read
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 31261311, OS thread handle 139552870532864, query id 11624975758 10.23.134.92 erp_crm__6f73 updating
/*id:3637ba36*/UPDATE tenant_config SET
       open_card_point =  0
       where tenant_id = 123
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 1322 page no 534 n bits 960 index uidx_tenant of table `erp_crm_member_plan`.`tenant_config` trx id 2660206486 lock mode S
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1322 page no 534 n bits 960 index uidx_tenant of table `erp_crm_member_plan`.`tenant_config` trx id 2660206486 lock_mode X locks rec but not gap waiting
*** WE ROLL BACK TRANSACTION (1)
------------

Позвольте мне дать вам простой анализ и объяснение этого журнала взаимоблокировок. Когда транзакция 1 выполняет оператор обновления, ей необходимо получить индекс uidx_tenant и блокировку X (блокировку строки) для условия где. Транзакция 2 выполняет тот же оператор обновления, а также думает о uidx_tenant Чтобы получить блокировку X (блокировку строки), тогда возникает взаимоблокировка, и транзакция 1 откатывается. В то время я очень растерялся и вспомнил необходимые условия возникновения дедлока:

  1. взаимоисключающий.
  2. Условия запроса и удержания.
  3. Никаких условий лишения.
  4. Круговое ожидание. Судя по журналу, транзакция 1 и транзакция 2 конкурируют за блокировку строки одной и той же строки, что немного отличается от предыдущей взаимной циклической блокировки блокировки.Как бы вы ни смотрели на это, условие циклического ожидания не может быть выполнено. После того, как коллеги напомнили, что, поскольку журнал взаимоблокировок нельзя проверить, его можно проверить только из бизнес-кода и бизнес-журнала. Логика этого кода следующая:
public int saveTenantConfig(PoiContext poiContext, TenantConfigDO tenantConfig) {
        try {
            return tenantConfigMapper.saveTenantConfig(poiContext.getTenantId(), poiContext.getPoiId(), tenantConfig);
        } catch (DuplicateKeyException e) {
            LOGGER.warn("[saveTenantConfig] 主键冲突,更新该记录。context:{}, config:{}", poiContext, tenantConfig);
            return tenantConfigMapper.updateTenantConfig(poiContext.getTenantId(), tenantConfig);
        }
    }

Смысл этого кода в сохранении конфигурационного файла.Если будет конфликт уникальных индексов, то он будет обновлен.Конечно,может он здесь не очень хорошо написан,но его можно использовать

insert into ... 
on duplicate key update 

Тот же эффект может быть достигнут, но даже при этом фактически произойдет взаимоблокировка. Прочитав код, мой коллега прислал мне бизнес-журнал того времени,

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

На данный момент посмотрите на структуру нашей таблицы следующим образом (упрощенная обработка):

CREATE TABLE `tenant_config` (
  `id` bigint(21) NOT NULL AUTO_INCREMENT,
  `tenant_id` int(11) NOT NULL,
  `open_card_point` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uidx_tenant` (`tenant_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT

Наш tenant_id используется как уникальный индекс, а наши условия вставки и обновления основаны на уникальном индексе.

UPDATE tenant_config SET
       open_card_point =  0
       where tenant_id = 123

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

Углубленный анализ

Выше мы сказали, что есть три транзакции, входящие в оператор обновления. Чтобы упростить объяснение, нам нужно только две транзакции для ввода оператора обновления одновременно. В следующей таблице показан весь наш процесс:

Лента новостей Транзакция 1 Транзакция 2 Транзакция 3
1 insert into xx insert into xx insert into xx
2 Получить текущую блокировку строки X
3 Необходимо определить, конфликтует ли уникальный индекс, чтобы получить блокировку S, блокируя Необходимо определить, конфликтует ли уникальный индекс, чтобы получить блокировку S, блокируя
4 commit; Приобретен замок S Приобретен замок S
5 Обнаружен уникальный конфликт индексов, выполните оператор Update (в настоящее время блокировка S не снята) Обнаружен уникальный конфликт индексов, выполните оператор Update.
6 Получите блокировку X строки, заблокированную блокировкой S транзакции 3 Получите блокировку X на строку, заблокированную блокировкой S транзакции 2
7 Найден тупик, откатить транзакцию обновление выполнено успешно, зафиксировать;

Советы: Блокировки S — это разделяемые блокировки, а блокировки X — блокировки мьютекса. Вообще говоря, блокировки X и блокировки S и X являются взаимоисключающими, в то время как блокировки S и блокировки S не являются взаимоисключающими.

Из приведенного выше процесса видно, что ключ к этой взаимоблокировке должен получить блокировку S. Почему нам нужно получить блокировку S, когда мы вставляем ее снова? Потому что нам нужно определить уникальный индекс? При уровне изоляции RR, если вы хотите читать, то это текущее чтение, тогда вам нужно добавить блокировку S. Здесь обнаруживается, что уникальный ключ уже существует.В это время выполнение обновления будет заблокировано S-блокировками двух транзакций, таким образом формируя описанное выше условие циклического ожидания.

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

решение

Основная проблема здесь заключается в том, что необходимо отключить блокировку S. Вот три решения для справки:

  • Уменьшите уровень изоляции RR до уровня изоляции RC. Здесь уровень изоляции RC будет использовать чтение моментальных снимков, поэтому блокировка S не будет добавлена.
  • При повторной вставке используйте select * для обновления и добавьте блокировку X, чтобы блокировка S не добавлялась.
  • Вы можете добавить распределенные блокировки заранее, вы можете использовать Redis или ZK и т. д. Для распределенных блокировок вы можете обратиться к этой моей статье.Разговор о распределенных блокировках

Первый способ не очень реалистичен, ведь уровень изоляции нельзя так просто модифицировать. Третий способ более хлопотный. Итак, второй метод — это то, что мы доработали.

Суммировать

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

Последняя статья была включена в JGrowing-CaseStudy, всеобъемлющий и отличный маршрут изучения Java, совместно созданный сообществом. Если вы хотите участвовать в обслуживании проектов с открытым исходным кодом, вы можете создать его вместе. Адрес github:GitHub.com/Java растет…Пожалуйста, дайте мне маленькую звезду.

Если вы считаете, что эта статья полезна для вас, то ваше внимание и пересылка - самая большая поддержка для меня, O(∩_∩)O: