Firebird存储过程如何返回新生成的ID?
问题翻译
我有一个向表中插入记录的存储过程,同时存在一个在BEFORE INSERT时自动递增序列的触发器(表也包含identity类型字段)。我希望获取新生成的ID并加以使用,比如在网站中高亮这条新记录。请问使用带GEN_ID(<sequence>, 0)的RETURNS是不是唯一方法?如果是,是否存在竞态条件?若其他事务先提交,我获取的ID可能不属于我的记录。另外还有RETURNING子句,据我了解它可用于DML查询,比如execute block或动态语句中的INSERT INTO ... RETURNING ID,是否属实?
回答
关于GEN_ID(<sequence>, 0)的问题
这绝对不是唯一方法,而且确实存在严重的竞态条件风险。
- 触发器里调用
GEN_ID(<sequence>,1)生成ID后,如果你在存储过程里再调用GEN_ID(<sequence>,0)取当前序列值,只要有其他事务同时操作这个序列(哪怕对方事务还没提交),序列值都会被递增。等你拿值的时候,很可能拿到的是其他事务生成的ID,完全和你插入的记录不匹配。这种方法可靠性极低,不建议使用。
关于RETURNING子句的使用
完全属实!RETURNING是获取新插入ID最安全、最直接的方案,能完美规避竞态问题,而且适用场景非常广:
- 直接INSERT语句使用
最基础的用法,执行后直接返回当前插入记录的ID:INSERT INTO your_table (col1, col2) VALUES ('val1', 'val2') RETURNING id; - 存储过程中使用
可以把ID作为输出参数返回,或者直接赋值给变量:
调用存储过程时就能直接拿到对应记录的真实ID,不会出错。CREATE OR ALTER PROCEDURE insert_record(p_col1 VARCHAR(50), p_col2 VARCHAR(50), OUT p_new_id INTEGER) AS BEGIN INSERT INTO your_table (col1, col2) VALUES (:p_col1, :p_col2) RETURNING id INTO :p_new_id; END - execute block或动态SQL中使用
不管是匿名块还是动态拼接的SQL语句,RETURNING都能正常工作,比如:EXECUTE BLOCK RETURNS (new_id INTEGER) AS BEGIN INSERT INTO your_table (col1) VALUES ('test') RETURNING id INTO :new_id; SUSPEND; END
针对IDENTITY字段的补充
如果表用的是IDENTITY自动生成字段(比如id INTEGER GENERATED ALWAYS AS IDENTITY),RETURNING同样适用,不需要额外操作序列,直接返回系统生成的ID即可,可靠性和触发器生成序列的场景一致。
总结
别用GEN_ID(<sequence>,0)的方式,竞态条件是真实存在的,会导致你拿到错误的ID。RETURNING才是获取新插入记录ID的标准、可靠做法,完全避免并发问题,覆盖所有你提到的使用场景。
内容的提问来源于stack exchange,提问作者Anthony Voronkov
相关产品推荐
相关产品推荐

