Select FOR UPDATE 给出重复键错误

Select FOR UPDATE gives duplicate key error

我正在使用 SELECT...FOR UPDATE 强制执行唯一密钥。我的 table 看起来像:

CREATE TABLE tblProductKeys (
  pkKey varchar(100) DEFAULT NULL,
  fkVendor varchar(50) DEFAULT NULL,
  productType varchar(100) DEFAULT NULL,
  productKey bigint(20) DEFAULT NULL,
  UNIQUE KEY pkKey (pkKey,fkVendor,productType,productKey)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

因此行可能如下所示:

{'Ace-Hammer','Ace','Hammer',121}, 
{'Ace-Hammer','Ace','Hammer',122},
... 
{'Menards-Hammer','Menards','Hammer',121},
...

所以注意'Ace-Hammer'和'Menards-Hammer'可以有相同的productKey,只是product+key的组合需要是唯一的。它是一个以这种方式定义的整数的要求是有组织的,我不认为这是我可以使用 innoDb auto_increment 做的事情,但因此问题。

因此,如果供应商创建现有产品的新版本,我们会为该 vendor/product 组合提供一个不同的密钥(我意识到 pkKey 列在这些示例中是多余的)。

我的存储过程是这样的:

CREATE PROCEDURE getNewKey(IN vkey varchar(50),vvendor varchar(50),vkeyType varchar(50)) BEGIN
start transaction;

set @newKey=(select max(productKey) from tblProductKeys where pkKey=vkey and fkVendor=vvendor and productType=vkeyType FOR UPDATE);
set @newKey=coalesce(@newKey,0);
set @newKey=@newKey+1;
insert into tblProductKeys values (vkey,vclient,vkeyType,@newKey);

commit;
select @newKey as keyMax;
END

就是这样!在大量使用期间(1000 个用户),我看到: 键 'pkKey' 的重复条目 'Ace-Hammer-Ace-Hammer-44613'。

我可以重试交易,但这不是我所期望的错误,我想了解为什么会发生这种情况。我可以理解导致死锁的行锁定,但在这种情况下,行似乎根本没有被锁定。我想知道在这种情况下问题是出在 max() 上,还是出在 table 索引上。此存储过程是在此 table.

上执行的唯一事务

如有任何见解,我们将不胜感激。我已经阅读了几篇关于这个主题的 MySql/SO 帖子,大多数关注点和问题似乎都与过度锁定或死锁有关。例如。这里:When using MySQL's FOR UPDATE locking, what is exactly locked?

实现"only the product+key combination needs to be unique",说

UNIQUE(pkKey, productKey)

顺序不限。然后,您的 4 列 UNIQUE 是多余的。如果某些特定查询需要,它可以变成普通的INDEX

此外,你真的应该有一个PRIMARY KEY。还不如

PRIMARY KEY(pkKey, productKey)  -- in either order

然后删除我建议的 UNIQUE 密钥。

没有充分的理由让 productKey 依赖于 pkKey,如果这是您的想法。相反,只需做

productKey INT UNSIGNED AUTO_INCREMENT

至少需要 INDEX(productKey).

现在,我不清楚您是否需要 'Menards' 和 'Ace' 锤子都为 121 号?摘要:

PRIMARY KEY(pkKey, productKey),
INDEX(productKey)

情况1:两者都需要是“121”。您需要一些方法来显式插入具有现有 auto-inc 值的新行。这不是问题;您只需指定“121”而不是让它获取下一个自动增量值。

情况2:没有必要两者都是“121”。然后简单地使用AUTO_INCREMENT的全部力量:

PRIMARY KEY(productKey)

但是如果你真的很喜欢你的SP,那我们就把它精简成一个语句,甚至折腾交易:

BEGIN;
    INSERT
         INTO  tblProductKeys 
    SELECT  vkey, vclient, vkeyType,
            @new_id := COALESCE(MAX(productKey) + 1, 0)
        FROM  tblProductKeys
        WHERE  pkKey = vkey
          AND  fkVendor = vvendor
          AND  productType = vkeyType;
END //

现在,您需要

INDEX(pkKey, fkVendor, productType,  -- in any order
      productKey)                    -- last
PRIMARY KEY(pkKey, productKey)  -- in either order (as previously discussed)

然后在SP外使用@new_id

我有点尴尬,但这是一个很明显的问题。问题是 'FOR UPDATE' 只锁定当前行。所以你可以更新它。但我正在做一个插入!不是更新。

如果 2 个查询发生冲突,该行将被锁定,但在事务完成后,该行将被解锁并可以读取。所以你仍在阅读陈旧的价值。要获得我预期的行为,您需要锁定整个 table.

所以我认为自动递增对我有用,尽管我需要一种方法来获得 last_inserted_id 所以无论如何我都需要在过程的上下文中(我使用的是 c# 驱动程序)。