【问题标题】:Stored proc running 30% slower through Java versus running directly on database通过 Java 运行的存储过程比直接在数据库上运行慢 30%
【发布时间】:2010-12-19 22:11:37
【问题描述】:

我正在使用 Java 1.6、JTDS 1.2.2(也只是尝试 1.2.4 无济于事)和 SQL Server 2005 来创建 CallableStatement 来运行存储过程(不带参数)。我看到运行相同存储过程的 Java 包装器比使用 SQL Server Management Studio 慢 30%。我已经运行了 MS SQL 分析器,两个进程之间的 I/O 差别不大,所以我认为这与查询计划缓存无关。

存储的过程不接受任何参数并且不返回任何数据。它使用服务器端游标来计算填充表所需的值。

我看不出从 Java 调用存储过程应该如何增加 30% 的开销,当然它只是一个到数据库的管道,SQL 被发送下来,然后数据库执行它......数据库可以是给 Java 应用一个不同的查询计划??

我已经在the MSDN forums 和 sourceforge JTDS 论坛上发帖(主题:“在 JTDS 中存储 proc 比直接在 DB 中慢”)我想知道是否有人对为什么会发生这种情况有任何建议?

提前致谢,

-詹姆斯

(请注意,不要害怕,一旦找到解决方案,我会在这里整理我在其他论坛中获得的任何答案)

Java 代码 sn-p:

sLogger.info("Preparing call...");
stmt = mCon.prepareCall("SP_WB200_POPULATE_TABLE_limited_rows");
sLogger.info("Call prepared.  Executing procedure...");
stmt.executeQuery();
sLogger.info("Procedure complete.");

我运行了 sql profiler,发现如下:

Java 应用程序: CPU:466,514 读取:142,478,387 写入:284,078 持续时间:983,796

SSMS: CPU:466,973 读取:142,440,401 写入:280,244 持续时间:769,851

(在分析之前运行 DBCC DROPCLEANBUFFERS,并且都产生正确的行数)

所以我的结论是它们都执行相同的读取和写入,只是它们执行的方式不同,你们怎么看?

事实证明,不同客户端的查询计划明显不同(Java 客户端在插入期间更新索引,而不是在更快的 SQL 客户端中,此外,它执行连接的方式也不同(嵌套循环与收集流,嵌套循环与索引扫描,啊!))。为什么会这样,我还不知道(我会在深入了解后重新发布)

结语

我无法让它正常工作。我尝试同质化 Java 和 Mgmt Studio 客户端之间的连接属性(arithabortansi_nulls 等)。最终,两个不同的客户端具有非常相似的查询/执行计划(但仍然具有不同的实际 plan_id)。我在the MSDN SQL Server forums 上发布了我发现的摘要,因为我发现不仅在 JDBC 客户端和管理工作室之间,而且在微软自己的命令行客户端 SQLCMD 之间也存在不同的性能,我还检查了一些更激进的事情,比如网络流量,或者将存储过程包装在另一个存储过程中,只是为了笑。

我感觉问题出在游标执行方式的某个地方,它以某种方式导致 Java 进程被挂起,但是为什么不同的客户端应该在什么都没有的情况下产生这种不同的锁定/等待行为else 正在运行并且相同的执行计划正在运行,这超出了我的技能(我不是 DBA!)。

因此,我决定 4 天的时间足以让任何人浪费在这样的事情上,所以我会勉强围绕它编写代码(老实说,存储过程需要重新编写代码以增加增量而不是每周重新计算所有数据),并将这个记录下来以体验。我会留下这个问题,非常感谢所有把帽子放在戒指上的人,这一切都很有用,如果有人想出更多的东西,我很想听听更多的选择......如果有人找到这篇文章是在他们自己的环境中看到这种行为的结果,那么希望这里有一些建议您可以自己尝试,并希望比我们更深入地了解。

我现在已经为我的周末做好准备了!

-詹姆斯

【问题讨论】:

  • 在您的 JDBC 代码中,您使用的是CallableStatementPreparedStatement 还是Statement
  • Java 案例的时间安排在哪里?在数据库中?在 Java 领域完成查询?结果处理 Java 完成后?
  • 我正在使用 CallableStatement。 Java 案例的时序来自于日志记录语句。作为参考,我们在 MGMT 工作室谈了大约 12 分钟,Vs。约 16 分钟的 Java 语言。

标签: java sql sql-server stored-procedures jtds


【解决方案1】:

Java 案例是否包括将结果传输到 Java 服务器(网络开销)以及一些 Java 处理? 12 分钟的查询可能会产生相当多的数据。

【讨论】:

  • 谢谢,我已经更新了我的原始描述,但是不,存储的过程不返回任何数据。从字面上看,调用应该在数据库中触发 sql,然后阻塞直到过程完成,而这在 Java 中需要更长的时间。
【解决方案2】:

如果您正在查看分析器并且执行之间没有差异,那么差异必须与客户端系统有关。

4 分钟只是为了准备要发送的语句似乎很长,所以 12 分钟的等待肯定会导致其他一些影响——不知道它是什么。

【讨论】:

  • 谢谢,我已经用 Java 客户端的一些代码更新了我的原始描述。 16 分钟是执行存储过程所花费的纯数据库时间,因此与 Mgmt Studio 中需要 12 分钟的数据库进行直接比较。即看到消息“正在执行程序...”和“程序完成”之间的时间为 16 分钟。
【解决方案3】:

您可以附加 Profiler 并监视事件 SQL:BatchCompletedSP:Completed,过滤器的持续时间 > 1000。从 Java 客户端和 SSMS 运行该过程。比较两个事件的读取和写入(Java 与 SSMS)。它们有显着不同吗?这表明执行路径或计划有很大不同,I/O 也有很大差异。

也尝试捕获两者的Showplan XML事件并比较计划(将事件保存为.sqlplan文件,在SSMS中打开以方便分析)。他们有类似的计划吗?估计与实际(行、倒带、重新绑定)是否存在巨大差异?它们是否具有相同程度的并行性?计划也可以从sys.dm_exec_requests 视图中检索。

是否引发了任何警告事件,例如Missing Column StatisticsSort WarningsHash WarningExecution WarningsBlocked Process

关键是您可以使用一整套调查工具。找到差异的根本原因后,您可以将其追溯到 Java 环境设置和 SSMS 环境 (ADO.Net SqlClient) 之间的差异。默认事务隔离级别、ANSI 设置等。

【讨论】:

  • 谢谢,这绝对和我一直在想的一样,我运行了 sql profiler,发现以下内容:Java 应用程序:CPU:466,514 读取:142,478,387 写入:284,078 持续时间:983,796 SSMS: CPU:466,973 读取:142,440,401 写入:280,244 持续时间:769,851 所以我由此得出结论,他们正在做类似的工作,只是他们的工作方式导致了瓶颈?我刚刚问了一个关于如何使用 JDBC 连接获取 xml 显示计划的新问题:stackoverflow.com/questions/1791494/…
  • 它们的 IO 模式非常相似,CPU 几乎相同。唯一显着的不同是持续时间。这将表明 Java 应用程序需要更多等待。在每次测试之前运行DBCC SQLPERF('sys.dm_os_wait_stats', CLEAR),然后在每次测试之后收集sys.dm_os_wait_stats,它应该指出如果正在等待。
  • 哇!....针对不同客户的计划是不同的! (请记住,存储的 proc 调用完全相同!)好的,现在我有一些事情要做(而不是通常的错误报告“它很慢”)。我将结束这个问题,如果需要,我会问一个更有针对性的关于查询计划重用的问题,并在此处引用它。感谢您对 Remus 的所有帮助,我会为您提供这个答案。
【解决方案4】:

检查:您的问题是两个应用程序(SSMS、Java)对 SQL Server 进行完全相同的调用,而 SQL Server 对每个应用程序的行为不同?如果是这样,我每隔一两年就会遇到这样的事情,它们会伤到我的大脑好几天。

有一次,我最终隔离了每个进程调用并在 Profiler 中为整个进程记录所有内容。我最终注意到 Login 事件(在 TextData 下)显示了大量信息,如下所示:

-- network protocol: TCP/IP
set quoted_identifier on
set arithabort off
set numeric_roundabort off
set ansi_warnings on
set ansi_padding on
set ansi_nulls on
set concat_null_yields_null on
set cursor_close_on_commit off
set implicit_transactions off
set language us_english
set dateformat mdy
set datefirst 7
set transaction isolation level read committed

“现有连接”事件也会显示此信息——但是,有时会立即发送后续调用(批处理、RPC,我刚才不记得了)[ISQL 或 OSQL 这样做了,我认为] 立即重置其中的一些——Arithabort 和 Quoted_Identifier 似乎是最喜欢的,其他 SET 选项也会根据应用程序数据库接口使用的任何连接协议的设置或要求进行修改。

另一个:一些设置在“创建”时保留为过程的属性,而其他设置在编译时考虑在内。一方面,您的连接的 SET 值可能会被创建过程时保存的配置覆盖;另一方面,您的两个连接可能相差很大,以至于为一个过程生成了两个执行计划。 (经过充分研究,所有这些信息都可以在系统表和 DMV 中找到。)

简而言之,在我看来,SQL 晦涩难懂让您感到困惑。直到今天,我都讨厌所有这些 goombah 设置。我注意到的事情一直在搞乱他们[我的意思是,真的,哪个傻瓜会为连接池设置implicit_transaction?但是一旦他们这样做了......] 当地面(规则)不断从你下面改变时,很难建造结构。毕竟,记住那个家伙说的在沼泽中建造城堡......

【讨论】:

  • 谢谢菲利普,这证实了我的一些怀疑,我最初打折了“goombah”设置,因为我认为不同客户端之间的查询计划不同,但是经过几次运行,他们收敛(我认为这是由于随着时间的推移收集的关于表使用情况的统计信息)。我今天的计划是研究“goombah”(喜欢这个词,顺便说一句)设置,特别是当我发现 sqlcmd 的行为与我的 Java 客户端相同时......我仍在处理这个案子,我必须解决这个问题一,我会报告我的发现。干杯,-詹姆斯
  • 我最初也对这些设置不屑一顾,因为它们看起来很不起眼,尤其是对于存储过程的执行,尽管存在细微的差异。
  • 如果没有记错的话,我认为 Arithabort 是一个关键因素,因为 SQL 认为(必须认为)Off 或 On 会产生很大的不同。或者如果不是这个,它就是这样的。
  • Artithabort 绝对是这种行为在网络上其他地方的知名罪魁祸首(这就是我提到它的原因),但是,我在这两种情况下都尝试过,但仍然没有乐趣。我已经在这个 MSDN 列表上发布了(因为我看到了与 sqlcmd 相同的行为),但恐怕我将不得不承认失败(这很难承认,尤其是在这个论坛上!)在这个一个,然后按照我的方式编写代码。
【解决方案5】:

对不起,我没有找到正确的答案,所以我不想将其中任何一个分配为正确的,所以我将这个答案标记为正确,并祝所有遇到类似问题的人好运!

【讨论】:

  • 您的问题解决了吗?是 sendStringParametersAsUnicode=false 吗?我发现在使用 Microsoft 驱动程序时会有所帮助,但还没有让它与 JTDS 一起使用。
  • 这个帖子连答案都没有,这怎么可能是正确答案?
【解决方案6】:

您知道 Microsoft 为其数据库提供 JDBC 驱动程序吗?

这些可能会更高效。

显然..您现在可能已经解决了问题。

【讨论】:

    【解决方案7】:

    我记得不久前遇到过类似的问题,因为 JTDS 正在默默地将字符串参数转换为 Unicode 或类似的东西。由于该转换,当我们从 SSMS 运行存储过程时,SQL Server 无法使用正在使用的索引。

    【讨论】:

      【解决方案8】:

      我不确定这篇文章是否仍然相关。我们在应用程序中遇到了类似的问题。

      在 SQL Management Studio 中运行存储过程与从 ​​JDBC 中运行存储过程的一个关键区别在于事务上下文。如果您在 Java 中使用 ORM,则默认情况下存储过程在事务上下文中运行。当您直接在 SQL 管理工作室中运行存储过程时,事务处于关闭状态。存在很大的性能差异。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-07-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-09-01
        相关资源
        最近更新 更多