1. Предпосылки
В последнее время были какие-то действия, поэтому я сделаю некоторое подавление в системе с небольшим количеством путей. Хорошо, если оно не подавлено. Есть странная проблема с этим подавлением. В старом коде есть проблема взаимоблокировки. ., лог такой:
Увидев журнал, я встал на путь расследования тупиковой ситуации. Конечно, если вы не очень хорошо знакомы с блокировками, вы можете сначала прочитать две мои статьи, чтобы увидеть основы блокировок базы данных:Почему разработчики должны понимать блокировки базы данных:иПомните исследование артефакта в тупиковой ситуации в MySQL?
2. Анализ проблемы
Код базы данных выглядит следующим образом:
CREATE TABLE `order_extrainfo` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`orderId` int(11) NOT NULL,
`extraInfo` text NOT NULL,
`appProductId` int(11) NOT NULL DEFAULT '0',
`hostAppProductId` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `orderId` (`orderId`)
) ENGINE=InnoDB AUTO_INCREMENT=17835265 DEFAULT CHARSET=utf8mb4;
Рассматриваемый оператор SQL выглядит следующим образом:
INSERT INTO `order_extrainfo` (orderId, " +
"extraInfo, appProductId, hostAppProductId) VALUES (?,?, ?, ?) ON " +
"DUPLICATE KEY UPDATE extraInfo = ?, appProductId = ?, " +
"hostAppProductId = ?
Раньше я не сталкивался с взаимоблокировкой при вставке, поэтому я подумал, что это было вызвано обновлением ключа dpolicate. Чтобы найти сцену взаимоблокировки в это время, введите: SHOW ENGINE INNODB STATUS; просмотрите последний журнал взаимоблокировки:
Очевидно, что транзакция 1 и транзакция 2 ожидают снятия блокировки гэпа Они должны удерживать блокировки гэпа друг с другом, и они обе ждут друг друга, чтобы вызвать это.
Общий журнал взаимоблокировок вызван двумя транзакциями, поэтому он может вызвать некоторую путаницу. На самом деле, большинство взаимоблокировок вызвано более чем двумя транзакциями. Этот раз не является исключением. На самом деле это ошибка MySQL,Не говоря .MySQL.com/, но .PHP?ID=…Кому интересно, могут посмотреть.
Для описания в баге начните воспроизводить эту сцену локально:
Лента новостей | session1 | session2 | session3 |
---|---|---|---|
1 | begin; | insert into xx | |
2 | INSERT INTO order_extrainfo (orderId, extraInfo, appProductId, hostAppProductId) VALUES (158360183,'', 0, 0) ON DUPLICATE KEY UPDATE extraInfo = ''; |
begin; | |
3 | 1 row in affected; | INSERT INTO order_extrainfo (orderId, extraInfo, appProductId, hostAppProductId) VALUES (158360184,'', 0, 0) ON DUPLICATE KEY UPDATE extraInfo = ''; |
begin; |
4 | INSERT INTO order_extrainfo (orderId, extraInfo, appProductId, hostAppProductId) VALUES (158360185,'', 0, 0) ON DUPLICATE KEY UPDATE extraInfo = ''; |
||
5 | commit; | ||
6 | 1 row in affected; | Deadlock found when trying to get lock; try restarting transaction |
Примечание: session1,2,3 каждый orderId увеличивается по очереди.
Сессия 3 заблокирована.
Порядок выполнения session1,2,3 легко реализовать, когда у нас высокий параллелизм, поэтому будет большое количество ошибок взаимоблокировки.
2.1 Анализ блокировки
Здесь мы подробно разберем, какие блокировки добавляются.Мы знаем, что блокировки X и блокировки намерения вставки будут добавлены во время операции вставки.Здесь мы увидим, какие блокировки добавляются для вставки в дубликат ключа.
Это проверено на моем локальном компьютере, сначала откройте:
set GLOBAL innodb_status_output_locks=ON;
set GLOBAL innodb_status_output=ON;
Статистика блокировки MySQL, если это онлайн не рекомендуется открывать, журнал будет записывать больше.
Сначала выполните первый sql:
INSERT INTO order_extrainfo
(orderId, extraInfo, appProductId, hostAppProductId) VALUES (158360183,'', 0, 0) ON DUPLICATE KEY UPDATE extraInfo = '';
Введите команду show engine innodb status для просмотра
Ситуация с блокировкой показана на рисунке выше. Здесь мы хотим объяснить, что намерение вставки здесь представляет собой неявную блокировку. Добавленная здесь блокировка на самом деле представляет собой x + GAP (блокировка промежутка от отрицательной бесконечности до положительной бесконечности) + намерение вставки трех блокировок.
Здесь мы выполняем второй sql,
INSERT INTO order_extrainfo
(orderId, extraInfo, appProductId, hostAppProductId) VALUES (158360184,'', 0, 0) ON DUPLICATE KEY UPDATE extraInfo = '';
Обнаружено, что блокировка намерения вставки блокируется блокировкой промежутка.
Точно так же, если мы выполним третий sql, блокировка намерения вставки также будет заблокирована блокировкой пробела первой транзакции.Если блокировка пробела первой транзакции зафиксирована, они сначала получат блокировку пробела (здесь, судя по блокировка информации, когда она заблокирована, блокировка пробела отсутствует), а затем приобретается блокировка намерения вставки, что приводит к тому, что сеансы Session2 и Session3 образуют циклическую ссылку, что в конечном итоге приводит к тупиковой ситуации.
2.2 Почему существует гэп-лок
Блокировка пробелов — это средство для решения проблемы фантомного чтения на уровне изоляции RR.Обычно появляется при удалении.Почему появляется здесь? существуетНе говоря .MySQL.com/, но .PHP?ID=…Эту ошибку можно увидеть в:
«Параллельные операторы «INSERT ... ON DUPLICATE KEY UPDATE» выполняются в таблице с несколькими уникальными индексами иногда приводило к записи событий в бинарный лог неверный" "
Когда мы используем INSERT... ON DUPLICATE KEY UPDATE одновременно, если у нас есть несколько уникальных индексов, это может вызвать ошибку binlog, то есть это приведет к несогласованной репликации master-slave, а некоторые конкретные тесты можно посмотреть по ссылке
3. Как решить
Что мне делать, если я столкнулся с этой проблемой? У нас есть следующие методы решения этой проблемы:
- Используя версию mysql5.6, вы можете видеть, что это было введено в 5.7, и эта ситуация не возникнет в 5.6.
- Используя уровень RC, не будет блокировки промежутка на уровне изоляции RC -- Не используйте вставку при обновлении двойного ключа, используйте обычную вставку. Мы остановились на этом методе, потому что ON DUPLICATE KEY UPDATE действительно не нужен в коде.
- В таблице базы данных устанавливается только первичный ключ, и никакие другие уникальные индексы не устанавливаются.
- Сначала вставьте, затем перехватите исключение, затем обновите
- Используйте игнорирование вставки, а затем определите, равны ли строки обновления 1, а затем решите, следует ли обновлять.
Если вы считаете, что эта статья полезна для вас, то ваше внимание и пересылка - самая большая поддержка для меня, O(∩_∩)O: