Mysql 并发插入和锁定
Mysql concurrent inserts and locking
我们有 2 tables
CREATE TABLE `Queue_token` (
`token_id` int(11) NOT NULL AUTO_INCREMENT,
`token_queue_id` int(11) NOT NULL,
`total_process_time` smallint(6) NOT NULL,
`token_user` int(11) DEFAULT NULL,
`created_on` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`join_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`join_time` time NOT NULL,
`app_type` tinyint(1) NOT NULL DEFAULT '1',
`is_advance` tinyint(1) NOT NULL DEFAULT '0',
`is_confirmed` tinyint(1) NOT NULL DEFAULT '1',
`token_user_group` int(11) DEFAULT NULL,
`uuid` binary(16) DEFAULT NULL,
PRIMARY KEY (`token_id`),
KEY `join_date_idx` (`join_date`),
KEY `queue_join_date` (`token_queue_id`,`join_date`),
KEY `token_user` (`token_user`),
KEY `fk_token_user_group` (`token_user_group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `Live_token_sequence` (
`token_id` int(11) NOT NULL,
`queue_id` int(11) NOT NULL,
`sequence` int(11) NOT NULL,
`time_slot_id` mediumint(9) NOT NULL,
`time_slot_sequence` tinyint(4) NOT NULL,
`created_on` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`token_id`),
KEY `queue_sequence` (`queue_id`,`sequence`),
KEY `queue_time_slot` (`time_slot_id`),
CONSTRAINT `token_id_seq_fk` FOREIGN KEY (`token_id`) REFERENCES `Queue_token` (`token_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8
根据在 Queue_token table 中为某个日期生成的标记数量,我们为 Live_token_sequence table.[=14 中的每个标记生成一个唯一序列=]
为了生成序列,我们首先从 Queue_token table 中获取令牌的计数,然后生成的下一个令牌获得计数 + 1 序列。
我们面临一个问题,即在并发插入时,令牌的顺序相同。如果我们尝试使用 SELECT FOR UPDATE,我们将面临死锁,因为上面的计数查询也会与其他 table 进行连接。
我们该怎么做?
更新------
计数查询
```
select count(`sminq`.`Queue_token`.`token_id`)
from `sminq`.`Queue_token`
join `sminq`.`Live_token_sequence`
on `sminq`.`Queue_token`.`token_id` = `sminq`.`Live_token_sequence`.`token_id`
join `sminq`.`Calendar_time_slot`
on `sminq`.`Live_token_sequence`.`time_slot_id` = `sminq`.`Calendar_time_slot`.`slot_id`
join `sminq`.`Live_token_status` on `sminq`.`Queue_token`.`token_id` = `sminq`.`Live_token_status`.`token_id`
left outer join `sminq`.`Status_code`
on (`sminq`.`Live_token_status`.`token_status_id` = `sminq`.`Status_code`.`status_id`
and `sminq`.`Status_code`.`status_type` not in (?))
where (`sminq`.`Queue_token`.`join_date` >= ? and `sminq`.`Queue_token`.`join_date` < ?
and `sminq`.`Live_token_sequence`.`queue_id` = ? and `sminq`.`Calendar_time_slot`.`group_id` = ?) for update
包含新索引后,explin 输出
+------+-------------+---------------------+--------+---------------- ----------------------------------------------+------------+---------+--- -------------------------------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+---------------------+--------+---------------- ----------------------------------------------+------------+---------+--- -------------------------------------+------+-------------+
| 1 | SIMPLE | Calendar_time_slot | ref | slot_group,group_slot | group_slot | 4 | const | 6 | Using index |
| 1 | SIMPLE | Live_token_sequence | ref | PRIMARY,queue_sequence,queue_time_slot,queue_slot,slot_queue | queue_slot | 7 | const,sminq.Calendar_time_slot.slot_id | 1 | Using index |
| 1 | SIMPLE | Queue_token | eq_ref | PRIMARY,join_date_idx | PRIMARY | 4 | sminq.Live_token_sequence.token_id | 1 | Using where |
+------+-------------+---------------------+--------+---------------- ----------------------------------------------+------------+---------+--- -------------------------------------+------+-------------+
更容易阅读:
select count(qt.`token_id`)
from `Queue_token` AS qt
join `Live_token_sequence` AS seq ON qt.`token_id` = seq.`token_id`
join `Calendar_time_slot` AS cts ON seq.`time_slot_id` = cts.`slot_id`
join `Live_token_status` AS stat ON qt.`token_id` = stat.`token_id`
left outer join `Status_code` AS code
ON ( stat.`token_status_id` = code.`status_id`
and code.`status_type` not in (?) )
where (qt.`join_date` >= ?
and qt.`join_date` < ?
and seq.`queue_id` = ?
and cts.`group_id` = ?
)
for update
提防not in (?)
;如果给定绑定 '1,2,3',它可能导致 not in ('1,2,3')
,not 与 not in (1,2,3)
相同,也不 not in ('1','2','3')
].
而不是 COUNT(token_id)
,您可能需要 COUNT(DISTINCT token_id)
没有事务的其余部分,很难讨论锁定。
同时,让我们看看可能的加速。
两个或三个表似乎在 token_id
上处于 1:1 关系。不合并表是什么原因?
建议的附加索引:
seq: INDEX(queue_id, time_slot_id), INDEX(time_slot_id, queue_id)
(In doing so, KEY `queue_time_slot` (`time_slot_id`) can be removed.)
cts: INDEX(group_id, slot_id), INDEX(slot_id, group_id)
删除 LEFT JOIN Status_code ...
-- 它只会增加计数。
请提供EXPLAIN SELECT ...
;
"To generate the sequence we first fetch the count of tokens from the Queue_token table, and the next token generated gets the count + 1 sequence. We facing an issue where on concurrent inserts, tokens are getting the same, sequence."
这是一个非常糟糕的设计。
如果一行被删除,您肯定会生成一个副本。
如果您有两个线程 运行 'simultaneously' 并执行 'same' 操作,它们几乎肯定会生成重复值。这是因为 transaction_isolation_mode
,可能无法通过 FOR UPDATE
.
修复
使用 AUTO_INCREMENT
列替换它。 token_id
就是这样,不知道为什么不行。 AUTO_INCREMENT
保证生成不同的值,无论并发和删除。 (它没有说明两个 'concurrent inserts' 中哪一个的 ID 较小。)
我们有 2 tables
CREATE TABLE `Queue_token` (
`token_id` int(11) NOT NULL AUTO_INCREMENT,
`token_queue_id` int(11) NOT NULL,
`total_process_time` smallint(6) NOT NULL,
`token_user` int(11) DEFAULT NULL,
`created_on` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`join_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`join_time` time NOT NULL,
`app_type` tinyint(1) NOT NULL DEFAULT '1',
`is_advance` tinyint(1) NOT NULL DEFAULT '0',
`is_confirmed` tinyint(1) NOT NULL DEFAULT '1',
`token_user_group` int(11) DEFAULT NULL,
`uuid` binary(16) DEFAULT NULL,
PRIMARY KEY (`token_id`),
KEY `join_date_idx` (`join_date`),
KEY `queue_join_date` (`token_queue_id`,`join_date`),
KEY `token_user` (`token_user`),
KEY `fk_token_user_group` (`token_user_group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `Live_token_sequence` (
`token_id` int(11) NOT NULL,
`queue_id` int(11) NOT NULL,
`sequence` int(11) NOT NULL,
`time_slot_id` mediumint(9) NOT NULL,
`time_slot_sequence` tinyint(4) NOT NULL,
`created_on` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`token_id`),
KEY `queue_sequence` (`queue_id`,`sequence`),
KEY `queue_time_slot` (`time_slot_id`),
CONSTRAINT `token_id_seq_fk` FOREIGN KEY (`token_id`) REFERENCES `Queue_token` (`token_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8
根据在 Queue_token table 中为某个日期生成的标记数量,我们为 Live_token_sequence table.[=14 中的每个标记生成一个唯一序列=]
为了生成序列,我们首先从 Queue_token table 中获取令牌的计数,然后生成的下一个令牌获得计数 + 1 序列。
我们面临一个问题,即在并发插入时,令牌的顺序相同。如果我们尝试使用 SELECT FOR UPDATE,我们将面临死锁,因为上面的计数查询也会与其他 table 进行连接。
我们该怎么做?
更新------ 计数查询
```
select count(`sminq`.`Queue_token`.`token_id`)
from `sminq`.`Queue_token`
join `sminq`.`Live_token_sequence`
on `sminq`.`Queue_token`.`token_id` = `sminq`.`Live_token_sequence`.`token_id`
join `sminq`.`Calendar_time_slot`
on `sminq`.`Live_token_sequence`.`time_slot_id` = `sminq`.`Calendar_time_slot`.`slot_id`
join `sminq`.`Live_token_status` on `sminq`.`Queue_token`.`token_id` = `sminq`.`Live_token_status`.`token_id`
left outer join `sminq`.`Status_code`
on (`sminq`.`Live_token_status`.`token_status_id` = `sminq`.`Status_code`.`status_id`
and `sminq`.`Status_code`.`status_type` not in (?))
where (`sminq`.`Queue_token`.`join_date` >= ? and `sminq`.`Queue_token`.`join_date` < ?
and `sminq`.`Live_token_sequence`.`queue_id` = ? and `sminq`.`Calendar_time_slot`.`group_id` = ?) for update
包含新索引后,explin 输出
+------+-------------+---------------------+--------+---------------- ----------------------------------------------+------------+---------+--- -------------------------------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+---------------------+--------+---------------- ----------------------------------------------+------------+---------+--- -------------------------------------+------+-------------+
| 1 | SIMPLE | Calendar_time_slot | ref | slot_group,group_slot | group_slot | 4 | const | 6 | Using index |
| 1 | SIMPLE | Live_token_sequence | ref | PRIMARY,queue_sequence,queue_time_slot,queue_slot,slot_queue | queue_slot | 7 | const,sminq.Calendar_time_slot.slot_id | 1 | Using index |
| 1 | SIMPLE | Queue_token | eq_ref | PRIMARY,join_date_idx | PRIMARY | 4 | sminq.Live_token_sequence.token_id | 1 | Using where |
+------+-------------+---------------------+--------+---------------- ----------------------------------------------+------------+---------+--- -------------------------------------+------+-------------+
更容易阅读:
select count(qt.`token_id`)
from `Queue_token` AS qt
join `Live_token_sequence` AS seq ON qt.`token_id` = seq.`token_id`
join `Calendar_time_slot` AS cts ON seq.`time_slot_id` = cts.`slot_id`
join `Live_token_status` AS stat ON qt.`token_id` = stat.`token_id`
left outer join `Status_code` AS code
ON ( stat.`token_status_id` = code.`status_id`
and code.`status_type` not in (?) )
where (qt.`join_date` >= ?
and qt.`join_date` < ?
and seq.`queue_id` = ?
and cts.`group_id` = ?
)
for update
提防not in (?)
;如果给定绑定 '1,2,3',它可能导致 not in ('1,2,3')
,not 与 not in (1,2,3)
相同,也不 not in ('1','2','3')
].
而不是 COUNT(token_id)
,您可能需要 COUNT(DISTINCT token_id)
没有事务的其余部分,很难讨论锁定。
同时,让我们看看可能的加速。
两个或三个表似乎在 token_id
上处于 1:1 关系。不合并表是什么原因?
建议的附加索引:
seq: INDEX(queue_id, time_slot_id), INDEX(time_slot_id, queue_id)
(In doing so, KEY `queue_time_slot` (`time_slot_id`) can be removed.)
cts: INDEX(group_id, slot_id), INDEX(slot_id, group_id)
删除 LEFT JOIN Status_code ...
-- 它只会增加计数。
请提供EXPLAIN SELECT ...
;
"To generate the sequence we first fetch the count of tokens from the Queue_token table, and the next token generated gets the count + 1 sequence. We facing an issue where on concurrent inserts, tokens are getting the same, sequence."
这是一个非常糟糕的设计。
如果一行被删除,您肯定会生成一个副本。
如果您有两个线程 运行 'simultaneously' 并执行 'same' 操作,它们几乎肯定会生成重复值。这是因为 transaction_isolation_mode
,可能无法通过 FOR UPDATE
.
使用 AUTO_INCREMENT
列替换它。 token_id
就是这样,不知道为什么不行。 AUTO_INCREMENT
保证生成不同的值,无论并发和删除。 (它没有说明两个 'concurrent inserts' 中哪一个的 ID 较小。)