在 MySQL 的 TRIGGER 中更改 LAST_INSERT_ID()

Posted

技术标签:

【中文标题】在 MySQL 的 TRIGGER 中更改 LAST_INSERT_ID()【英文标题】:Alter the LAST_INSERT_ID() from within a TRIGGER in MySQL 【发布时间】:2015-02-13 01:47:13 【问题描述】:

我有一个 BEFORE INSERT TRIGGER 用于计算列的 AUTO_INCREMENT 值 (id_2)。

id_1 | id_2 | data
1    | 1    | 'a'
1    | 2    | 'b'
1    | 3    | 'c'
2    | 1    | 'a'
2    | 2    | 'b'
2    | 3    | 'c'
2    | 4    | 'a'
3    | 1    | 'b'
3    | 2    | 'c'

我有 PRIMARY(id_1, id_2) 并且我正在使用 InnoDB。之前,该表使用 MyISAM,我没有遇到任何问题:id_2 设置为 AUTO_INCREMENT,因此id_1 的每个新条目都会自行生成新的id_2。现在,在切换到 InnoDB 之后,我有这个触发器来做同样的事情:

SET @id = NULL;
SELECT COALESCE(MAX(id_2) + 1, 1) INTO @id FROM tbl WHERE id_1 = NEW.id_1;
SET NEW.id_2= @id;

它工作得很好,除了现在LAST_INSERT_ID() 的值错误(它返回 0)。很多代码取决于LAST_INSERT_ID() 是否正确。但是,从 mysql 5.0.12 开始,对 TRIGGERS 中的LAST_INSERT_ID 所做的任何更改都不会影响全局值。有没有办法绕过这个?我可以通过调用LAST_INSERT_ID(NEW.id_2) 轻松设置AFTER UPDATE TRIGGER,它会更改LAST_INSERT_ID,但是任何客户端都会将LAST_INSERT_ID 设置为0。

是否有任何可行的解决方法来强制 MySQL 保持在触发器内部更改的 LAST_INSERT_ID 的状态?除了切换回支持开箱即用的 MyISAM 或运行另一个 SELECT max(id_2) FROM tbl WHERE id_1 = :id 作为事务的一部分以确保找到的行将是之前插入的行之外,还有其他选择吗?

> SHOW CREATE TABLE tbl;

CREATE TABLE `tbl` (
   `id_1` int(11) NOT NULL,
   `id_2` int(11) NOT NULL,
   `data` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
   PRIMARY KEY (`id_1`,`id_2`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

示例:

INSERT INTO tbl (id_1, id_2, data) VALUES (1, NULL, 'd');
SELECT LAST_INSERT_ID();

第一条语句将1 | 4 | 'd' 行插入到表中。第二条语句将返回0,但我需要它返回4

根据Ravinder Reddy的要求,添加关于系统的简短说明:

我有一个包含 baskets 的表格,还有另一个包含 items 的表格 (tbl)。 basket 由应用程序创建,并分配了一个来自篮子表上AUTO_INCREMENT 的ID。任务是将 ID = id_1 的购物篮中的 items 插入 tbl,并在该购物篮的范围内为其分配一个唯一 ID。每个item都有一些与之关联的data,它们可能在同一个basket中重复。所以在实践中,我试图将所有 data 条目存储在一个 basket 中,然后能够通过它们的 id_1-id_2 引用(和检索)这些单独的条目对。

【问题讨论】:

我有点困惑,但是在执行 INSERT 之前你怎么会有一个 LAST_INSERT_ID() 呢?我相信 LAST_INSERT_ID() 只有在同一客户端连接上执行插入时才可靠。这意味着如果应用程序打开一个连接,执行插入,然后关闭连接,您将无法简单地打开一个新连接并访问 LAST_INSERT_ID() 而无需在调用函数之前对该连接实际执行插入。 在 INSERT 完成后,我正在调用 LAST_INSERT_ID(),所以在触发器完成运行后。换句话说,为双列主键的第二列生成值的触发器无法传达新设置的值。我在问题中添加了一个示例。 @Xeos: 你能在帖子中添加show create table tbl 输出吗? @RavinderReddy 我已经编辑了问题以包含输出。 【参考方案1】:

根据您的表结构描述,很明显它没有可以自动生成值的主键字段。 MySQL 的information_schema.tables 不包含auto_increment 值,但null 用于那些未定义auto_increment 的字段。

触发问题

触发器主体中使用的代码块似乎取决于 id 字段的显式计算和输入。它没有使用 auto_increment 字段的默认行为。

根据MySQL's documentation on LAST_INSERT_ID

LAST_INSERT_ID() 返回 BIGINT UNSIGNED(64 位)值 表示第一个自动生成值 成功插入 AUTO_INCREMENT 列 作为最近执行的 INSERT 语句的结果。

很明显,它仅适用于 auto_increment 字段。id_1id_2 字段均未归属于 auto_increment。 由于这个原因,尽管您在插入时将null 作为这些字段的输入传递,但不会自动生成任何值并分配给它们。

更改您的表格以将auto_increment 设置为id_x 字段之一,然后开始插入值。一个注意事项是,在插入过程中将值显式传递给auto_increment 字段将导致last_insert_id 返回zero 或最近的自动生成值,而不是NEW.id。在插入过程中传递null 或不选择auto_increment 字段将触发该字段的新值生成,last_insert_id 可以选择并返回它。

以下示例演示了上述行为

mysql> drop table if exists so_q27476005;
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> create table so_q27476005( i int primary key );
Query OK, 0 rows affected (0.33 sec)

以下语句显示字段的下一个适用auto_increment 值。

mysql> select auto_increment
    ->   from information_schema.tables
    ->  where table_name='so_q27476005';
+----------------+
| auto_increment |
+----------------+
|           NULL |
+----------------+
1 row in set (0.00 sec)

让我们尝试在字段中插入一个null 值。

mysql> insert into so_q27476005 values( null );
ERROR 1048 (23000): Column 'i' cannot be null

上述语句失败,因为输入到 not null primary key 字段但未归因于 auto_increment。仅对于 auto_increment 字段,您可以传递 null 输入。

现在让我们看看last_insert_id的行为:

mysql> insert into so_q27476005 values( 1 );
Query OK, 1 row affected (0.04 sec)

mysql> select last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
|                0 |
+------------------+
1 row in set (0.00 sec)

由于输入是明确的,并且该字段未归因于auto_increment, 调用last_insert_id 得到0。请注意,这也可以是其他一些值, 如果有另一个insert 调用另一个表的任何其他auto_increment 字段, 在同一个数据库连接会话中。

让我们看看表中的记录。

mysql> select * from so_q27476005;
+---+
| i |
+---+
| 1 |
+---+
1 row in set (0.00 sec)

现在,让我们将auto_increment 应用于字段i

mysql> alter table so_q27476005 change column i i int auto_increment;
Query OK, 1 row affected (0.66 sec)
Records: 1  Duplicates: 0  Warnings: 0

以下语句显示字段i 的下一个适用auto_increment 值。

mysql> select auto_increment
    ->   from information_schema.tables
    ->  where table_name='so_q27476005';
+----------------+
| auto_increment |
+----------------+
|              2 |
+----------------+
1 row in set (0.00 sec)

您可以交叉检查last_insert_id 是否仍然相同。

mysql> select last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
|                0 |
+------------------+
1 row in set (0.00 sec)

让我们在字段i 中插入一个null 值。

mysql> insert into so_q27476005 values( null );
Query OK, 1 row affected (0.03 sec)

虽然将null 传递给primary key 字段,但它成功了, 因为该字段归属于auto_increment。 让我们看看生成并插入了哪个值。

mysql> select last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
|                2 |
+------------------+
1 row in set (0.00 sec)

字段i 的下一个适用auto_increment 值是:

mysql> select auto_increment
    ->   from information_schema.tables
    ->  where table_name='so_q27476005';
+----------------+
| auto_increment |
+----------------+
|              3 |
+----------------+
1 row in set (0.00 sec)

mysql> select * from so_q27476005;
+---+
| i |
+---+
| 1 |
| 2 |
+---+
2 rows in set (0.00 sec)

现在,让我们观察last_insert_id 在为字段提供显式输入时的结果。

mysql> insert into so_q27476005 values( 3 );
Query OK, 1 row affected (0.07 sec)

mysql> select * from so_q27476005;
+---+
| i |
+---+
| 1 |
| 2 |
| 3 |
+---+
3 rows in set (0.00 sec)


mysql> select last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
|                2 |
+------------------+
1 row in set (0.00 sec)

您可以看到 last_insert_id 由于显式输入而没有捕获该值。 但是,信息架构确实注册了下一个适用值。

mysql> select auto_increment
    ->   from information_schema.tables
    ->  where table_name='so_q27476005';
+----------------+
| auto_increment |
+----------------+
|              4 |
+----------------+
1 row in set (0.08 sec)

现在,让我们观察last_insert_id 在字段输入为自动/隐式时的结果。

mysql> insert into so_q27476005 values( null );
Query OK, 1 row affected (0.10 sec)

mysql> select last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
|                4 |
+------------------+
1 row in set (0.00 sec)

希望这些细节对您有所帮助。

【讨论】:

感谢您如此详细的解释。我在将id_2 列设置为自动增量时遇到问题。我得到Error Code: 1075. Incorrect table definition; there can be only one auto column and it must be defined as a key。我的情况是我知道id_1 的值(由应用程序定义),但我不知道id_2 的值。所以对于每个唯一的id_1,我需要有id_2 自动增量。所以我想要自动递增的列是id_2 错误信息非常清楚。每个表只能有一个 auto_increment 字段。我建议您将您的问题重新构建为新帖子。解释你真正想要什么。 我已在问题中添加了对使用此表的系统的简要说明。 @Xeos:根据您的描述,表具有父子关系。更清楚地说,您可以在要使用的所有相关表上添加show create table <table_name> 吗?您最好使用sqlfiddle 定义表及其数据,然后运行一些查询。

以上是关于在 MySQL 的 TRIGGER 中更改 LAST_INSERT_ID()的主要内容,如果未能解决你的问题,请参考以下文章

如何更改使用 Trigger.io 构建的应用程序的启动画面?

使用 val() 和 trigger('change') 更改输入值

RefluxJS 商店可以在调用 trigger() 时指示哪些属性已更改?

php 在Shopkeeper主题中为XL WooCommerce Sales Trigger插件更改WooCommerce单一产品位置

mysql trigger 触发器

MySQL 在线更改 Schema 工具