MySQL原子插入 - 如果不存在,具有稳定的自动增量

时间:2017-06-14 17:06:06

标签: mysql concurrency locking innodb concurrentmodification

在MySQL中,我使用的InnoDB表包含唯一的名称和这些名称的ID。客户端需要原子地检查现有名称,如果不存在则插入新名称,并获取ID。该ID是AUTO_INCREMENT值,在检查现有值时,无论the setting of "innodb_autoinc_lock_mode"如何,它都不得超出控制范围;这是因为通常会检查相同的名称(例如“Alice”),并且偶尔会出现一些新名称(例如“Bob”)。

INSERT...ON DUPLICATE KEY UPDATE”语句即使在重复键情况下也会导致AUTO_INCREMENT增加,具体取决于“innodb_autoinc_lock_mode”,因此是不可接受的。该ID将用作外键约束的目标(在另一个表中),因此不能更改现有ID。客户端在同时执行此操作时不得死锁,无论操作如何交错。

我想在原子操作期间进行处理(例如检查现有ID并决定是否进行插入)要在服务器端而不是客户端完成,以便延迟其他操作试图同时做同样事情的会话是最小的,不需要等待客户端处理。

我的测试表演示了这个名为FirstNames

CREATE TABLE `FirstNames` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `FirstName` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `FirstName_UNIQUE` (`FirstName`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

到目前为止,我提出的最佳解决方案如下:

COMMIT;
SET @myName='Alice';
SET @curId=NULL;
SET autocommit=0;
LOCK TABLES FirstNames WRITE;
SELECT Id INTO @curId FROM FirstNames WHERE FirstName = @myName;
INSERT INTO `FirstNames` (`FirstName`) SELECT @myName FROM DUAL WHERE @curId IS NULL;
COMMIT;
UNLOCK TABLES;
SET @curId=IF(@curId IS NULL, LAST_INSERT_ID(), @curId);
SELECT @curId;

这使用“LOCK TABLES...WRITE”按照MySQL "Interaction of Table Locking and Transactions" documentation中给出的说明来确定锁定InnoDB表的正确方法。此解决方案要求用户具有“LOCK TABLES”权限。

如果我使用@myName="Alice"运行上述查询,我​​会获得一个新ID,然后无论我运行多少次,都会继续获取相同的ID。如果我随后使用@myName="Bob"运行,则会获得另一个具有下一个AUTO_INCREMENT值的ID,依此类推。检查已存在的名称不会增加表的AUTO_INCREMENT值。

我想知道是否有更好的解决方案来实现这一点,也许是一个不需要“LOCK TABLES”/“UNLOCK TABLES”命令并结合更多“基本”命令(例如“{ {1}}“和”INSERT“)以更聪明的方式?或者这是MySQL目前提供的最佳方法吗?

修改

这不是“How to 'insert if not exists' in MySQL?”的副本。这个问题没有解决我所说的所有标准。保持SELECT值稳定的问题在那里没有得到解决(只是顺便提一下)。

许多答案都不涉及获取现有/插入记录的ID,一些答案不提供原子操作,而且一些答案的逻辑是在客户端而不是服务器上完成的-侧。许多答案改变了现有记录,这不是我想要的。我要求更好的方法以满足所有标准,或确认我的解决方案是现有MySQL支持的最佳解决方案。

1 个答案:

答案 0 :(得分:1)

问题实际上是关于如何在您希望重复数据时规范化数据。然后避免"燃烧" IDS。

http://mysql.rjweb.org/doc.php/staging_table#normalization讨论了一个两步过程,旨在通过高速摄取行来进行大量更新。它退化为一行,但仍然需要两个步骤。

第1步INSERTs任何行,创建新的auto_inc ID。

步骤2集中拉回ids。

请注意,最好使用autocommit = ON并在加载数据的主事务之外完成工作。这避免了燃烧ID的额外原因,即潜在的回滚。