MySQL atomic insert-if-not-exists 具有稳定的自动增量
Posted
技术标签:
【中文标题】MySQL atomic insert-if-not-exists 具有稳定的自动增量【英文标题】:MySQL atomic insert-if-not-exists with stable autoincrement 【发布时间】:2017-11-16 23:03:47 【问题描述】:在 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"
运行,我将获得另一个ID 和下一个AUTO_INCREMENT
值,依此类推。检查已存在的名称不会增加表的 AUTO_INCREMENT
值。
我想知道是否有更好的解决方案来实现这一点,也许不需要“LOCK TABLES
”/“UNLOCK TABLES
”命令并结合更多“基本”命令(例如“INSERT
”和"SELECT
") 以更聪明的方式?或者这是 MySQL 目前必须提供的最好的方法?
编辑
这不是“How to 'insert if not exists' in MySQL?”的副本。这个问题并没有解决我所说的所有标准。保持AUTO_INCREMENT
值稳定的问题在那里没有解决(只是顺便提一下)。
许多答案没有解决获取现有/插入记录的 ID,一些答案不提供原子操作,一些答案的逻辑是在客户端而不是服务器上完成的-边。许多答案改变了现有记录,这不是我想要的。我要求一种更好的方法满足所有规定的标准,或者确认我的解决方案是现有 MySQL 支持的最佳解决方案。
【问题讨论】:
如果您只需要检查特定记录是否存在并在不存在的情况下进行插入,那么您不需要on duplicate key update
。您只需要 firstname 字段上的唯一索引,尝试插入新名称。如果插入成功,则调用 last_insert_id()。如果失败,则根据 firstname 值选择 id。
我不太确定您在以下 SO 问题之外还需要什么:***.com/questions/1361340/…
这不是一个重复的问题——这个问题实际上是关于如何避免AUTO_INCREMENT
被递增。
【参考方案1】:
问题实际上是关于如何在您期望有重复数据时标准化数据。然后避免“燃烧” id。
http://mysql.rjweb.org/doc.php/staging_table#normalization 讨论了一个两步过程,旨在由于高速摄取行而进行大规模更新。它退化为单行,但仍需要 2 个步骤。
第 1 步 INSERTs
任何 new 行,创建新的 auto_inc id。
第 2 步将 id 整体拉回。
请注意,最好使用 autocommit=ON 并在加载数据的主事务之外完成这项工作。这避免了烧毁 id 的额外原因,即潜在的回滚。
【讨论】:
以上是关于MySQL atomic insert-if-not-exists 具有稳定的自动增量的主要内容,如果未能解决你的问题,请参考以下文章