在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支持的最佳解决方案。
答案 0 :(得分:1)
问题实际上是关于如何在您希望重复数据时规范化数据。然后避免"燃烧" IDS。
http://mysql.rjweb.org/doc.php/staging_table#normalization讨论了一个两步过程,旨在通过高速摄取行来进行大量更新。它退化为一行,但仍然需要两个步骤。
第1步INSERTs
任何新行,创建新的auto_inc ID。
步骤2集中拉回ids。
请注意,最好使用autocommit = ON并在加载数据的主事务之外完成工作。这避免了燃烧ID的额外原因,即潜在的回滚。