清除LAST_INSERT_ID()以在插入数据时判断返回值是否来自于我的插入

16

LAST_INSERT_ID() 返回当前连接中由自增列生成的最近 id,但是我如何确定该值来自于最后一个插入而不是上一个在同一连接上的插入呢?

假设我正在使用从池中获取的连接,在我获得该连接之前,该连接可能已经插入了一行,然后我执行条件插入:

insert into mytable (colA)
select 'foo' from bar
where <some condition>;
select LAST_INSERT_ID();

我无法确定返回的值是否来自我的插入操作。

我想到的一种方法是:

@previousId := LAST_INSERT_ID();
insert into mytable (colA)
select 'foo' from bar
where <some condition>;
select if(LAST_INSERT_ID() != @previousId, LAST_INSERT_ID(), null);

有没有一种方法可以“清除” LAST_INSERT_ID() 的值,以便在返回非零值时,我知道它是由我的 SQL 导致的新值?


这是一个部分解决的问题。你能发一下你想要解决的原始问题吗? - Simon Richter
1
使用Digital Chris建议的row_count有什么问题? - rsanchez
5个回答

13

我赞同@Digital Chris的回答,即您不应该通过检查LAST_INSERT_ID()返回的值来确定插入是否成功或失败:有更直接的途径,例如受影响的行数。然而,可能仍然需要从LAST_INSERT_ID()获取“干净”的值。

当然,您提出方案的一个问题是,插入操作可能会成功,并且其分配的自增值恰好与先前插入(假设在另一个表中)的值相同。因此,比较可能导致认为插入失败,而实际上它已经成功了。

我建议,如果可能的话,优先使用mysql_insert_id() API调用(通过您的驱动程序),而不是使用LAST_INSERT_ID() SQL函数。正如后者的文档中所解释的那样:

mysql_insert_id()如果前一个语句未使用AUTO_INCREMENT值,则返回0。如果需要稍后保存该值,请确保在生成该值的语句后立即调用mysql_insert_id()

[删除内容]

LAST_INSERT_ID()mysql_insert_id()之间的差异是,LAST_INSERT_ID()易于在脚本中使用,而mysql_insert_id()则试图提供有关AUTO_INCREMENT列发生了什么的更精确信息。

无论如何,根据LAST_INSERT_ID(expr)下记录的:

如果将expr作为参数传递给LAST_INSERT_ID(),则该函数将返回该参数的值,并记住下一个要由LAST_INSERT_ID()返回的值。

因此,在执行您的INSERT之前,您可以使用以下代码重置:

SELECT LAST_INSERT_ID(NULL);

这也应该重置由mysql_insert_id()返回的值,尽管文档建议调用LAST_INSERT_ID(expr)必须在INSERTUPDATE语句内进行 - 这可能需要测试来验证。无论如何,如果需要,创建这样的无操作语句应该非常简单:

INSERT INTO my_table (my_column) SELECT NULL WHERE LAST_INSERT_ID(NULL);

值得注意的是,也可以设置 identitylast_insert_id 系统变量(但这些变量仅影响由 LAST_INSERT_ID() 返回的值,而不影响由 mysql_insert_id() 返回的值):

SET @@last_insert_id := NULL;

9

你猜测可能会获得以前的LAST_INSERT_ID(),但实际上,我不知道这种情况会发生在哪里。如果你正在进行条件插入,你必须在继续下一步之前检查它是否成功。例如,你将始终使用ROW_COUNT()来检查已插入/更新的记录。伪代码:

insert into mytable (colA)
select 'foo' from bar
where <some condition>;

IF ROW_COUNT() > 0
    select LAST_INSERT_ID();
    -- ...use selected last_insert_id for other updates/inserts...
END IF;

5
使用 ROW_COUNT() 来确定您的条件插入尝试是否成功,然后根据此返回 LAST_INSERT_ID() 或默认值:
select IF(ROW_COUNT() > 0, LAST_INSERT_ID(), 0);

4

由于LAST_INSERT_ID()存在许多陷阱, 我使用一个AFTER INSERT触发器将ID记录在日志表中,以解决我尝试解决的问题。

在您的情况下,由于插入操作是有条件的,请使用唯一标识符来“标记”插入操作,然后检查插入日志中是否存在该标识符。如果标识标签存在,则已进行插入并且您拥有已插入的ID。如果标识标签不存在,则未进行插入操作。

参考实现

DELIMITER $$
CREATE TRIGGER MyTable_AI AFTER INSERT ON MyTable FOR EACH ROW
BEGIN
    INSERT INTO MyTable_InsertLog (myTableId, ident) VALUES (NEW.myTableId, COALESCE(@tag, CONNECTION_ID()));
END $$
DELIMITER ;

CREATE TABLE MyTable_InsertLog (
    myTableId BIGINT UNSIGNED PRIMARY KEY NOT NULL REFERENCES MyTable (myTableId),
    tag CHAR(32) NOT NULL
);

示例用法

SET @tag=MD5('A');
INSERT INTO MyTable SELECT NULL,colA FROM Foo WHERE colA='whatever';
SELECT myTableId FROM MyTable_InsertLog WHERE tag=@tag;
DELETE FROM MyTable_InsertLog WHERE tag=@tag;

如果插入成功,你将从选择中获取行 -- 并且这些行将包含你的 ID。没有行,也没有插入。无论哪种情况,都需要从插入日志中删除结果,以便在后续调用中重用该标记。

触发器插入后不会有所帮助。我的应用程序代码需要刚刚添加的行的ID,但仅当它添加时。我需要能够区分从函数返回的最后插入ID是否来自我刚刚执行的SQL。 - Bohemian
@Bohemian:如果我正确理解了您的用例,那么您肯定可以。请参考我的编辑以获取可工作的示例。 - bishop

2
< p > LAST_INSERT_ID() 函数的应用范围相对较窄:由于 MySQL 不支持 SQL 序列,该函数用于创建一个事务,将数据一致地插入到多个表中,如果其中一个表引用了另一个表的替代键:

CREATE TABLE foo (id INTEGER PRIMARY KEY AUTO_INCREMENT, value INTEGER NOT NULL);
CREATE TABLE bar (id INTEGER PRIMARY KEY AUTO_INCREMENT, fooref INTEGER NOT NULL);
INSERT INTO foo(value) VALUES ('1');
INSERT INTO bar(fooref) VALUES (LAST_INSERT_ID());

如果您真的想在条件插入中使用它,您需要通过将插入的值与第一个INSERT的SELECT连接并随后丢弃额外的列(以使第二个INSERT也具有零行或一行)来使第二个INSERT也是有条件的。
不过我建议不要这样做。这种方式的条件插入已经有点脆弱了,进一步构建只会给您的应用程序带来不稳定性。

不好。没有第二个插入。最后一个插入ID被应用程序代码在数据库之外使用。此外,这也没有解答问题。 - Bohemian
答案是:“你不应该以这种方式使用LAST_INSERT_ID()。”这就是为什么我评论要求提供原始问题的描述。 - Simon Richter
我想从SQL中获取最后插入的ID到我的应用程序中,但是我的SQL执行了一个有条件的插入,因此我需要能够区分来自我的插入或我正在使用的连接的上一个插入的最后插入的ID。如果它不是来自我的插入(尝试),我希望它为零或空。 - Bohemian
这不是一个受支持的用例。即使你找到了一个可行的解决方案,它也可能在未来的任何时候出现问题,而你将不得不自己承担后果。 - Simon Richter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接