Обновите / Назначьте внешний ключ к существующей строке таблицы и не перезаписывайте ее [mysql] - PullRequest
1 голос
/ 03 декабря 2011

У меня есть таблица с названием Promotion_codes

CREATE TABLE promotion_codes (
  id int(10) UNSIGNED NOT NULL auto_increment,
  created_at datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  code varchar(255) NOT NULL,
  order_id int(10) UNSIGNED NULL DEFAULT NULL,
  allocated_at datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

Эта таблица предварительно заполнена доступными кодами, которые будут назначены заказам, которые соответствуют определенным критериям.

Мне нужно убедиться, что после создания ЗАКАЗА я получу доступный промо-код и обновлю его запись, чтобы отразить, что он был распределен.

Я не уверен на 100%, как не захватить одну и ту же запись дважды, если поступают одновременные запросы.

Я попытался заблокировать строку во время выбора и заблокировать строку во время обновления - оба они, похоже, позволяют вторую (одновременную) попытку получить одну и ту же запись - чего я хочу избежать

UPDATE promotion_code 
SET allocated_at = "' . $db_now . '", order_id = ' . $donation->id . ' 
WHERE order_id IS NULL LIMIT 1

1 Ответ

0 голосов
/ 04 декабря 2011

Вы можете добавить вторую таблицу, которая содержит все используемые коды. Таким образом, вы можете использовать уникальное ограничение в таблице назначений, чтобы убедиться, что один код не назначен дважды.

CREATE TABLE `used_codes` (`usage` INTEGER PRIMARY KEY auto_increment,
 `id` INTEGER NOT NULL UNIQ, -- This makes sure, that there are no two assignments of one code
  allocated_at datetime NOT NULL);

Вы добавляете идентификатор использованного кода в таблицу used_codes и запрашиваете, какой код вы использовали впоследствии. Когда эти две операции выполняются в одной транзакции, вся транзакция завершится неудачно при второй попытке использовать один и тот же код.

Я не тестировал следующий код, вы можете его настроить.

Также вам необходимо убедиться, что ваш сервер соответствует требованиям для транзакций .

-- There are changes which have to be atomic, so don't use autocommit
SET autocommit = 0;
BEGIN TRANSACTION
INSERT INTO `used_codes` (`id`, `allocated_at`) VALUES
  (SELECT `id` FROM `promotion_codes`
    WHERE NOT `id` in (SELECT `id` FROM `used_codes`)
   LIMIT 1), now());
SELECT `code` FROM `promotion_codes` WHERE `id` =
  -- You might need to adjust the extraction of insertion ID, since
  -- I don't know if parallel running transactions can see the maximum
  -- their maximum IDs. But there should be a way to extract the last assigned
  -- ID within this transaction.
  (SELECT `id` FROM `used_codes` HAVING `usage` = max(`usage`));
COMMIT

Вы можете использовать возвращенный код, если транзакция прошла успешно. Если там, где более одного процесса запущено для использования одного и того же кода, только один из них завершился успешно, в то время как остальные завершаются неудачно с ошибками вставки о дублированной строке. В вашем программном обеспечении вам необходимо различать ошибку дублируемой строки и другие ошибки и повторно выполнять оператор ошибок дублирования.

...