При автокоммитах запрос вызывает Deadlock. MySQL. InnoDB

В попытках побороть взаимоблокировки (дедлоки) на боевом сервере зашёл в тупик. Есть 2 таблицы:

Первая:

create table table_1
(
    id    int auto_increment
        primary key,
    data1 text null,
    data2 text null
);

Вторая:

create table table_2
(
    id    int auto_increment
        primary key,
    t1_id int  null,
    data1 text null,
    data2 text null,
    constraint table_2_table_1_id_fk
        foreign key (t1_id) references table_1 (id)
            on update cascade on delete cascade
);

Для тестов, в table_1 30 записей, в table_2 60 (каждые 2 записи из table_2 ссылаются по ключу к 1 запись в table_1).

Далее, простой php скрипт, который обновляет некоторые записи из таблицы table_1, по условию из table_2 в бесконечном цикле:

<?php
$db = new PDO("mysql:host=127.0.0.1;dbname=test_db;", 'debian-sys-maint', 'pass', [
    PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'",
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
$random = rand(0, 9);
while (true) {
    $db->exec("
UPDATE table_1
INNER JOIN table_2 on table_1.id = table_2.t1_id and table_2.data2 like '%$random%'
SET table_1.data1 = table_1.data1 + 1,
    table_1.data2 = table_1.data2 + 1;");
}

Так вот, запускаю этот PHP скрипт в 150 экземплярах, и генерируется ошибка Deadlock. Я пытался её устранить, модифицировав запрос следующим образом:

UPDATE table_1
    INNER JOIN (SELECT t1_id FROM table_2 WHERE table_2.data2 like '%$random%' ORDER BY t1_id) table_2 on table_1.id = table_2.t1_id
SET table_1.data1 = table_1.data1 + 1,
    table_1.data2 = table_1.data2 + 1;
WITH tmp (id) AS (SELECT t1_id FROM table_2 WHERE table_2.data2 like '%$random%' ORDER BY t1_id)
UPDATE table_1
    INNER JOIN tmp on table_1.id = tmp.id
SET table_1.data1 = table_1.data1 + 1,
    table_1.data2 = table_1.data2 + 1;
UPDATE table_1
SET table_1.data1 = table_1.data1 + 1,
    table_1.data2 = table_1.data2 + 1
WHERE table_1.id in (SELECT t1_id FROM table_2 WHERE table_2.data2 like '%$random%' ORDER BY t1_id);
UPDATE table_1, (SELECT t1_id FROM table_2 WHERE table_2.data2 like '%$random%' ORDER BY t1_id) tmp1
SET table_1.data1 = table_1.data1 + 1,
    table_1.data2 = table_1.data2 + 1
WHERE table_1.id = tmp1.t1_id;

Собственно первый вопрос: Я не понимаю, откуда берётся дедлок, если записи сортируются всегда в одном и том же порядке, состояние гонки исключено. Вот что возвращает SHOW ENGINE INNODB STATUS; при поимке дедлока:

------------------------
LATEST DETECTED DEADLOCK
------------------------
2022-05-10 01:56:01 140233769219840
*** (1) TRANSACTION:
TRANSACTION 3529589, ACTIVE 1 sec starting index read
mysql tables in use 2, locked 2
LOCK WAIT 7 lock struct(s), heap size 1128, 90 row lock(s)
MySQL thread id 20, OS thread handle 140233708357376, query id 173 localhost 127.0.0.1 debian-sys-maint executing
UPDATE table_1
INNER JOIN table_2 on table_1.id = table_2.t1_id and table_2.data2 like '%9%'
SET table_1.data1 = table_1.data1 + 1,
    table_1.data2 = table_1.data2 + 1

*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 2 page no 4 n bits 224 index PRIMARY of table `test_db`.`table_1` trx id 3529589 lock_mode X locks rec but not gap
.....................
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 2 page no 4 n bits 224 index PRIMARY of table `test_db`.`table_1` trx id 3529589 lock_mode X locks rec but not gap waiting
.....................
*** (2) TRANSACTION:
TRANSACTION 3529887, ACTIVE 0 sec starting index read
mysql tables in use 2, locked 2
LOCK WAIT 5 lock struct(s), heap size 1128, 52 row lock(s)
MySQL thread id 118, OS thread handle 140229428811520, query id 444 localhost 127.0.0.1 debian-sys-maint executing
UPDATE table_1
INNER JOIN table_2 on table_1.id = table_2.t1_id and table_2.data2 like '%7%'
SET table_1.data1 = table_1.data1 + 1,
    table_1.data2 = table_1.data2 + 1

*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 2 page no 4 n bits 224 index PRIMARY of table `test_db`.`table_1` trx id 3529887 lock_mode X locks rec but not gap
.....................
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 2 page no 4 n bits 224 index PRIMARY of table `test_db`.`table_1` trx id 3529887 lock_mode X locks rec but not gap waiting
.....................
*** WE ROLL BACK TRANSACTION (2)

Единственный способ, которым у меня получилось решить взаимоблокировку, это создание временной таблицы, которая бы содержала готовый набор ключей, которые будут затронуты UPDATE сразу из table_1:

DROP TABLE IF EXISTS tmp1;
CREATE TEMPORARY TABLE tmp1
SELECT table_2.t1_id FROM table_2 WHERE table_2.data2 like '%$random%'

UPDATE table_1
    INNER JOIN tmp1 on table_1.id = tmp1.t1_id
SET table_1.data1 = table_1.data1 + 1,
    table_1.data2 = table_1.data2 + 1;

Или с тем же успехом я могу создать копию table_2 в временную таблицу, оно тоже не вызывает дедлоки:

DROP TABLE IF EXISTS tmp1;
CREATE TEMPORARY TABLE tmp1
SELECT * FROM table_2 WHERE 1;

UPDATE table_1
    INNER JOIN tmp1 on table_1.id = tmp1.t1_id and tmp1.data2 like '%$random%'
SET table_1.data1 = table_1.data1 + 1,
    table_1.data2 = table_1.data2 + 1;

Второй вопрос: Почему этот вариант не вызывает дедлоки? Я так понимаю, блокируются именно записи из таблицы table_1, в данном случае они точно так же блокируются, только поиск происходит не по данным таблицы table_2, а по данным временной таблицы tmp1.

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

BEGIN;
UPDATE table_1
INNER JOIN table_2 on table_1.id = table_2.t1_id and table_2.data2 like '%$random%'
SET table_1.data1 = table_1.data1 + 1,
    table_1.data2 = table_1.data2 + 1;
COMMIT;

Это, пожалуй, последний и наиболее волнующий меня вопрос... У меня включен автокоммит, почему если я явно не начинаю и не завершая транзакцию, то вылазиют дедлоки? PHP как известно работает в одном потоке, на 1 скрипт ровно 1 подключение к БД, все 150 скриптов работают параллельно


Ответы (0 шт):