Функция MySQL с LAST_INSERT_ID() дает неожиданные результаты

Я потратил на это 24 часа, и мне нужно использовать имеющиеся знания для ускорения.

У меня есть эта функция mysql:

DELIMITER $$
USE `db`$$

DROP FUNCTION IF EXISTS `insertindb`$$

CREATE DEFINER=`root`@`localhost` FUNCTION `insertindb`(itemtype  VARCHAR(10), dname VARCHAR (50), id INTEGER, fname VARCHAR(50), lname VARCHAR(50)) RETURNS INT(11)
DETERMINISTIC
BEGIN 
    DECLARE ret INT;
    DECLARE ret2 INT;
    IF itemtype = 'state' THEN 
        INSERT IGNORE INTO state (`name`) VALUES (dname);
        SELECT LAST_INSERT_ID() INTO ret;       
        IF ret = 0 THEN
           SELECT `id` INTO ret2 FROM state WHERE `name` LIKE dname;
           SET ret = ret2;
        END IF;              
    END IF;
  RETURN ret;
END$$

DELIMITER ;

Цель? Когда состояние уже существует в таблице state, оно должно возвращать столбец id (который является ключом автоинкремента). Если он не существует, он должен вставить его и вернуть новый (автоматически) назначенный идентификатор.

Функция работает, но делать 2 вещи я не хочу:

1) когда запись уже существует, она возвращает ноль, а не идентификатор существующей записи. Мой код предназначен для решения этой проблемы, но он не работает. Пожалуйста, обратите внимание, что я определил индекс в таблице, чтобы обеспечить уникальность state.name в таблице, чтобы INSERT IGNORE работал

2) LAST_INSERT_ID() продолжает увеличиваться, даже если запись существует и, следовательно, ничего не было вставлено. Так, например, если в таблице состояний есть только одна запись для начала (скажем, id ==> 1, имя ==> «Состояние A»), если я вызову функцию 2 раза, чтобы попытаться повторно вставить «Состояние A» (что не будет вставляться, как указано в пункте 1 выше), а затем снова вызовите его, чтобы вставить «Состояние B», теперь таблица будет иметь вторую запись с идентификатором ==> 4, имя ==> «Состояние B», пропуская значения идентификатора 2 и 3 , Есть ли способ заставить LAST_INSERT_ID работать по-другому?

Мой код сейчас

BEGIN 
DECLARE ret,ret2 INT;

IF itemtype = 'state' THEN 
    SELECT `id` INTO ret FROM state WHERE `name` LIKE dname;
    #insert into debug (dump1,dump2) values ('ret will be',ret);
    IF ret IS NULL OR ret = '' THEN

        INSERT IGNORE INTO state (`name`) VALUES (dname);
        SELECT LAST_INSERT_ID() INTO ret2;  
        #SELECT `id` INTO ret FROM state WHERE `name` LIKE dname;
        RETURN ret2;
    ELSE
        RETURN ret;
    END IF; 
END IF;

END$$


person user1729972    schedule 16.05.2015    source источник


Ответы (2)


Лоренц уже дал вам решение:

 SELECT `id` INTO ret FROM state WHERE `name` LIKE dname;
 IF ret IS NULL THEN
     INSERT IGNORE INTO state (`name`) VALUES (dname);
     SELECT `id` INTO ret FROM state WHERE `name` LIKE dname;
 END IF;         

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

1) удалите auto_increment для вашего идентификатора 2) создайте уникальный индекс для идентификатора 3) используйте этот тип запроса для вставки новых значений:

INSERT INTO state (name, id)
SELECT dname, MAX(id) + 1 FROM state
WHERE NOT EXISTS (SELECT * FROM state WHERE name=dname);
SELECT id INTO ret FROM state WHERE name=dname;

Это может не работать для первого состояния, когда MAX(id) возвращает NULL.

person Tim3880    schedule 16.05.2015
comment
Я собираюсь с вашим первым вариантом, но он не работает. Независимо от того, существует запись или нет, ret возвращает NULL и ничего не вставляется. Когда я переключаю оператор if на IF ret IS NULL, по крайней мере новое состояние вставляется, но оба условия возвращают нулевое значение ret. Это так озадачивает, я не вижу ничего плохого в коде! Мне абсолютно необходимо, чтобы новое или существующее значение идентификатора возвращалось в ret. - person user1729972; 16.05.2015
comment
Для устранения неполадок я дополнительно изменил второй идентификатор SELECT на ret... на SELECT LAST_INSERT_ID() INTO ret;. Теперь, когда состояние новое, он возвращает новый идентификатор. Оставшаяся проблема заключается в следующем: когда это существующее состояние, оно возвращает ret, равное нулю. - person user1729972; 16.05.2015
comment
Первый возврат должен быть RETURN ret; Ret2 не нужен - person Tim3880; 16.05.2015
comment
Я в курсе: это было сделано для устранения неполадок, так как ret не работал. Вы видите строку, вставляемую в значения отладки (dump1,dump2) ('ret will be',ret);? Ну, это сбрасывает ноль в таблицу отладки, поэтому с самого начала возникает проблема с получением идентификатора из первого оператора выбора. Я смотрел на это заявление и не вижу в нем ничего плохого. Я запустил его прямо в консоли MySQL, заменив «dname» фактическим значением, присутствующим в таблице состояний, и он правильно возвращает идентификатор. Итак, я думаю, что что-то не так с SELECT INTO, но что именно? - person user1729972; 16.05.2015
comment
Вам нужно сбросить значение вставки, чтобы убедиться, что вы правильно вызываете функцию. - person Tim3880; 16.05.2015
comment
Я также сбросил dname, и все в порядке. Я в растерянности. - person user1729972; 16.05.2015
comment
Давайте продолжим обсуждение в чате. - person Tim3880; 16.05.2015
comment
...id INTEGER... в списке параметров определения функции конфликтует с идентификатором, используемым в операторе SELECT в теле функции. - person user1729972; 17.05.2015

Это ожидаемое поведение с InnoDB. insert всегда увеличивает insert_id, даже если вставка не удалась. Причина в том, что механизм базы данных резервирует идентификатор перед попыткой вставки.

Вы можете решить проблему, выбрав сначала строку в таблице и вставив имя, только если оно еще не существует.

Кроме того, вы объявляете свою функцию детерминированной, но это не так. Простое объявление не делает его детерминированным.

person Lorenz Meyer    schedule 16.05.2015
comment
Когда я удаляю детерминированность, я получаю код ошибки: 1418. Эта функция не имеет ни одного из DETERMINISTIC, NO SQL или READS SQL DATA в своем объявлении, и включено двоичное ведение журнала (вы можете использовать менее безопасную переменную log_bin_trust_function_creators ). Использование НЕ ДЕТЕРМИНИСТИЧЕСКОГО также приводит к другой запутанной чепухе. Между тем, я думаю, вы отвечаете на вопрос 2, но что я могу сделать с вопросом 1? - person user1729972; 16.05.2015