一、问题背景
最近在本地调试一个老项目时,发现一个非常奇怪的现象:
- 同一套 Oracle 数据库
- 线上环境访问正常、执行很快
- 本地开发环境访问却非常慢
- 换到另一个 Spring Boot 项目 访问同一库,速度又是正常的
也就是说:
问题只出现在这一个老项目的本地环境中,同库、同网络条件,别的项目没问题。
这类问题最容易让人误判到 “网络”、“数据库本身”、“SQL 写得烂”等方向。
但既然同样的 SQL、同一个库、在其他项目里是快的,那基本可以锁定是 “项目自身的配置或技术栈造成的性能问题”。
下面按照时间线,记录一下我是如何一步步定位并解决这个问题的。
二、初步假设与排查方向
根据现象,我一开始做了几个假设:
-
SQL 自身有问题?
- 用慢的项目和快的项目,分别执行同一条 SQL。
- 在数据库里直接执行(例如 SQL Developer / PL/SQL Developer 等)。
- 结论:SQL 在数据库侧执行很快,换到另一个项目里执行也很快 → 可以排除 SQL 本身问题。
-
数据库连接配置错误?
- 驱动版本是否一致?
- URL / 用户名 / 密码 是否一致?
- 实际检查后发现,两个项目连接的是 同一套数据库,同一用户,驱动版本也兼容 → 基本可排除。
-
网络问题?
- 开发机器到数据库服务器的网络延迟、丢包情况等。
- 如果是网络问题,那所有项目都会慢,而不是只慢一个项目。
- 实测其他项目访问同库正常 → 网络问题基本可以排除。
排除以上几类问题后,矛头就指向“项目内部对数据源、连接池的配置方式”。
三、对比两个项目的配置
为了找差异,我特地对比了:
- 这个“访问很慢”的传统 Spring MVC 项目(非 Spring Boot)
- 一个访问同一数据库、性能正常的 Spring Boot 项目
1. 慢项目的数据源配置(XML)
在这个老项目中,数据源是通过 Spring XML 配置的,大致类似:
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName">
<value>${jdbc.driverClassName}</value>
</property>
<property name="url">
<value>${jdbc.url}</value>
</property>
<property name="username">
<value>${jdbc.username}</value>
</property>
<property name="password">
<value>${jdbc.password}</value>
</property>
</bean>
注意这里的实现类是:
class="org.springframework.jdbc.datasource.DriverManagerDataSource"2. 同库正常的 Spring Boot 项目的数据源配置(YAML)
Spring Boot 项目则是类似下面这样的配置(以 Druid 为例):
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: oracle.jdbc.driver.OracleDriver
druid:
url: jdbc:oracle:thin:@xxx.xxx.xxx.xxx:1521:orcl
username: xxx
password: xxx
initialSize: 5
minIdle: 10
maxActive: 20
maxWait: 60000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
可以看到,Spring Boot 项目使用的是 Druid 连接池,而老项目使用的是 DriverManagerDataSource。
四、关键差异:DriverManagerDataSource vs 连接池
查了一下 Spring 文档,确认了一个关键点:
1. DriverManagerDataSource 的特性
- 不是连接池实现
- 每次从
dataSource.getConnection()获取连接时,都会创建一个新的物理连接 - 用完连接后关闭,下次再用又重新建连接
- 优点:简单直观
- 缺点:频繁建立/断开数据库连接,极大增加延迟和资源开销
在网络延迟较小、访问频率不高的场景下,这个缺点不太明显。
但在开发环境(比如到远端数据库,VPN/公网等)下,每次建连的网络、认证开销都被放大,就会表现为每次 SQL 执行都很慢。
2. Druid / HikariCP 等连接池的特性
以 Druid 为例:
- 启动时根据配置创建一定数量的数据库连接(初始连接池)
- 连接在池中重复复用,不必每次都新建
- 支持空闲连接检测、失效重连等机制
- 对网络延迟和数据库建立连接的开销非常友好
所以,这就是为什么:
- 同样的 Oracle 数据库
- Spring Boot + Druid 的项目访问很快
- 使用
DriverManagerDataSource的老项目访问却很慢
根本原因:老项目本地环境没有使用连接池,每次请求都重新建 Oracle 连接。
五、解决思路:给老项目也上连接池
既然问题定位在数据源实现上,那么解决方案就很清晰了:
把
DriverManagerDataSource换成一个真正的连接池实现,比如 Druid。
这里以 Druid 为例进行改造(不涉及任何具体项目名、IP 或库名)。
1. 添加 Druid 依赖(Maven)
在 pom.xml 中加入:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.20</version>
</dependency>
版本号可以按自己项目实际情况选择,但建议选一个相对新的稳定版本。
2. 修改 Spring XML 中的数据源配置
将原来的:
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName">
<value>${jdbc.driverClassName}</value>
</property>
<property name="url">
<value>${jdbc.url}</value>
</property>
<property name="username">
<value>${jdbc.username}</value>
</property>
<property name="password">
<value>${jdbc.password}</value>
</property>
</bean>
替换为 Druid 连接池配置,例如:
<!-- Druid 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 初始连接数 -->
<property name="initialSize" value="${jdbc.initialSize:5}"/>
<!-- 最小连接池数量 -->
<property name="minIdle" value="${jdbc.minIdle:5}"/>
<!-- 最大连接池数量 -->
<property name="maxActive" value="${jdbc.maxActive:20}"/>
<!-- 获取连接时的最大等待时间(毫秒) -->
<property name="maxWait" value="${jdbc.maxWait:60000}"/>
<!-- 下面几个可以写死在 XML,也可以抽到配置文件 -->
<!-- 多久检测一次需要关闭的空闲连接(毫秒) -->
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<!-- 连接在池中最小生存时间(毫秒) -->
<property name="minEvictableIdleTimeMillis" value="300000"/>
<!-- 验证连接是否有效 -->
<property name="validationQuery" value="SELECT 1 FROM DUAL"/>
<property name="testWhileIdle" value="true"/>
<property name="testOnBorrow" value="false"/>
<property name="testOnReturn" value="false"/>
</bean>
这里有几点说明:
init-method="init"和destroy-method="close"是 Druid 推荐的生命周期方法。- 连接池大小根据自己机器和数据库承载能力调整。
validationQuery用的是 Oracle 常用的检测语句:SELECT 1 FROM DUAL。
3. 在 properties 中添加连接池相关配置(可选)
如果希望连接池参数可配,可以在 jdbc.properties(或类似配置文件)中添加:
jdbc.driverClassName=oracle.jdbc.driver.OracleDriver jdbc.url=jdbc:oracle:thin:@xxx.xxx.xxx.xxx:1521:orcl jdbc.username=xxx jdbc.password=xxx # Druid 连接池配置 jdbc.initialSize=5 jdbc.minIdle=5 jdbc.maxActive=20 jdbc.maxWait=60000
再结合上面的 XML 就能动态读取这些值。
六、修改后的效果验证
完成以上修改后,我做了几步验证:
- 重新加载 Maven 依赖,重启应用
- 执行之前那些明显感觉“卡”的操作,比如某些列表查询、分页查询等
- 同时对比:
- 修改前:一次查询需要等好几秒甚至十几秒
- 修改后:几乎是秒回,体验和 Spring Boot 项目完全一致
再打开数据库侧的监控也能看到:
- 连接数在一个合理区间内波动,而不是频繁创建、关闭连接
- QPS、响应时间等指标回归正常
到此可以确认:
问题确实是由本地环境使用 DriverManagerDataSource 而非连接池造成的。
七、经验与总结
最后总结一下这次排查的几个关键点,供以后遇到类似问题时参考:
-
遇到“同库、同 SQL,某个项目慢,另一个项目快”的情况,优先怀疑:
- 数据源实现
- 连接池配置
- 事务、超时设置等
-
DriverManagerDataSource只适合:- 测试、demo、小工具
- 不适合作为生产或日常开发环境的数据源实现(特别是远程数据库)
-
对于老项目(非 Spring Boot):
- 不要忘了手动配置连接池
- 常见选择有:Druid、HikariCP、C3P0 等(现在推荐 Druid / HikariCP)
-
排查步骤可以遵循:
- 先排除 SQL 本身、数据库本身的问题
- 再对比不同项目的配置:驱动版本、URL、数据源类型、连接池参数
- 注意观察数据源类名:
DriverManagerDataSource基本就意味着“没有连接池”
-
不要被“正式环境没问题”误导
- 正式环境通常网络好、机器好、数据库更近,问题可能被掩盖
- 开发环境往往更容易暴露这类连接建立开销问题
通过这次排查,也再次印证了一个老生常谈的原则:
性能问题,配置同样重要;
不仅要看 SQL 和代码,更要看运行环境和中间层(连接池、线程池等)的配置。
如果你以后也遇到:
“本地访问 Oracle(或其他数据库)巨慢,但同库的其他项目却很快”的情况,
不妨第一时间检查一下:
- 你的
DataSource是不是DriverManagerDataSource? - 项目里是不是压根没用连接池?
往往,这就是问题的根源。
文章评论