簡體   English   中英

使用復合主鍵的MySQL死鎖並觸發自動遞增

[英]MySQL deadlocks with composite primary key and trigger autoincrement

我有獨立服務器和2000個在線用戶(不是很多)。 帶有表request_action的MySQL DB 5.6(沒有自動增量BUT增量的復合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

您能解決死鎖問題嗎? 我該如何解決? 我應該在死鎖中重新運行查詢嗎?

TL; DR-在嘗試為每個cl_id生成新的遞增id時,無法進行並發插入。 您必須使用表鎖來執行此操作,從而導致並發插入以串行方式運行。


AUTO_INCREMENT繞過此死鎖的原因是它獲取了簡短的表鎖以生成下一個ID。 從技術上講,這會導致所有執行INSERT的並發會話按順序執行。 幸運的是,表鎖非常簡短。 默認情況下,ID生成后立即將其釋放。 您可以在這里閱讀更多信息: https : //dev.mysql.com/doc/refman/8.0/en/innodb-auto-increment-handling.html

而您生成id的方法會導致死鎖,因為它使用了兩個鎖定操作:

  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某行以獲取max(id)。

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

那應該使用從foo讀取的值在bar創建一個新行。 但是交易仍在進行中。

在第二個窗口中,執行以下操作:

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

這掛起,等待對foo X鎖定。 它無法更新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秒,這是我返回第一個窗口並提交事務所花的時間。

使會話順序插入會限制應用程序的吞吐量,因為會話正在排隊。 但這避免了死鎖。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM