InnoDB: пользовательское автоинкремент с использованием выбора вставки. Может ли быть ошибка двойного ключа?
У меня есть таблица, как: idx (PK) clmn_1
Оба INT. idx
не определяется как автоинкремент, но я пытаюсь смоделировать его. Чтобы вставить в эту таблицу, я использую:
"INSERT INTO my_tbl (idx, clmn_1) \
SELECT IFNULL(MAX(idx), 0) + 1, %s \
FROM my_tbl", val_clmn_1
Теперь это работает. У меня вопрос об атомарности. Поскольку я читаю, а затем вставляю в одну и ту же таблицу, при одновременном выполнении нескольких вставок может ли быть ошибка с повторяющимся ключом?
И как я могу проверить это сам?
Я использую Percona XtraDB server 5.5.
1 ответ
Это не очень хорошее решение, потому что оно создает общую блокировку для my_tbl во время выполнения SELECT. Любое количество потоков может одновременно иметь общую блокировку, но она блокирует одновременные блокировки записи. Таким образом, это приводит к тому, что вставки сериализуются, ожидая завершения SELECT.
Вы можете наблюдать этот замок. Запустите этот запрос за один сеанс:
INSERT INTO my_tbl (idx, clmn_1)
SELECT IFNULL(MAX(idx), 0) + 1, 1234+SLEEP(60)
FROM my_tbl;
Затем перейдите к другому сеансу, запустите innotop и просмотрите экран блокировки (нажмите клавишу "L"). Вы увидите вывод так:
___________________________________ InnoDB Locks ___________________________________
ID Type Waiting Wait Active Mode DB Table Index Ins Intent Special
61 TABLE 0 00:00 00:00 IS test my_tbl 0
61 RECORD 0 00:00 00:00 S test my_tbl PRIMARY 0
Вот почему механизм автоинкремента работает так, как работает. Независимо от изоляции транзакции, поток вставки блокирует таблицу только на короткое время, чтобы увеличить число авто-включения. Это очень быстро. Затем блокировка снята, что позволяет другим потокам продолжить работу немедленно. Тем временем первый поток пытается завершить вставку.
См. http://dev.mysql.com/doc/refman/5.5/en/innodb-auto-increment-handling.html для получения дополнительной информации об автоматической блокировке приращения.
Я не уверен, почему вы хотите имитировать поведение автоинкремента, а не просто определять столбец как автоинкрементный столбец. Вы можете изменить существующую таблицу для автоматического увеличения.
Re ваш комментарий:
Даже если PK объявлен как автоинкремент, вы все равно можете указать значение. Автоинкремент включается, только если вы не указали столбец PK в INSERT или указали NULL
или же DEFAULT
как его ценность.
CREATE TABLE foo (id INT AUTO_INCREMENT PRIMARY KEY, c CHAR(1));
INSERT INTO foo (id, c) VALUES (123, 'x'); -- inserts value 123
INSERT INTO foo (id, c) VALUES (DEFAULT, 'y'); -- inserts value 124
INSERT INTO foo (id, c) VALUES (42, 'n'); -- inserts specified value 42
INSERT INTO foo (c) VALUES ('Z'); -- inserts value 125
REPLACE INTO foo (id, c) VALUES (125, 'z'); -- changes existing row with id=125
Re ваш комментарий:
START TRANSACTION;
SELECT IFNULL(MAX(idx), 0)+1 FROM my_tbl FOR UPDATE;
INSERT INTO my_tbl (idx, clmn_1) VALUES (new_idx_val, some_val);
COMMIT;
Это на самом деле хуже, чем ваша первая идея, потому что теперь SELECT...FOR UPDATE
создает блокировку X вместо блокировки S.
Вы действительно не должны пытаться заново изобрести поведение AUTO-INCREMENT, потому что любое решение SQL ограничено свойствами ACID. Auto-inc обязательно работает за пределами ACID.
Если вам нужно исправить существующие строки атомарно, используйте REPLACE или INSERT... ON DUPLICATE KEY UPDATE.