Является ли функция MySql LAST_INSERT_ID () гарантированно правильной?

36

Когда я делаю одну строку INSERTв таблице, в которой есть AUTO_INCREMENTстолбец, я хотел бы использовать LAST_INSERT_ID()функцию для возврата нового AUTO_INCREMENTзначения ed, сохраненного для этой строки.

Поскольку многие разработчики и администраторы Microsoft SQL Server, без сомнения, знают, что эквивалентная функциональность в SQL Server ( SCOPE_IDENTITYи @@IDENTITY) не обошлась без проблем .

Я знаю состояние документов MySQL:

Сгенерированный идентификатор поддерживается на сервере для каждого подключения . Это означает, что значение, возвращаемое функцией данному клиенту, является первым AUTO_INCREMENTзначением, сгенерированным для самого последнего оператора, влияющего на AUTO_INCREMENTстолбец этим клиентом . На это значение не могут повлиять другие клиенты, даже если они генерируют AUTO_INCREMENTсобственные значения. Такое поведение гарантирует, что каждый клиент может получить свой собственный идентификатор, не заботясь об активности других клиентов и не нуждаясь в блокировках или транзакциях.

(источник)

и даже зайти так далеко, чтобы сказать:

Использование столбцов LAST_INSERT_ID()и AUTO_INCREMENTстолбцов одновременно от нескольких клиентов вполне допустимо.

(источник)

Существуют ли какие-либо известные риски или сценарии, которые могут привести LAST_INSERT_ID()к неправильному возвращению значения?

Я использую MySQL 5.5 на CentOS 5.5 x64 и Fedora 16 x64 и движок InnoDB.

Кев
источник

Ответы:

35

Несколько предостережений, на которые я хотел бы обратить внимание при использовании LAST_INSERT_ID:

  1. Я знаю, что вы упомянули однорядные вставки. Но при выполнении многострочных вставок LAST_INSERT_ID()будет возвращено значение первой вставленной строки (а не последней).

  2. Если вставка не удалась, LAST_INSERT_ID()будет неопределенным. То же самое верно для автоматических откатов транзакций (из-за ошибок).

  3. Если вы выполните вставку в успешной транзакции и по-прежнему выполняете команду ROLLBACK, она LAST_INSERT_ID()останется такой, какой она была до отката.

  4. Есть несколько предостережений при использовании AUTO_INCREMENTи LAST_INSERT_IDпри репликации на основе операторов. Первое, когда используется в триггере или функции. Вторым является менее распространенный сценарий, когда ваш столбец auto_increment является частью составного первичного ключа и не является первым столбцом в ключе.

Дерек Дауни
источник
если у вас есть «ON DUPLICATE KEY UPDATE», которое также возвращает 0, если ничего не обновлено, если вы установите некоторое поле date_field = now (), оно всегда будет возвращаться правильно
max4ever
7

Подробнее о пункте 2 в ответе DTest:

В версиях MySQL , которые я использовал, это хорошая идея , чтобы в явной форме сбросить значение LAST_INSERT_ID до каждого блока кода , где вы собираетесь выполнить вставку.

Это можно сделать так:

-- initialize the LAST_INSERT_ID to some flag value:
SELECT LAST_INSERT_ID( some_flag_init_value_of_your_choice );
-- perform the insert  
INSERT INTO ttt (ccc) VALUES (vvv);
-- retrieve the id of the inserted row:  
SELECT LAST_INSERT_ID();

После выполнения вышеуказанной серии операторов вы узнаете, оказал ли вставка какое-либо влияние, проверив, было ли LAST_INSERT_ID по-прежнему установлено в "some_flag_init_value_of_your_choice" в конце выполнения.

В противном случае вы можете столкнуться со следующей проблемной ситуацией:

INSERT INTO ttt ( ccc ) VALUES ( 'a' );    -- assume this succeeds.
SELECT LAST_INSERT_ID();                   -- this will return the unique id of the new row with value 'a'.
INSERT INTO ttt ( ccc ) VALUES ( 'b' );    -- assume this FAILS.
SELECT LAST_INSERT_ID();                   -- this will STILL RETURN the unique id of the row with 'a'.

Поскольку вторая вставка завершилась неудачно , вы, возможно, ожидали, что второй вызов LAST_INSERT_ID либо возвратит NULL, либо что он даст пустой набор результатов (ноль строк). Тот факт, что он по-прежнему возвращает действительный целочисленный идентификатор, может ввести вас в заблуждение, полагая, что вторая вставка SUCCEEDED, если это не так.

Ситуация ДАЖЕ ВЕЧНАЯ, если учесть, что LAST_INSERT_ID будет продолжать сохранять и повторять последний успешный уникальный идентификатор, даже если последующие ошибочные операторы вставки нацелены на таблицы, отличные от таблицы, которая создала последний успешный уникальный идентификатор. Другими словами, вы вставляете в таблицу TA и получаете идентификатор 5, затем вставляете в TB (но он терпит неудачу), но вы все равно видите 5. На основании этого вы думаете, что вы только что создали новую строку в TA с идентификатором 5 и новой строкой в ​​ТБ с идентификатором 5, тогда как в действительности либо в ТБ не существует ни одной строки с идентификатором 5, или такая строка существует, но на самом деле она не имеет ничего общего с любым кодом, который вы только что RAN.

pestophagous
источник
2
Во-первых, вы не должны были использовать существование, last_insert_id()чтобы судить, был ли запрос успешным. В конце концов, это последний вставленный идентификатор, содержащий значения, которые вам нужны, когда вы уже знали, что добились успеха.
Pacerier