Выбор FOR UPDATE дает ошибку дублированного ключа - PullRequest
0 голосов
/ 26 декабря 2018

Я использую 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, толькокомбинация «продукт + ключ» должна быть уникальной.Требование, что это целое число, определенное таким образом, является организационным, я не думаю, что это то, что я могу сделать с auto_increment, используя innoDb, но, следовательно, вопрос.

Так что, если поставщик создает новую версиюсуществующий продукт, мы даем ему отдельный ключ для этой комбинации поставщика / продукта (я понимаю, что столбец 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

Это все!В периоды интенсивного использования (тысячи пользователей) я вижу: Дублирующую запись «Ace-Hammer-Ace-Hammer-44613» для ключа «pkKey».

Я могу повторить транзакцию, но это не такошибка, которую я ожидал, и я хотел бы понять, почему это происходит.Я мог понять блокировку строк, вызывающую взаимоблокировку, но в этом случае кажется, что строки вообще не заблокированы.Интересно, проблема в max () в этом контексте или, возможно, в табличном индексе.Эта операция является единственной транзакцией, которая выполняется на этой таблице.

Любое понимание приветствуется.Я прочитал несколько постов MySql / SO на эту тему, большинство проблем и проблем, похоже, связано с оверлоком или тупиками.Например, здесь: Что именно блокирует FOR UPDATE в MySQL, что именно блокируется?

1 Ответ

0 голосов
/ 27 декабря 2018

Чтобы достичь «только комбинация« продукт + ключ »должна быть уникальной», скажем

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.

...