MySQL взаимоблокировки с составным первичным ключом и автоинкрементом триггера - PullRequest
0 голосов
/ 22 февраля 2019

У меня есть автономный сервер и 2000 пользователей онлайн (не так много).MySQL DB 5.6 с таблицей request_action (составной PK без автоинкремента, но приращение находится в триггере, вы можете увидеть его ниже):

  CREATE TABLE `request_action` (
  `ra_id` bigint(20) NOT NULL,
  `cl_id` int(11) NOT NULL DEFAULT '0',
  `ra_r_id` bigint(20) NOT NULL,
  `ra_tr_id` bigint(20) DEFAULT '0',
  `ra_ss_id` bigint(20) NOT NULL DEFAULT '0',
  `ra_h_id` int(11) NOT NULL DEFAULT '0',
  `ra_uch_id` bigint(20) DEFAULT '0',
  `ra_u_id` int(11) DEFAULT '0',
  `ra_datetime` datetime NOT NULL,
  `ra_uct_id` int(11) NOT NULL DEFAULT '0',
  `ra_text` longtext NOT NULL,
  `ra_datetime_reply` datetime NOT NULL,
  `ra_reply` longtext NOT NULL,
  `ra_line_breaks` tinyint(4) NOT NULL DEFAULT '0',
  `ra_plan` tinyint(4) NOT NULL DEFAULT '0',
  `ra_shw` tinyint(4) NOT NULL DEFAULT '1',
  `ra_to_u_id` int(11) DEFAULT '0',
  `ra_created_at` datetime DEFAULT NULL,
  `ra_seen` tinyint(4) NOT NULL DEFAULT '0',
  `ra_seen_u_id` bigint(20) NOT NULL DEFAULT '0',
  PRIMARY KEY (`cl_id`,`ra_id`),
  KEY `rm_r_id` (`ra_r_id`),
  KEY `ra_u_id` (`ra_u_id`),
  KEY `ra_plan` (`ra_plan`),
  KEY `ra_rat_id` (`ra_ss_id`),
  KEY `ra_h_id` (`ra_h_id`),
  KEY `ra_tr_id` (`ra_tr_id`),
  KEY `ra_id` (`ra_id`),
  KEY `ra_datetime` (`ra_datetime`,`ra_seen`),
  KEY `ra_shw` (`ra_shw`,`ra_seen`,`ra_to_u_id`),
  KEY `ra_r_id` (`ra_r_id`,`ra_tr_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Триггер на этой таблице (ДО ВСТАВКИ):

if (cast(NEW.ra_id as UNSIGNED) = 0) then
SET NEW.ra_id = (SELECT COALESCE(MAX(ra_id)+1, 1) FROM request_action WHERE cl_id = NEW.cl_id);
end if

И у меня есть взаимные блокировки много раз в день ((Например, 100 в день.

LATEST DETECTED DEADLOCK
------------------------
2019-02-21 21:09:34 7f5e11f3b700
*** (1) TRANSACTION:
TRANSACTION 2947112777, ACTIVE 0 sec inserting
mysql tables in use 11, locked 11
LOCK WAIT 5 lock struct(s), heap size 1184, 3 row lock(s)
MySQL thread id 19952598, OS thread handle 0x7f5e10e38700, query id 248552715 192.168.0.7 vh_uon_com_ru
insert into request_action (
                    ra_r_id,
                    ra_u_id,
                    ra_datetime,
                    ra_text,
                    ra_datetime_reply,
                    ra_reply,
                    ra_plan,
                    cl_id,
                    ra_tr_id,
                    ra_ss_id,
                    ra_h_id,
                    ra_uch_id,
                    ra_to_u_id,
                    ra_uct_id,
                    ra_shw
                ) values (
                    40053,
                    906,
                    '2019-02-21 21:09:34',
                    'Звонок',
                    '2019-02-21 21:09:34',
                    '',
                    '0',
                    698,
                    0,
                    0,
                    0,
                    171114,
                    0,

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 2320 page no 546708 n bits 104 index `PRIMARY` of table `request_action` trx id 2947112777 lock_mode X locks gap before rec insert intention waiting
*** (2) TRANSACTION:
TRANSACTION 2947112774, ACTIVE 0 sec inserting
mysql tables in use 11, locked 11
5 lock struct(s), heap size 1184, 3 row lock(s)
MySQL thread id 19952597, OS thread handle 0x7f5e11f3b700, query id 248552705 192.168.0.7
insert into request_action (
                    ra_r_id,
                    ra_u_id,
                    ra_datetime,
                    ra_text,
                    ra_datetime_reply,
                    ra_reply,
                    ra_plan,
                    cl_id,
                    ra_tr_id,
                    ra_ss_id,
                    ra_h_id,
                    ra_uch_id,
                    ra_to_u_id,
                    ra_uct_id,
                    ra_shw
                ) values (
                    25182,
                    906,
                    '2019-02-21 21:09:34',
                    'Звонок',
                    '2019-02-21 21:09:34',
                    '',
                    '0',
                    698,
                    0,
                    0,
                    0,
                    171113,
                    0,

*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 2320 page no 546708 n bits 104 index `PRIMARY` of table `request_action` trx id 2947112774 lock mode S locks gap before rec
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 2320 page no 546708 n bits 104 index `PRIMARY` of table `request_action` trx id 2947112774 lock_mode X locks gap before rec insert intention waiting
*** WE ROLL BACK TRANSACTION (2)

В my.cf у нас есть следующие опции:

max_connections = 10000
key_buffer_size = 1024M
join_buffer_size = 256M
read_buffer_size = 256M
sort_buffer_size = 256M
tmp_table_size = 512M
read_rnd_buffer_size = 8M
max_heap_table_size = 512M

thread_cache_size = 8192
query_cache_type = 1

query_cache_size = 15G
wait_timeout = 6000
connect_timeout = 15
interactive_timeout = 60
max_allowed_packet = 512M
bulk_insert_buffer_size = 64M

innodb_log_file_size                    = 512M
innodb_log_buffer_size                  = 2G
innodb_buffer_pool_size                 = 20G

Не могли бы выпожалуйста, помогите мне с проблемой взаимоблокировки? Как я могу это исправить? Должен ли я повторно выполнять запросы в взаимоблокировках?

1 Ответ

0 голосов
/ 22 февраля 2019

TL; DR - вы не можете выполнять одновременные вставки, когда пытаетесь создать новый инкрементный идентификатор для каждого отдельного cl_id.Для этого необходимо использовать блокировку таблицы, в результате чего параллельные вставки запускаются последовательно.


Причина, по которой AUTO_INCREMENT обходит этот тупик, заключается в том, что он получает краткую блокировку таблицы для создания следующего идентификатора.Технически, это приводит к тому, что все параллельные сеансы, выполняющие INSERT, выполняются последовательно.К счастью, замок на столе очень короткий.По умолчанию он освобождается сразу после создания идентификатора.Вы можете прочитать больше здесь: https://dev.mysql.com/doc/refman/8.0/en/innodb-auto-increment-handling.html

В то время как ваш метод генерации идентификаторов вызывает взаимные блокировки, потому что он использует две операции блокировки:

  1. Один X-замокдля создания строки.
  2. Один S-замок для чтения таблицы.Когда вы читаете таблицу как часть INSERT / UPDATE / DELETE, вы создаете общую блокировку для строк, которые вы читаете.

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

mysql> create table foo ( id serial primary key);
mysql> insert into foo (id) values (1);

mysql> create table bar ( id serial primary key);

mysql> create trigger b before insert on bar 
       for each row set new.id=(select max(id) from foo);

Теперь у нас есть триггер на bar, который будет читать некоторую строку в foo, чтобы получить максимум (id).

mysql> begin;
mysql> insert into bar () values ();

Это должно создать новую строку в bar, используя значение, прочитанное из foo.Но транзакция все еще открыта.

Во втором окне, сделайте это:

mysql> update foo set id = 2;
...

Это зависает, ожидая, когда его X-замок будет на foo.Он не может обновить foo, потому что на нем уже есть S-замок, помещенный сеансом в первое окно.

Вернитесь к первому окну и выполните:

mysql> update foo set id = 3;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

Это создает циклическое ожидание блокировки, которое является тупиком.Обе транзакции ожидают блокировки, удерживаемые другой транзакцией.Во втором окне мы видим, что транзакция была прервана:

mysql> update foo set id = 2;
...
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

«Как это исправить? Следует ли повторно запускать запросы в тупиках?»

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

mysql> begin;
mysql> lock tables foo write, bar write;
mysql> insert into bar () values ();

Второе окно зависает, нов этот раз он висит на блокировке таблицы, а не на блокировке строки.

mysql> update foo set id = 2;
...

В первом окне завершите транзакцию.Разблокировка блокировок таблицы неявно фиксирует транзакцию.

mysql> unlock tables;

Второе окно перестает ждать и успешно завершает свое обновление.

mysql> update foo set id = 2;
...
Query OK, 1 row affected (3.50 sec)
Rows matched: 1  Changed: 1  Warnings: 0

Обратите внимание, что он ждал 3,5 секунды, то есть сколько времени мне потребовалось, чтобы вернуться к первому окну и зафиксировать транзакцию.

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

...