我正在使用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
仅此而已!在大量使用期间(数千名用户),我看到: 密钥“ pkKey”的条目“ Ace-Hammer-Ace-Hammer-44613”重复。
我可以重试该事务,但这不是我所期望的错误,我想了解为什么会发生。我可以理解导致死锁的行锁定,但是在这种情况下,似乎行根本没有被锁定。我想知道问题是否在这种情况下与max()有关,或者可能与表索引有关。此存储过程是对此表执行的唯一事务。
任何见识都值得赞赏。我已经阅读了有关该主题的多个MySql / SO帖子,大多数担忧和问题似乎与过度锁定或死锁有关。例如。此处:When using MySQL's FOR UPDATE locking, what is exactly locked?
答案 0 :(得分:0)
要实现“只有产品和组合键才需要唯一”,说
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)
。
现在,我不清楚您是否需要将“梅纳德斯”和“王牌”锤都设为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
。
答案 1 :(得分:0)
我有点不好意思,但这是一个非常明显的问题。问题是“ FOR UPDATE”仅锁定当前行。这样您就可以更新它。但是我正在做一个INSERT!不是更新。
如果两个查询发生冲突,则该行被锁定,但是在事务完成之后,该行被解锁并且可以读取。因此,您仍在读取过时的值。要获得我所期望的行为,您需要锁定整个表。
因此,我认为自动增量将对我有用,尽管我需要一种获取last_inserted_id的方法,因此无论如何我都需要处于过程上下文中(我正在使用c#驱动程序)。