Skip to content

批量操作与性能优化 ​

在现代 Web 系统中,数据库往往是性能瓶颈,而批量操作(Batch) 是最直接、最有效的优化手段之一。

本文将在已经完成:

  • tio-boot + jOOQ 整合
  • AOP 注入 DSLContext
  • 手动 / AOP 事务管理

的基础上,深入讲解:

  • 如何使用 jOOQ 进行高性能批量操作
  • 如何避免常见性能陷阱
  • 如何提升 PostgreSQL 写入吞吐
  • 如何设计更合理的数据访问策略

目标是让的系统从“能跑”升级到“高并发可扩展”。


一、为什么必须掌握批量操作 ​

很多系统慢,并不是 SQL 写得差,而是犯了一个经典错误:

❌ 循环 insert ​

java
for (User u : users) {
  dsl.insertInto(USER)
     .set(USER.NAME, u.getName())
     .execute();
}

看起来只是一个循环,但实际发生的是:

  • 每次执行都要:

    • 获取连接
    • 发送 SQL
    • 等待数据库响应
    • 提交事务

如果插入 1000 条数据,就会产生:

1000 次网络往返 + 1000 次 SQL 执行

性能会呈指数级下降。


二、jOOQ 的三种批量模式(非常重要) ​

理解这三种模式,是性能优化的关键。

⭐ 模式一:Batch(推荐) ​

jOOQ 会使用 JDBC Batch:

java
dsl.batch(
  dsl.insertInto(USER).set(USER.NAME, "A"),
  dsl.insertInto(USER).set(USER.NAME, "B")
).execute();

特点:

  • 单次发送到数据库
  • 极大减少网络 IO
  • 性能提升通常 5~50 倍

这是最常用的方式。


⭐⭐ 模式二:Batch Bind(更高性能) ​

当 SQL 模板相同时,推荐使用:

java
dsl.batch(
  dsl.insertInto(USER, USER.NAME)
     .values((String) null)
).bind("A")
 .bind("B")
 .bind("C")
 .execute();

优势:

  • SQL 只编译一次
  • 数据库执行计划可复用
  • CPU 更低

在超大批量写入时优势明显。


⭐⭐⭐ 模式三:Multi-row Insert(PostgreSQL 极强) ​

PostgreSQL 对这种语法优化极好:

sql
INSERT INTO user(name)
VALUES ('A'),('B'),('C');

jOOQ 写法:

java
dsl.insertInto(USER, USER.NAME)
   .values("A")
   .values("B")
   .values("C")
   .execute();

在 PG 中:

通常是最快的写入方式

甚至可能比 JDBC Batch 更快。


三、在事务中执行批量(强烈建议) ​

无论哪种批量:

✅ 必须放进事务中!

否则:

  • 每条 SQL 自动提交
  • WAL 写入暴涨
  • IO 飙升

推荐写法: ​

java
txManager.tx(() -> {
  userDao.batchInsert(users);
});

不要在 DAO 内开启事务。

事务边界始终放在 Service 层。


四、实战:高性能 Batch Insert DAO ​

示例:SystemAdminDao ​

java
  public int[] batchInsert(List<SystemAdmin> admins) {

    DSLContext ctx = TransactionContext.get();

    InsertValuesStep2<Record, Object, Object> step = ctx.insertInto(DSL.table("system_admin"),
        //
        DSL.field("login_name"), DSL.field("password"))
        //
        .values((String) null, null);

    BatchBindStep batch = ctx.batch(step);

    for (SystemAdmin a : admins) {
      batch.bind(a.getLoginName(), a.getPassword());
    }

    return batch.execute();
  }

性能通常可提升:

10 倍以上


五、超大批量写入策略(百万级) ​

当数据量非常大时,不要一次提交!

❌ 错误示例 ​

一次插入 1,000,000 条

可能导致:

  • OOM
  • 事务日志暴涨
  • 锁时间过长

✅ 推荐策略:分块提交(Chunking) ​

java
int batchSize = 1000;

for (int i = 0; i < list.size(); i += batchSize) {

  List<SystemAdmin> chunk =
    list.subList(i, Math.min(i + batchSize, list.size()));

  txManager.tx(() -> {
    dao.batchInsert(chunk);
  });
}

经验值:

数据库推荐 chunk
PostgreSQL500~2000
MySQL500~1000

六、批量更新优化 ​

不要循环 update:

❌ ​

java
for (...) {
  update where id=?
}

✅ CASE WHEN 批量更新 ​

PostgreSQL 非常擅长:

sql
UPDATE system_admin
SET password =
CASE id
  WHEN 1 THEN 'a'
  WHEN 2 THEN 'b'
END
WHERE id IN (1,2);

jOOQ 可以构建这种 SQL。

优点:

  • 一次扫描表
  • 一次锁
  • 极低 IO

七、读取性能优化(很多人忽略) ​

写入只是半边天。

读取同样关键。

⭐ 只查需要的字段 ​

不要:

java
select *

而要:

java
dsl.select(USER.ID, USER.NAME)

减少:

  • 网络传输
  • JVM 反序列化
  • GC 压力

⭐ 使用 fetchSize(超大查询) ​

java
dsl.fetchSize(1000);

效果:

  • 避免一次加载百万行到内存
  • 使用游标流式读取

非常适合:

  • 导出数据
  • 数据迁移

八、连接池优化建议(很多人忽略) ​

默认配置通常偏保守。

建议调整 Druid:

java
dataSource.setInitialSize(5);
dataSource.setMaxActive(30);
dataSource.setMinIdle(5);
dataSource.setMaxWait(60000);

核心原则:

连接数 ≈ CPU核心数 × 2~4

不要盲目开到 200。

数据库会先崩。


九、jOOQ 性能隐藏技巧(高手常用) ​

⭐ 使用 PreparedStatement(默认开启) ​

jOOQ 已经帮做好了。

不要关闭。


⭐ 避免频繁创建 DSLContext ​

正确:

  • DSLContext 单例
  • 事务内用 Connection 创建子 DSL

错误:

每次 new DSL.using()

⭐ 善用 PostgreSQL UPSERT ​

sql
INSERT ...
ON CONFLICT DO UPDATE

避免:

  • 先 select
  • 再 insert/update

直接减少一次 IO。


十、什么时候不该用 Batch? ​

少量数据:

  • < 50 条

收益不明显。

但:

超过 200 条就建议批量。


十一、性能优化优先级(建议收藏) ​

如果只能做 3 件事:

第一优先级 ​

✅ 批量写入 ✅ 放入事务

立刻提升 5~20 倍。


第二优先级 ​

✅ 分块提交 ✅ 避免 select *

再提升 30~50%。


第三优先级 ​

✅ 合理连接池 ✅ 使用 UPSERT

系统稳定性大幅提升。


十二、总结 ​

掌握 jOOQ 批量操作,本质是在掌控三件事:

  • 网络 IO
  • 事务提交频率
  • SQL 执行次数

当做到:

  • 批量替代循环
  • 事务包裹批量
  • 合理分块

的数据库性能通常会出现“跃迁式”提升。

一句话总结:

不会 Batch 的系统,很难支撑高并发。


MIT Licensed