Statement.RETURN_GENERATED_KEYS 是不是会生成任何额外的往返行程来获取新创建的标识符?

Posted

技术标签:

【中文标题】Statement.RETURN_GENERATED_KEYS 是不是会生成任何额外的往返行程来获取新创建的标识符?【英文标题】:Does Statement.RETURN_GENERATED_KEYS generate any extra round trip to fetch the newly created identifier?Statement.RETURN_GENERATED_KEYS 是否会生成任何额外的往返行程来获取新创建的标识符? 【发布时间】:2016-10-26 15:38:54 【问题描述】:

JDBC 允许我们使用以下语法获取由数据库自动生成的主键的值(例如 IDENTITYAUTO_INCREMENT):

PreparedStatement ps= connection.prepareStatement(
    "INSERT INTO post (title) VALUES (?)",
     Statement.RETURN_GENERATED_KEYS
);

while (resultSet.next()) 
    LOGGER.info("Generated identifier: ", resultSet.getLong(1));

我感兴趣的是 Oracle、SQL Server、postgresQL 或 mysql 驱动程序是否使用单独的往返来获取标识符,或者是否存在执行插入并自动获取 ResultSet 的单个往返。

【问题讨论】:

【参考方案1】:

这取决于数据库和驱动程序。

虽然你没有要求,但我会为 Firebird 回答;)。在 Firebird/Jaybird 中,检索本身不需要额外的往返,但使用 Statement.RETURN_GENERATED_KEYS 或整数数组版本将需要三个额外的往返(准备、执行、获取)来确定要请求的列(我仍然需要构建一个表单为它缓存)。使用带有String 数组的版本不需要额外的往返(我希望有RETURNING * 就像在PostgreSQL 中一样......)。

【讨论】:

您还可以从声音的消息/查询管道中受益。 Prepare、Execute、Fetch(或者,在 PostgreSQL 中,Parse/Bind/Execute/Sync)只是 PostgreSQL 协议中的一次往返。 @CraigRinger 哦,我希望就这么简单;)【参考方案2】:

在带有 PgJDBC 的 PostgreSQL 中,没有额外的往返来获取生成的密钥。

它发送一个 Parse/Describe/Bind/Execute 消息系列,然后是一个 Sync,然后读取结果,包括返回的结果集。由于协议管道请求,因此只需要一次客户端/服务器往返。

但是,如果请求生成的密钥,有时可以将原本可以流式传输到服务器的批次分解成更小的块或逐个运行。为避免这种情况,请使用String[] 数组形式,在其中命名要返回的列,并仅命名固定宽度数据类型的列,例如integer。这仅对批次和it's a due to a design problem in PgJDBC 很重要。

(我发布的 a patch to add batch pipelining support in libpq 没有这个限制,它会为任意大小的批次执行一次客户端/服务器往返,并获得任意大小的结果,包括返回键。)

【讨论】:

感谢您提供对 PgJDBC 的深入了解。 令人惊讶的是,即使在生成的密钥输出中的NUMERIC (10) 列也会禁用批处理,因为后端报告它“最多 65535 字节”。有一个单独的线程来读取结果并启用正确的流水线,这又多了一点。 是的,目前测试很简单,不检查 typmod 信息。欢迎使用补丁。但实际上 PgJDBC 试图管理服务器发送缓冲区的整个方法是错误的,它应该进行本地非阻塞写入或管理 jdbc 发送缓冲区,因此它永远不会填充和阻塞。不过,我没有时间重新设计驱动程序的缓冲区管理。【参考方案3】:

MySQL 自动在协议的 OK 数据包中接收生成的密钥,以响应执行语句。请求生成的密钥时没有通信开销。

【讨论】:

【参考方案4】:

在我看来,即使是这样一件微不足道的事情,在所有数据库系统中工作的单一方法也会失败。 唯一实用的解决方案是(类似于 Hibernate)为每个目标 RDBMS(和 将其称为您的所有解决方案的方言:)

这里是Oracle 的信息

我使用 sequence 来生成密钥,对于 IDENTITY 列观察到相同的行为。

create  table auto_pk
(id number,
pad varchar2(100));

这可行并且只使用一次往返

def stmt = con.prepareStatement("insert into auto_pk  values(auto_pk_seq.nextval, 'XXX')",
Statement.RETURN_GENERATED_KEYS) 
 
def rowCount = stmt.executeUpdate()

def generatedKeys = stmt.getGeneratedKeys()

if (null != generatedKeys && generatedKeys.next()) 
def   id = generatedKeys.getString(1);

但不幸的是,您会得到 ROWID - 不是生成的密钥

它是如何在内部实现的?如果您激活 10046 跟踪,您可以看到它(顺便说一句,这也是查看的最佳方式 执行了多少次往返)

PARSING IN CURSOR 
insert into auto_pk values(auto_pk_seq.nextval, 'XXX') 
RETURNING ROWID INTO :1 
END OF STMT

所以您看到 JDBC 标准 3.0 已实现,但您没有得到请求的结果。在封面下使用 RETURNING 子句。

因此,在 Oracle 中获取生成密钥的正确方法是:

def stmt = con.prepareStatement("insert into auto_pk values(auto_pk_seq.nextval, 'XXX') returning id into ?") 
 
stmt.registerReturnParameter(1, Types.INTEGER);          
 
def rowCount = stmt.executeUpdate()
 
def generatedKeys = stmt.getReturnResultSet()
 
if (null != generatedKeys && generatedKeys.next()) 
     def   id = generatedKeys.getLong(1);
     

注意: Oracle Release 12.1.0.2.0

要激活10046 trace 使用

con.createStatement().execute "alter session set events '10046 trace name context forever, level 12'"
con.createStatement().execute "ALTER SESSION SET tracefile_identifier = my_identifier"

【讨论】:

【参考方案5】:

依赖框架或库来完成在纯 SQL 中完全可能的事情是糟糕的设计恕我直言,尤其是在针对已定义的 DBMS 工作时。 (Statement.RETURN_GENERATED_KEYS 相对无害,尽管它显然确实给您提出了一个问题,但是如果框架构建在单独的实体上并在代码中执行各种连接和过滤器或具有自定义构建的事务隔离逻辑,事情就会变得低效和混乱很快。)

为什么不简单:

PreparedStatement ps= connection.prepareStatement(
    "INSERT INTO post (title) VALUES (?) RETURNING id");

单程,确定结果。

【讨论】:

因为RETURNING是一个PostgreSQL扩展,而这个人显然关心多个DBMS。

以上是关于Statement.RETURN_GENERATED_KEYS 是不是会生成任何额外的往返行程来获取新创建的标识符?的主要内容,如果未能解决你的问题,请参考以下文章