15

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 引起的新值?

4

5 回答 5

12

我同意@Digital Chris 的回答,您不应该通过检查返回的值来确定插入是成功还是失败LAST_INSERT_ID():还有更多直接路线,例如受影响的行数。尽管如此,可能仍然需要从LAST_INSERT_ID().

当然,您提出的解决方案(与插入前的值进行比较)的一个问题是,插入可能会成功并且其分配的自动递增值与之前插入的值巧合地相同(大概在另一张桌子)。因此,比较可能会导致插入失败的假设,而实际上它已经成功。

我建议您尽可能避免使用LAST_INSERT_ID()SQL 函数而不是mysql_insert_id()API 调用(通过您的驱动程序)。如后者的文档中所述:

mysql_insert_id()0如果前一个语句不使用AUTO_INCREMENT值,则返回。如果您需要保存该值以供以后使用,请务必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(),尽管文档建议调用必须发生在or语句中——可能需要测试来验证。无论如何,如果需要,创建这样一个无操作语句应该是非常简单的: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;
于 2013-12-22T17:08:34.337 回答
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;
于 2013-12-20T14:06:06.330 回答
5

用于ROW_COUNT()确定您的条件插入尝试是否成功,然后LAST_INSERT_ID()基于此返回或默认值:

select IF(ROW_COUNT() > 0, LAST_INSERT_ID(), 0);
于 2013-12-29T01:05:39.560 回答
4

由于LAST_INSERT_ID()充满陷阱,我使用AFTER INSERT触发器将 ID 记录在日志表中,并以与我试图解决的问题一致的方式记录。

在您的场景中,由于插入是有条件的,因此使用 unqiue 标识符来“标记”插入,然后检查插入日志中是否存在该标记。如果存在识别标签,则您的插入发生并且您拥有插入的 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。没有行,没有插入。无论哪种方式,从插入日志中删除结果,以便您可以在后续调用中重复使用该标签。

于 2013-12-20T13:58:37.760 回答
2

LAST_INSERT_ID()函数的应用范围相当狭窄:由于 MySQL 不支持 SQL ,如果一个表引用了另一个表中的代理键,则SEQUENCE该函数用于创建一个将INSERT数据一致地写入多个表的事务:

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也有零或一行)来使第二个有条件.

不过,我建议不要这样做。这种方式的条件插入已经有些脆弱,在它之上构建不会增加应用程序的稳定性。

于 2013-12-20T14:09:40.263 回答