В MySQL я использую таблицу InnoDB, которая содержит уникальные имена и идентификаторы для этих имен. Клиентам необходимо атомизировать проверку существующего имени, вставить новый, если он не существует, и получить идентификатор. Идентификатор - это значение AUTO_INCREMENT
, и он не должен увеличивать контроль над отсутствием контроля при проверке существующих значений независимо от настройки "innodb_autoinc_lock_mode
" ; это потому, что очень часто одно и то же имя будет проверяться (например, "Alice
" ), и время от времени будет появляться новое имя (например, "Bob
" ).
Оператор "INSERT...ON DUPLICATE KEY UPDATE
" вызывает увеличение AUTO_INCREMENT
даже в случае с дубликат-ключ, в зависимости от "innodb_autoinc_lock_mode
", и поэтому неприемлемо. Идентификатор будет использоваться как цель ограничения внешних ключей (в другой таблице), и, следовательно, изменить существующие идентификаторы нехорошо. Клиенты не должны заходить в тупик, когда они выполняют это действие одновременно, независимо от того, как операции могут чередоваться.
Мне нужна обработка во время атомной операции (например, проверка существующего идентификатора и принятие решения о том, следует ли делать вставку) для выполнения на стороне сервера, а не на стороне клиента, так что задержка для другого сеансы, пытающиеся сделать одно и то же одновременно, минимальны и не требуют ожидания обработки на стороне клиента.
Моя тестовая таблица, чтобы продемонстрировать это, называется FirstNames
:
CREATE TABLE `FirstNames` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`FirstName` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `FirstName_UNIQUE` (`FirstName`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Самое лучшее решение, которое я придумал до сих пор, выглядит следующим образом:
COMMIT;
SET @myName='Alice';
SET @curId=NULL;
SET autocommit=0;
LOCK TABLES FirstNames WRITE;
SELECT Id INTO @curId FROM FirstNames WHERE FirstName = @myName;
INSERT INTO `FirstNames` (`FirstName`) SELECT @myName FROM DUAL WHERE @curId IS NULL;
COMMIT;
UNLOCK TABLES;
SET @curId=IF(@curId IS NULL, LAST_INSERT_ID(), @curId);
SELECT @curId;
Это использует "LOCK TABLES...WRITE
", следуя инструкциям, приведенным в документации по MySQL "Взаимодействие таблицы и транзакций" для правильного пути для блокировки таблиц InnoDB. Это решение требует, чтобы пользователь имел привилегию "LOCK TABLES
".
Если я запустил указанный выше запрос с помощью @myName="Alice"
, я получу новый идентификатор, а затем продолжу получать тот же ID независимо от того, сколько раз я его запускаю. Если я запускаю с @myName="Bob"
, я получаю другой идентификатор со следующим значением AUTO_INCREMENT
и так далее. Проверка имени, которое уже существует, не увеличивает значение таблицы AUTO_INCREMENT
.
Мне интересно, есть ли лучшее решение для этого, возможно, такое, которое не требует команд "LOCK TABLES
" / "UNLOCK TABLES
" и объединяет более "рудиментарные" команды (например, "INSERT
" и "SELECT
" ) более умным способом? Или это лучшая методология, которую MySQL может предложить в настоящее время?
Изменить
Это не дубликат " "Как вставить, если не существует" в MySQL?". Этот вопрос не касается всех критериев, которые я изложил. Вопрос о сохранении стабильного значения AUTO_INCREMENT
не разрешен там (это упоминается только попутно).
Многие ответы не касаются получения идентификатора существующей/вставленной записи, некоторые из ответов не обеспечивают атомную операцию, а некоторые ответы имеют логику, выполняемую на стороне клиента, а не на сервере -боковая сторона. Ряд ответов изменяет существующую запись, и это не то, что я ищу. Я прошу либо лучший способ выполнить все указанные критерии, либо подтвердить, что мое решение является оптимальным с существующей поддержкой MySQL.