bigywater.tech
Spring · 61

R2DBC:响应式访问数据库的事务与连接

R2DBC 里 save() 为什么没写进去?没有事务时两条语句是同一个连接吗?事务里写 subscribe() 会怎样?连接池默认多大、满了会怎样?这篇在 Spring Boot 4.0.8 和本机 PostgreSQL 16 上逐个验证。

0 / 2
保存两行再抛错:@Transactional 留下 0 行,没有事务留下 2 行
1 行
@Transactional 里 subscribe() 一个不接链的 save 再抛错,库里仍留下的行数
10
默认 R2DBC 连接池在服务端同时存在的连接数上限

惰性

实测 · 本机执行输出
  repo.save(...) 调用后不订阅:
      库里 'lazy-no-sub' 的行数 = 0
  repo.save(...).block():
      返回的实体 id = 1,库里 'lazy-sub' 的行数 = 1
  repo.findAll().count().block() = 1
  db.sql("select name from users order by id").map(...).all().collectList().block() = [lazy-sub]

调用 repository 方法不发 SQL,订阅才发。

连接

实测 · 本机执行输出
  无事务:第一条语句 pid=19925,第二条语句 pid=19925
  无事务(再来一次):第一条语句 pid=19926,第二条语句 pid=19927
  @Transactional:第一条语句 pid=19927,第二条语句 pid=19927
  @Transactional(再来一次):第一条语句 pid=19927,第二条语句 pid=19927

事务外每条语句临时取连接,可能不是同一个;事务内绑定一个连接。

回滚

实测 · 本机执行输出
  没有事务:保存两行再抛错 → 抛 IllegalStateException: boom
      库里 name 以 'notx-' 开头的行数 = 2
  @Transactional:保存两行再抛错 → 抛 IllegalStateException: boom
      库里 name 以 'tx-' 开头的行数 = 0
  TransactionalOperator:保存两行再抛错 → 抛 IllegalStateException: boom
      库里 name 以 'op-' 开头的行数 = 0
  @Transactional 里 subscribe() 一个没接到链上的 save,再抛错 → 抛 IllegalStateException: boom
      库里 name 以 'forgot' 开头的行数 = 1
  @Transactional 里把 save().block() 放到 boundedElastic 上,再抛错 → 抛 IllegalStateException: boom
      库里 name 以 'block-inside' 开头的行数 = 1

@Transactional 与 TransactionalOperator 能回滚;链外的 subscribe() / block() 不在事务里。

连接池

实测 · 本机执行输出
  默认的 R2DBC 连接池:
  30 个并发的 pg_sleep(300 ms):成功 30,失败 {},总用时 1100 ms
      服务端同时存在的连接数最大值 = 10
  spring.r2dbc.pool.max-size=3:
  30 个并发的 pg_sleep(300 ms):成功 30,失败 {},总用时 3182 ms
      服务端同时存在的连接数最大值 = 3
  spring.r2dbc.pool.max-size=30:
  30 个并发的 pg_sleep(300 ms):成功 30,失败 {},总用时 523 ms
      服务端同时存在的连接数最大值 = 30
  max-size=2、max-acquire-time=200ms,10 个并发的 pg_sleep(600 ms):
  10 个并发的 pg_sleep(600 ms):成功 2,失败 {DataAccessResourceFailureException ← R2dbcTimeoutException ← TimeoutException=8},总用时 755 ms
      服务端同时存在的连接数最大值 = 2

默认 10 个连接,满了排队,可设 max-acquire-time。

分步演示

事务与连接
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动。用 Spring Boot 4.0.8 的 SpringApplication(spring-boot-starter-data-r2dbc + r2dbc-postgresql 1.1.2,非 Web 模式);数据库是本机自己 initdb 出来的 PostgreSQL 16,端口 9601;服务端连接数每 50 ms 轮询 pg_stat_activity 取最大值,所以是近似值;pid 那一节是否同一个连接在不同次调用里不一定相同
    • 没有验证:MySQL / MariaDB / H2 的 R2DBC 驱动;R2dbcEntityTemplate 与派生查询方法;事务隔离级别与传播行为(@Transactional(propagation=...));连接池的空闲回收(max-idle-time)与预热;r2dbc-pool 的指标;批处理 Batch;读写分离;事务里调用外部 HTTP 服务的超时处理
    京ICP备2026067502号-1京公网安备11010502063487号