mysql - 选择 FOR UPDATE 给出重复键错误
问题描述
我正在使用 SELECT...FOR UPDATE 来强制执行唯一键。我的桌子看起来像:
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 做的事情,但因此是个问题。
因此,如果供应商创建了现有产品的新版本,我们为该供应商/产品组合提供一个不同的密钥(我意识到 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() 或可能是表索引有关。此存储过程是在此表上执行的唯一事务。
任何见解都值得赞赏。我已经阅读了有关该主题的几篇 MySql/SO 帖子,大多数问题和问题似乎与过度锁定或死锁有关。例如这里: 当使用 MySQL 的 FOR UPDATE 锁定时,究竟锁定了什么?
解决方案
实现“只有产品+按键组合需要唯一”,说
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' 而不是让它获取下一个 auto-inc 值。
情况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)
然后@new_id
在SP外使用。
推荐阅读
- java - 如何使用 Intent 将自定义类 ArrayList 传递给另一个 Activity?
- node.js - 使用 Agora 将语音空间限制为 60 分钟
- python - 满足特定条件后在列中查找值
- webpack - 我想将域相关文件复制到我的 dist 文件夹,以便我可以提供该文件,我使用了 copy-webpack-plugin 但它不起作用
- java - IntelliJ 无法检测到 jdk.crypto.cryptoki 包
- c++ - 使用没有定义 c++ 的函数是否合法?
- swift - UITextView 字体调整
- azure-blob-storage - C#:使用 Azure.Storage.Blobs 客户端库将 blob 从一个容器移动到另一个容器
- mysql - 如何在mysql中以一个顺序获得一组结果,并以不同顺序获得另一组行?
- wordpress - 无法使用禁用 wordfence 的 wordpress 管理员登录