bigywater.tech
Java · 318

JDBC Generated Keys:安全取得数据库生成主键

插入后怎么拿到数据库生成的主键?PostgreSQL 与 MySQL 的 getGeneratedKeys 差别在哪?批量插入时每行的键怎么取?为什么不能用 select max(id)?这篇逐项实测。

整行 vs 一列
PostgreSQL 的 RETURN_GENERATED_KEYS 返回 [id, name, created] 整行;MySQL 只有 GENERATED_KEY 一列
-2
MySQL 加 rewriteBatchedStatements 后 executeBatch 的每个更新计数
2919 / 3200
PostgreSQL 并发下 select max(id) 与自己插入的键不一致的次数

PostgreSQL 取主键

实测 · 本机执行输出
1 RETURN_GENERATED_KEYS:列 [id, name, created],行 [[1, a, <时间>]]
2 只要 id 列(new String[]{"id"}):列 [id],行 [[2]]
3 按列序号 prepare:PSQLException(0A000) Returning autogenerated keys is not supported.
4 没有请求时 getGeneratedKeys():列 [],行 []
5 显式给了 id=100(没有用到自增):列 [id, name, created],行 [[100, explicit, <时间>]]
6 一条 INSERT 带三行 VALUES:列 [id, name, created],行 [[4, m1, <时间>], [5, m2, <时间>], [6, m3, <时间>]]
7 Statement.executeUpdate(sql, RETURN_GENERATED_KEYS):列 [id, name, created],行 [[7, st, <时间>]]
8 executeBatch 4 行:更新计数 [1, 1, 1, 1];列 [id, name, created],行 [[8, b0, <时间>], [9, b1, <时间>], [10, b2, <时间>], [11, b3, <时间>]]

PostgreSQL 改写批量插入

实测 · 本机执行输出
1 RETURN_GENERATED_KEYS:列 [id, name, created],行 [[1, a, <时间>]]
2 只要 id 列(new String[]{"id"}):列 [id],行 [[2]]
3 按列序号 prepare:PSQLException(0A000) Returning autogenerated keys is not supported.
4 没有请求时 getGeneratedKeys():列 [],行 []
5 显式给了 id=100(没有用到自增):列 [id, name, created],行 [[100, explicit, <时间>]]
6 一条 INSERT 带三行 VALUES:列 [id, name, created],行 [[4, m1, <时间>], [5, m2, <时间>], [6, m3, <时间>]]
7 Statement.executeUpdate(sql, RETURN_GENERATED_KEYS):列 [id, name, created],行 [[7, st, <时间>]]
8 executeBatch 4 行:更新计数 [1, 1, 1, 1];列 [id, name, created],行 [[8, b0, <时间>], [9, b1, <时间>], [10, b2, <时间>], [11, b3, <时间>]]

MySQL 取主键

实测 · 本机执行输出
1 RETURN_GENERATED_KEYS:列 [GENERATED_KEY],行 [[1]]
2 只要 id 列(new String[]{"id"}):列 [GENERATED_KEY],行 [[2]]
3 按列序号 new int[]{1}:列 [GENERATED_KEY],行 [[3]]
4 没有请求时 getGeneratedKeys():SQLException(S1009) Generated keys not requested. You need to specify Statement.RETURN_GENERATED_KEYS to Statement.executeUpdate(), Statement.executeLargeUpdate() or Connection.prepareStatement().
5 显式给了 id=100(没有用到自增):列 [GENERATED_KEY],行 [[100]]
6 一条 INSERT 带三行 VALUES:列 [GENERATED_KEY],行 [[101], [102], [103]]
7 Statement.executeUpdate(sql, RETURN_GENERATED_KEYS):列 [GENERATED_KEY],行 [[104]]
8 executeBatch 4 行:更新计数 [1, 1, 1, 1];列 [GENERATED_KEY],行 [[105], [106], [107], [108]]

MySQL 改写批量语句

实测 · 本机执行输出
1 RETURN_GENERATED_KEYS:列 [GENERATED_KEY],行 [[1]]
2 只要 id 列(new String[]{"id"}):列 [GENERATED_KEY],行 [[2]]
3 按列序号 new int[]{1}:列 [GENERATED_KEY],行 [[3]]
4 没有请求时 getGeneratedKeys():SQLException(S1009) Generated keys not requested. You need to specify Statement.RETURN_GENERATED_KEYS to Statement.executeUpdate(), Statement.executeLargeUpdate() or Connection.prepareStatement().
5 显式给了 id=100(没有用到自增):列 [GENERATED_KEY],行 [[100]]
6 一条 INSERT 带三行 VALUES:列 [GENERATED_KEY],行 [[101], [102], [103]]
7 Statement.executeUpdate(sql, RETURN_GENERATED_KEYS):列 [GENERATED_KEY],行 [[104]]
8 executeBatch 4 行:更新计数 [-2, -2, -2, -2];列 [GENERATED_KEY],行 [[105], [106], [107], [108]]

并发下的 max(id)(PostgreSQL)

实测 · 本机执行输出
9 8 个线程各插入 400 行,每次插入后同时取 getGeneratedKeys() 与 select max(id):共 3200 次,两者不一致 2919 次(max(id) 看到了别的连接插入的行)

并发下的 max(id)(MySQL)

实测 · 本机执行输出
9 8 个线程各插入 400 行,每次插入后同时取 getGeneratedKeys() 与 select max(id):共 3200 次,两者不一致 2649 次(max(id) 看到了别的连接插入的行)

分步演示

JDBC Generated Keys
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12;自己起的 PostgreSQL 16.15(端口 9501)与 MySQL 26.7.0(端口 9601),用户自己的数据库没有被使用;pgjdbc 42.7.4、mysql-connector-j 9.1.0
    • 没有验证:innodb_autoinc_lock_mode 对多行 INSERT 键连续性的影响、INSERT ... ON CONFLICT / ON DUPLICATE KEY UPDATE 的返回、IDENTITY / UUID 主键、多列生成键、使用 SEQUENCE 的表、事务回滚后自增值的空洞、LAST_INSERT_ID() 的会话语义
    京ICP备2026067502号-1京公网安备11010502063487号