首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >具有复合主键和触发器自动增量的MySQL死锁

具有复合主键和触发器自动增量的MySQL死锁
EN

Stack Overflow用户
提问于 2019-02-22 05:35:56
回答 1查看 1.3K关注 0票数 2

我有独立的服务器和2000个在线用户(不是很多)。带表request_action的MySQL DB 5.6 (不带自动增量但增量在触发器中的复合主键,如下所示):

代码语言:javascript
复制
  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;

此表上的触发器(插入前):

代码语言:javascript
复制
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次。

代码语言:javascript
复制
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中,我们有以下选项:

代码语言:javascript
复制
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

你能帮我解决死锁问题吗?我怎么才能修复它?我应该在死锁中重新运行查询吗?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2019-02-22 06:14:02

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. One X锁。用于读取表的
  2. One S锁。当您在INSERT/UPDATE/DELETE过程中读取表时,将在所读取行上创建一个共享锁。

但是锁不是一起获得的,在两个步骤之间有一段短暂的时间,这就是竞争条件发生的地方。我们可以使用两个表来演示这一点:

代码语言:javascript
复制
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)。

代码语言:javascript
复制
mysql> begin;
mysql> insert into bar () values ();

这将使用它从foo读取的值在bar中创建一个新行。但交易仍在进行中。

在第二个窗口中,执行以下操作:

代码语言:javascript
复制
mysql> update foo set id = 2;
...

它挂起,等待它在foo上的X锁。它无法更新foo,因为第一个窗口中的会话已经设置了S锁。

返回到第一个窗口并运行:

代码语言:javascript
复制
mysql> update foo set id = 3;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

这会创建一个循环锁-等待,这就是死锁。两个事务都在等待另一个事务持有的锁。我们在第二个窗口中看到,该事务被终止:

代码语言:javascript
复制
mysql> update foo set id = 2;
...
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

"How can I fix it? How I rerun queries in deadlocks?"

一种解决方法是强制并发会话串行运行,方法是在尝试插入之前获取insert或触发器引用的所有表的表锁。

代码语言:javascript
复制
mysql> begin;
mysql> lock tables foo write, bar write;
mysql> insert into bar () values ();

第二个窗口挂起了,但它挂在一个表锁上,这次不是行锁。

代码语言:javascript
复制
mysql> update foo set id = 2;
...

在第一个窗口中,完成事务。解锁表锁隐式地提交事务。

代码语言:javascript
复制
mysql> unlock tables;

第二个窗口停止等待,并成功完成其更新。

代码语言:javascript
复制
mysql> update foo set id = 2;
...
Query OK, 1 row affected (3.50 sec)
Rows matched: 1  Changed: 1  Warnings: 0

请注意,它已经等待了3.5秒,这是我返回第一个窗口并提交事务所用的时间。

使会话按顺序插入会限制应用程序的吞吐量,因为会话正在排队。但它避免了死锁。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/54816599

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档