DM数据库排查
你提到的问题是:
-
达梦数据库中执行一条
SELECT ... OFFSET 0 ROWS FETCH NEXT 100001 ROWS ONLY查询; -
在 Spring 应用中(使用 Druid 数据源)耗时 40-60 秒;
-
但使用数据库工具(如达梦客户端)执行同样 SQL,耗时仅 1-2 秒;
这个差异很大,以下是可能的原因分析和排查建议:
🧠 一、根本原因分析
✅ 1. JDBC/Druid 预取行数(fetchSize)不合理
重点关注!
JDBC 默认一次从数据库中 “拉取”行数较少(例如 10 或 100),当你查 100001 行时,会进行多次网络往返(round trip),非常耗时。
而达梦数据库客户端可能默认设置为更大的 fetchSize(例如 5000 或 10000),一次性取大量数据,速度快很多。
这是你这种情况最常见的性能瓶颈之一。
✅ 2. 慢在 Druid 连接池的数据传输而非 SQL 执行本身
你可以通过设置:
或者通过打印 SQL 执行时间 vs 方法总耗时 来区分:
-
SQL 本身可能只耗时 2 秒;
-
数据通过 JDBC 和 Java 对象序列化耗时 30+ 秒。
✅ 3. 返回的数据量太大,Java 端内存压力高
返回 100001 行,每行 7 个字段(可能含字符串),最终 Java 会构造 10 万个对象,加上 JVM GC、对象创建、框架封装等,性能开销大。
特别是:
-
如果你用了 MyBatis,每行都会转为 Java Bean,成本更高;
-
如果使用了分页插件或者 ResultHandler,也可能被慢处理。
✅ 4. 未使用预编译参数
使用 PreparedStatement 才会避免重复 SQL 编译。
你可以打开 druid SQL 日志(或设置 log4jdbc)看是否有参数替换。若是拼接 SQL 方式,也会导致执行计划不缓存,变慢。
✅ 5. 网络或连接层存在瓶颈
-
Spring 应用和数据库不在同一台机器上;
-
网络带宽低或 RTT(往返时间)高;
-
JDBC/Druid 层对大结果集传输不友好。
🛠 二、解决建议(分步骤)
✔️ 1. 增加 fetchSize
在你的代码中手动设置:
如果你使用的是 MyBatis,可以通过拦截器或者配置中设置:
或者在 Druid 数据源配置中加入:
✔️ 2. 确认是否慢在网络还是 SQL 执行本身
打印 SQL 执行时间:
再测量 while(rs.next()) 的数据读取和转对象的耗时,两者对比可得知:
-
SQL 本身慢 → 优化索引、执行计划
-
数据传输慢 → 优化 fetchSize 或网络
✔️ 3. 限制返回行数,避免大结果集分页
你目前用的是:
建议使用合理分页,比如:
-
FETCH NEXT 5000 ROWS ONLY; -
或者限制查询条件,避免一次拉取 10w 条。
✔️ 4. 确认是否使用了分页插件或 Wrapper
一些分页插件(如 PageHelper)可能会自动包裹 SQL、再执行 count 查询,拖慢性能。务必确认。
✔️ 5. 启用 SQL 日志 / 分析执行计划
开启 druid 监控或数据库日志:
查看慢 SQL 或执行计划确认是否走索引。
🧪 三、验证建议
-
确认 SQL 执行耗时 vs 总耗时
-
尝试设置 fetchSize = 10000 测试性能
-
将返回数量从 10w 降到 5000,观察差异
-
确认 Druid 是否设置了
maxWait,socketTimeout,readTimeout等参数
✅ 总结重点
| 原因 | 优先级 | 解决建议 |
|---|---|---|
| JDBC 默认 fetchSize 太小 | 🟥 高 | 设置 fetchSize=10000 |
| 大量数据对象转换慢 | 🟧 中 | 分页限制条数 / 批处理 |
| 网络瓶颈或数据拉取慢 | 🟧 中 | 查看数据库与应用是否同机 |
| SQL 实际执行很快 | 🟨 次 | 需打印 JDBC 耗时进行验证 |
| SQL 没走索引 | 🟨 次 | 查看执行计划,确认是否需要优化 |
你现在的问题更清晰了:
✅ 使用 MyBatis +
param参数传入查询条件,执行 很慢(40-60s)
✅ 但如果直接在 XML 中 写死查询条件(硬编码),执行很快(1-2s)
这说明了一个关键问题:
🧠 根本原因:MyBatis + 参数绑定未正确使用预编译(PreparedStatement),导致 SQL 执行计划无法复用
或者说:
使用 param 参数的 SQL 被达梦数据库当作不同的 SQL 重新编译、执行计划缓存失败,性能变差。
🔍 举个例子说明
比如:
使用参数绑定(你的慢 SQL):
后台传的是:
达梦数据库每次执行的 SQL 是带参数变量的,但有可能:
-
达梦对这种形式的执行计划缓存做得不好;
-
或 MyBatis 实际上没走
PreparedStatement(非常罕见但可能); -
导致每次都重新生成执行计划 → 非常慢。
使用硬编码常量(你的快 SQL):
数据库识别为 完全一致的 SQL,可以直接复用已有的执行计划、索引优化命中效果更好。
🧪 你可以验证这个现象:
在达梦数据库中执行下面 SQL:
查看是否每次用参数查询时都生成了新的 SQL 哈希(说明计划未命中)。
✅ 最优解决方案
✅ 1. 确保 MyBatis 使用 PreparedStatement 而不是拼接 SQL
检查是否用了 ${} 而不是 #{}
-
正确的写法:(会使用 PreparedStatement)
-
错误写法:(直接拼接,容易导致 SQL 变更)
➡️ 请确认你用的是 # 而不是 $。
✅ 2. 启用 SQL 日志,看看实际执行的 SQL
设置 MyBatis 日志级别:
或者用 log4jdbc,打印真实 SQL,看是否重复拼接、是否命中计划。
✅ 3. 配置 MyBatis 使用 StatementType = PREPARED
你可以在 mapper 或 xml 中显式指定:
确保走的是 PreparedStatement 而不是 STATEMENT 或 CALLABLE。
✅ 4. 在达梦数据库端设置执行计划缓存优化
达梦对执行计划缓存(动态 SQL)可能有参数配置(比如 plan cache、SQL reuse)你可以查下:
或者联系 DBA 开启执行计划缓存。
✅ 5. 尝试在 SQL 上加 Hint 强制走计划
如果以上都没解决,还可以加 SQL Hint:
不过这个属于数据库层级优化,建议 DBA 配合。

浙公网安备 33010602011771号