带有 LAST_INSERT_ID() 的 MySQL 函数给出了意想不到的结果

Posted

技术标签:

【中文标题】带有 LAST_INSERT_ID() 的 MySQL 函数给出了意想不到的结果【英文标题】:MySQL Function with LAST_INSERT_ID() giving unexpected results 【发布时间】:2015-05-16 10:21:09 【问题描述】:

我已经为此花费了 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 列(这是自动增量键)。当它不存在时,它应该插入它并返回新(自动)分配的id。

该功能正在运行,但做了两件我不想要的事情:

1) 当记录已经存在时,它返回零而不是现有记录的id。我的代码旨在解决这个问题,但它不起作用请注意我在表上定义了一个索引以确保 state.name 在表中是唯一的,以便 INSERT IGNORE 工作

2) LAST_INSERT_ID() 即使在记录存在且没有插入任何内容的情况下也会不断增加。因此,例如,如果状态表只有一条记录开头(比如 id==>1,name ==> 'State A',),如果我调用该函数 2 次尝试重新插入 'State A'(其中不会像上面第 1 点中提到的那样插入)然后再次调用它来插入“状态 B”,该表现在将有第二条记录 id==>4,name ==>“状态 B”跳过 id 值 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;

结束$$

【问题讨论】:

【参考方案1】:

Lorenz 已经给你解决方案了:

 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;         

如果您在同一时间多次调用同一个函数,那么您仍有可能在 id 之间出现差距,那么您需要不同的方法:

1) 删除你的 id 上的 auto_increment 2) 在 id 上创建唯一索引 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 时,它可能不适用于第一个状态。

【讨论】:

我会选择你的第一个选项,但它不起作用。无论记录是否存在, ret 都返回 NULL 并且没有插入任何内容。当我将 if 语句切换为 IF ret IS NULL 时,至少插入了新状态,但两个条件都返回 ret 值为零。这太令人困惑了,我看不出代码有什么问题!我绝对需要在 ret 中返回新的或现有的 id 值。 为了解决问题,我进一步将第二个“SELECT id into ret...”更改为“SELECT LAST_INSERT_ID() INTO ret;”。现在当状态是新的时,它会返回新的 id。剩下的问题是:当它是一个现有状态时,它返回零。 第一个返回应该是“RETURN ret;”不需要 Ret2 我知道:这是为了排除故障,因为 ret 不起作用。你看到“插入调试(dump1,dump2)值('ret will be',ret);”这一行吗?好吧,它在调试表中转储为零,所以从一开始就存在从第一个 select 语句获取 ID 的问题。我盯着那句话,看不出有什么问题。我直接在 MySQL 控制台中运行它,将“dname”替换为状态表中存在的实际值,它会正确返回 id。所以我的想法是 SELECT INTO 有问题,但究竟是什么? 您需要转储您的插入值以确保您正确调用该函数。【参考方案2】:

这是 InnoDB 的预期行为。 insert 总是递增 insert_id,即使插入失败。原因是数据库引擎在尝试插入之前保留了 id。

您可以通过首先选择表中的行来解决此问题,并且仅当名称尚不存在时才插入该名称。

此外,您将函数声明为确定性,但事实并非如此。仅仅声明它并不能使它具有确定性。

【讨论】:

当我删除确定性时,我得到“错误代码:1418 此函数在其声明中没有 DETERMINISTIC、NO SQL 或 READS SQL DATA,并且启用了二进制日志记录(您可能 想要使用不太安全的 log_bin_trust_function_creators 变量)”。放 NOT DETERMINISTIC 也会导致其他一些令人困惑的胡言乱语。同时,我认为您解决了问题 2,但问题 1 我该怎么办?

以上是关于带有 LAST_INSERT_ID() 的 MySQL 函数给出了意想不到的结果的主要内容,如果未能解决你的问题,请参考以下文章

Perl 和 MySQL - 使用 DBI 从表的最后一行返回字段,可能带有 last_insert_id?

转 mysql中的LAST_INSERT_ID()分析

使用 LAST_INSERT_ID 插入

PHP SQL 将 LAST_INSERT_ID() 赋值给一个变量

Java中last_insert_id的使用

存储过程的 LAST_INSERT_ID() 结果