连接池、事务、数据库迁移与数据访问技术决策

即使 SQL 写对了,连接耗尽、事务过长或迁移顺序错误,仍会让数据访问出问题。本章把这些容易被框架教程略过的部分串起来:先确定连接和事务怎样使用,再安排结构变更与运行观察。

稳定的数据访问层依赖连接预算、事务语义、模式迁移和可观测性,而不仅是选择 MyBatis 或 JPA。

1. 本文覆盖范围

  • HikariCP 连接池预算
  • Spring 事务传播与隔离
  • Flyway/Liquibase 迁移
  • MyBatis、JPA、jOOQ/JDBC 决策

2. 核心知识详解

1. 连接池是受限资源

连接池复用昂贵的数据库连接,并限制应用对数据库的并发压力。池大小由数据库容量、实例数、查询延迟与并发模型共同决定。

  • 总连接预算 = 每实例最大连接数 × 最大实例数,再为运维和迁移预留。
  • 监控 active、idle、pending、acquire timeout 和数据库端会话。
  • 连接泄漏检测用于诊断,不作为日常关闭连接的替代。

这里容易混淆的是: 池越大吞吐并非越高;超过数据库并行能力会增加排队、上下文切换和尾延迟。

2. Spring 声明式事务

@Transactional 通常由代理拦截公开方法,事务资源绑定到当前线程;传播行为定义嵌套调用如何加入、挂起或新建事务。

  • 默认回滚规则、readOnly、timeout、isolation 均显式理解。
  • 同类自调用绕过代理;异步/新线程不会自动继承事务。
  • 数据库写入与消息发送使用 outbox 等方案连接,而非幻想单机注解覆盖两个系统。

这里容易混淆的是: REQUIRES_NEW 会占用额外连接并可能扩大连接池需求,不是“确保提交”的通用按钮。

3. 版本化模式迁移

迁移脚本与应用版本一起评审、测试和发布。生产使用 expand/migrate/contract 等兼容步骤,避免新旧实例并存时破坏读写。

  • 历史已执行迁移保持不可变,修复用新版本脚本。
  • 大表 DDL 评估锁、日志、复制延迟和回滚路径。
  • 启动迁移与应用副本扩容解耦,防止多实例竞争。

这里容易混淆的是: 回滚应用不一定能回滚数据结构;破坏性迁移需要前向修复与兼容窗口。

4. 技术选择矩阵

MyBatis 适合 SQL 可控和复杂查询;JPA 适合聚合持久化和领域模型;JDBC/jOOQ 适合显式 SQL 与类型安全组合。一个系统可按模块选择。

  • 按查询复杂度、对象图、团队技能、性能可预测性选择。
  • 统一事务、异常翻译、连接池和观测,不重复造基础设施。
  • 用基准和执行计划判断性能,不按框架名推测。

这里容易混淆的是: 数据访问框架不改变数据库的锁、隔离、索引与事务规律。

3. 工程链路

flowchart LR A["请求"] --> B["事务代理"] B --> C["连接池借连接"] C --> D["SQL Mapper / ORM"] D --> E["数据库"] E --> F["commit / rollback"] F --> G["归还连接 + 指标"]

4. 教学示例(结合本章运行前提)

这里用转账说明本地事务:扣款与入账需要共同成功或回滚。假设仓库已经注入,并使用同一受管理的数据源。若两步访问不同服务或数据库,单靠这个注解并不能获得相同保证。

java
@Service class TransferService { @Transactional public void transfer(long from, long to, BigDecimal amount) { accounts.debit(from, amount); accounts.credit(to, amount); } } // 事务方法应从代理外部调用;同类自调用不会经过代理拦截。

5. 实践与验证

  1. 计算 3 个应用实例的连接预算并模拟池耗尽。
  2. 实现可跨两个版本滚动发布的列重命名迁移。
  3. 为同一查询分别写 MyBatis/JPA 投影并记录 SQL 与耗时。

6. 掌握检查

  • 能解释代理和线程边界。
  • 能制定连接预算。
  • 能设计兼容迁移。
  • 能用证据选择数据访问技术。

参考资料