Hibernate SQL In 子句使 CPU 使用率达到 100%

Posted

技术标签:

【中文标题】Hibernate SQL In 子句使 CPU 使用率达到 100%【英文标题】:Hibernate SQL In clause making CPU usage to 100% 【发布时间】:2015-07-01 02:04:27 【问题描述】:

在我的 Java 应用程序中,我使用的是 SQL server and Hibernate3 with EJB。当我尝试执行选择查询with In clause 时,DB 服务器 CPU 使用率达到 100%。但是当我尝试在SQL management studio 中运行相同的查询时,查询运行时没有任何 CPU 峰值。应用服务器和数据库服务器是两台不同的机器。我的表具有以下架构,

CREATE TABLE student_table (
       Student_Id BIGINT NOT NULL IDENTITY
     , Class_Id BIGINT NOT NULL
     , Student_First_Name VARCHAR(100) NOT NULL
     , Student_Last_Name VARCHAR(100)
     , Roll_No VARCHAR(100) NOT NULL
     , PRIMARY KEY (Student_Id)
     , CONSTRAINT UK_StudentUnique_1 UNIQUE  (Class_Id, Roll_No)
);

该表包含大约 1000k 条记录。我的查询是

select Student_Id from student_table where Roll_No in ('A101','A102','A103',.....'A250');

In 子句包含 250 个值,当我尝试在 SQL 管理工作室中运行上述查询时,结果会在 1 秒内检索到,并且没有任何 CPU 峰值。但是当我尝试通过休眠运行相同的查询时,CPU 峰值达到 100% 大约 60 秒,结果在 60 秒左右检索到。休眠查询是,

Criteria studentCriteria = session.createCriteria(StudentTO.class);
studentCriteria.add(Restrictions.in("rollNo", rollNoLists)); //rollNoLists is an Arraylist contains 250 Strings
studentCriteria.setProjection(Projections.projectionList().add(Projections.property("studentId")));
List<Long> studentIds = new ArrayList<Long>();
List<Long> results = (ArrayList<Long>) studentCriteria.list();
if (results != null && results.size() > 0) 
   studentIds.addAll(results);

return studentIds;

这是什么问题。如果通过 Management Studio 运行相同的查询,则检索结果时不会出现任何峰值,并且会在 1 秒内检索结果。有什么解决办法???

编辑1: 我的休眠生成查询是,

select this_.Student_Id as y0_ from student_table this_ where this_.Roll_No in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)

编辑2: 我的执行计划 这是在索引 roll_no 之后

CREATE INDEX i_student_roll_no ON student_table (Roll_No) 

,

【问题讨论】:

请贴一下hibernate生成的SQL查询好吗? @Amogh 更新了休眠生成的查询 您是否尝试过用 HQL 替换条件? 是的,当然试过了..但没有用...同样的问题,100% CPU 使用率飙升 我遇到了类似的问题...我通过创建一个临时表来解决它,该表具有原始表(PK)和搜索字段(不是 PK)作为 PK,然后针对这个临时表。在 Hibernate(使用 Native SQL)下执行需要 20-30 秒的查询开始立即运行。所以我相信你应该试一试。 【参考方案1】:

您从控制台运行的查询很容易缓存,这就是响应是即时的原因。如果您查看查询,您会发现所有参数都嵌入在查询中,因此查询计划器可以检测到没有变化,并且所有执行将始终执行相同的计划和相同的缓存结果。

您使用 Hibernate 运行的查询,即使它是本机查询,它使用 PreparedStatement 并且参数在查询执行时绑定,to quote one of the best author on indexing:

这与绑定参数有什么关系?

DB2、Oracle 和 SQL Server 的共享执行计划缓存使用文字 SQL 字符串的哈希值作为缓存的键。缓存 如果 SQL 包含随时间变化的文字值,则找不到计划 每次执行。

占位符(绑定参数)统一语句,以便在使用不同值执行时 SQL 字符串是相同的——因此, 提高缓存命中率。

要解决这个问题,您需要在 (Roll_No, Student_Id) 列上添加索引,以便查询成为仅索引扫描。

SQL Server 默认为cluster indexes,这将您限制为每个表一个聚集索引,因此您可能希望turn this table into a heap table instead 并专注于仅索引扫描。

【讨论】:

我认为执行计划的缓存在这里不是问题。解析器非常快,对于这样一个简单的查询,没有很多可能的执行计划候选者。我宁愿说,如果有 250 个绑定变量,优化器可能选择了一个糟糕的执行计划,而不是可以通过内联值更容易选择的更优化的执行计划。在 Oracle 术语中,这曾经被称为“绑定变量窥视问题”(这在 11g 中不再是问题)。不过,不知道 SQL Server。 我也认为数据库不使用索引,如果甚至有要使用的索引。仅索引扫描非常适合这种类型的查询,因为它会过滤一列并再选择一列。 在这种情况下肯定会有所帮助(尽管仅当您在索引中使用(Roll_No, Student_id) 列排序时)。 没错。我将把它完全添加到答案中以避免任何混淆。【参考方案2】:

要回答“为什么休眠速度很慢”这个问题,您需要查看运行休眠代码时服务器使用的实际执行计划,而不是运行查询时服务器使用的执行计划SSMS。您在问题中包含的执行计划的屏幕截图与您在运行休眠代码时获得的实际计划不同。一旦您有了该计划,您就可以将其与您从 SSMS 获得的计划进行比较,差异很可能解释了为什么它在一种情况下慢而在另一种情况下快。

Erland Sommarskog 有一篇非常好的文章,主要关注所谓的“参数嗅探”,这可能是这里出现问题的原因,但可能性不大。本文对我们有用的是,他解释了如何通过extract execution plan 从缓存中进行检查。

没有这些信息,你只能猜测。一种猜测是您将参数传递为nvarchar,但索引字段Roll_Novarchar,因此未使用索引。服务器将您的varchar 列转换为nvarchar 以进行比较,这意味着无法使用索引=> 它很慢并且转换可能是CPU 使用率高的原因。 http://sqlperformance.com/2013/04/t-sql-queries/implicit-conversion-costs https://www.sqlskills.com/blogs/jonathan/implicit-conversions-that-cause-index-scans/


这不是您问题的答案,而是问题的可能解决方案。不要将 250 个单独的参数传递给 IN 子句的查询,而是使用 table-valued parameter 并将您的值作为表传递。在查询中使用JOIN 而不是IN。在您的 cmets 将拥有 100K 参数(这意味着您想要运行查询 400 次)之后,这一点尤其如此。事实上,即使对于表值参数来说,100K 也有点太多了,所以我会考虑拥有一个永久或临时的辅助表,它将保存这些Roll_No具有适当的索引。主要查询将JOIN 发送给它。像这样的:

CREATE TABLE RollNumbers (
     Roll_No VARCHAR(100) NOT NULL
     ,PRIMARY KEY (Roll_No)
);

确保Roll_No 上的表RollNumbers 中有索引。确保Roll_No 上的表student_table 中有索引。首先将INSERT 100K 值转换为RollNumbers,然后在主查询中使用它们:

SELECT Student_Id 
FROM
    student_table
    INNER JOIN RollNumbers ON RollNumbers.Roll_No = student_table.Roll_No

根据整个系统,RollNumbers 表可以是永久表、临时表或表变量。

【讨论】:

【参考方案3】:

在休眠级别检查查询中使用的所有字段的数据类型,并确保它与您的表定义匹配。像 hibernate 这样的框架使用 Unicode 支持的数据类型(例如 nvarchar)。尝试更改任一侧的数据类型。

或者,您可以在连接字符串中添加名为 sendStringParametersAsUnicode 的参数。它将强制休眠使用 varchar 而不是 nvarchar。

试一试,然后告诉我们!

【讨论】:

【参考方案4】:

您可能在想,因为您的慢查询需要 60 秒,所以您的“快速”查询需要 1 秒实际上很快。不是这种情况。这种执行速度差异使您无法理解此处的实际问题。

另一个问题(可能不是实际问题)

如果您在Roll_No 上有一个索引,那么无论您使用的是绑定变量还是内联值,您正在运行的非常简单的查询类型应该会在不到一毫秒的时间内返回结果。

我只是假设除了从表中的约束生成的索引之外,您没有任何索引。因此,您应该在Roll_No 上添加一个简单的索引:

CREATE INDEX i_student_roll_no ON student_table (Roll_No);

或者您可以在上述索引中添加一个附加列,以使其成为此查询的“覆盖索引” (as explained by Vlad)

CREATE INDEX i_student_roll_no2 ON student_table (Roll_No, Student_Id);

这将使这个特定的查询更快,因为执行计划不需要再次访问磁盘来从表中获取Student_Id。该信息将已包含在索引中。不过,请谨慎使用覆盖索引,因为它们:

    占用更多空间,特别是对于像您这样的中等大小的桌子 只有在您的查询仅限于真正涵盖的列时才能正常工作,在您的情况下不太可能保持这种方式。

如何使用 SQL Server Management Studio 识别这一点?

实际上,SQL Server Management Studio 中有一个非常好的功能。当您打开执行计划(您应该这样做)时,您将获得有关查询的以下附加信息:

右键单击该信息并选择“缺少索引详细信息...”以获取与此类似的信息:

/*
Missing Index Details from SQLQuery1.sql - 
  LUKAS-ENVY\SQLEXPRESS.test (LUKAS-ENVY\Lukas (52))
The Query Processor estimates that implementing the 
  following index could improve the query cost by 87.5035%.
*/

/*
USE [test]
GO
CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
ON [dbo].[student_table] ([Roll_No])
INCLUDE ([Student_Id])
GO
*/

为什么 SQL Server Mgmt Studio 和 Hibernate 有区别?

您的原始问题尚未得到解答。也许,附加索引修复了它,但也许您没有提供所有信息。你可以:

绑定变量窥视问题 Hibernate 中的 N+1 个问题(有那么多行)

【讨论】:

我试过 CREATE INDEX i_student_roll_no ON student_table (Roll_No) 但没有用。我已经更新了有问题的执行计划 @JayaAnanthram:“没用”是什么意思?它是如何影响两个查询执行速度的?另外:你确定 Hibernate 不执行其他查询吗? CPU 使用率仍为 100%。而且我确信在执行上述查询时没有其他查询正在执行。还有一点,我将有大约 100k 卷号,从那我将通过传递前 250 个卷号然后接下来的 250 个卷号等等来构建查询...... 嗯,我不确定我是否理解你的 250 步。那么,我怀疑您的问题中仍然缺少一些信息。尽管如此,索引位肯定会有用,但它并不能回答您的实际问题 我添加了所有信息。唯一的事情就是我重命名了变量名和表名。【参考方案5】:

一个奇怪的巧合,我这周才遇到这个问题,并不是这里很多人提到的 Hibernate N+1 问题。我使用带有 Postgres Java 驱动程序的 Amazon Redshift。

由于我不会在这里讨论的原因,我使用了一个 7,000 参数长的 WHERE IN () 语句,当对数据库运行时,该语句会导致 10 秒的查询执行(这是对大表的复杂查询,而且我们还没有设置索引——仍在开发中,我们还没有开始调整)。通过 Hibernate 运行时,查询执行时间为 120 秒。

我发现如果你从 Hibernate 中获取实际的 SQL 字符串,用实际值的字符串替换 (?,?...?) 并运行它(仍然通过 Hibernate),突然一切都恢复到 10秒。

深入研究 Hibernate 内部结构,结果发现它们每个参数进行了大量的处理,导致初始 CPU 峰值和具有大量语句的膨胀执行时间参数。

此外,一旦查询最终发送到数据库,在使用参数时,数据库服务器的 CPU 会在持续时间内达到 100%,但在不使用参数时并非如此。我还没有检查所有这些处理中有多少发生在栅栏的哪一侧的确切时间,但看起来使用这么多参数在 Hibernate 端或数据库端都不可行。

解决方案?使用更少的参数。或者找到一个支持大型参数集同时保持高性能的数据库。

我们可能会从 Hibernate 切换到 jOOQ,因为 jOOQ 允许您使用官方 DSL 的define your own custom SQL fragments。然后,我们将手动构建不带参数的 IN() 子句。我们可以这样做,因为我们的 IN 变量是内部 ID,所以 SQL 注入是不可能的,但如果 SQL 注入可能的,请确保清理您的输入。

【讨论】:

只是好奇,您使用的是 MS SQL Server 吗?据我所知maximum number of parameters in SQL Server is 2100,所以了解hibernate如何设法传递7000个参数很有趣。 在您的情况下,当您尝试通过休眠和针对数据库(SQL 服务器管理工​​作室)运行时,CPU 使用率是否有任何差异?? 实际上是 Amazon Redshift。我不知道是否有限制,但我们还没有达到。我认为我的一位同事尝试了 100,000 个值,但它仍然有效。 CPU使用率我没看,不过今天进去可以看看。 是的,我在 Hibernate 中运行时遇到 CPU 峰值。 CPU尖峰是什么意思?在整个查询时间内始终保持 100%?还是 X% 和 Y% 之间的一些起伏?【参考方案6】:

看起来你不是从休眠状态执行查询时获得的所有记录,而是在你的代码中,来自查询执行的所有记录都放入集合中。

【讨论】:

rollNoLists 仅包含 250 个值,下次调用时它将包含下一个 250 个值,依此类推。 嗨 Jaya,您是说您的查询结果集是 250 条记录吗? Alexander 指出,像 SSMS 这样的可视化工具会将从结果集中获取的行数限制在一个很小的范围内。例如,当您的结果集是 10,000 条记录时,您的 UI 可能只显示 250 条记录。这减少了执行时间,因为其他 9,750 条记录不需要推送到客户端应用程序。另一方面,Hibernate 会加载整个结果集。 @PeterSchuetze,虽然是一个有效的评论,但我从未见过 SSMS 不会显示所有行。例如,与 SQLyog 不同。【参考方案7】:

通过运行 SQL Profiler 很容易确定问题。您将确切地看到在这两种情况下对数据库执行的 SQL 语句。

http://www.codeproject.com/Articles/21371/SQL-Server-Profiler-Step-by-Step

【讨论】:

请注意,运行 SQL Profiler 会影响整个服务器的性能:dba.stackexchange.com/questions/818/…dba.stackexchange.com/questions/17474/… 明确地说,SQL Profiler 将显示服务器上正在执行的每条 SQL 语句。您将需要过滤这些结果以找到您感兴趣的语句。我认为毫无疑问,SQL Profiler 在运行时会对服务器产生一些影响。它相当于为应用程序启用日志记录。考虑到这一点,您应该将其用于开发、调试和调优。然后在完成后关闭或退出 SQL Profiler 应用程序。【参考方案8】:

我只是从this post为你指出LBushkin回答的这一部分

其次,当使用 IN 或 OR 与可变数量的 参数,您导致数据库必须重新解析查询 并在每次参数更改时重建执行计划。建筑 查询的执行计划可能是一个昂贵的步骤。最多 数据库缓存它们运行的​​查询的执行计划,使用 EXACT 查询文本作为键。如果您执行类似的查询但使用 谓词中的不同参数值 - 你很可能会 导致数据库花费大量时间解析和 制定执行计划。这就是为什么绑定变量是强 推荐作为确保最佳查询性能的一种方式。

所以可以尝试绑定变量,防止每次都运行执行计划

Bind Variables Usage (Parameterized Queries in SQL Server)

【讨论】:

如果是解决方案,是否需要尝试这种方式? select Student_Id from student_table where Roll_No in (:v1, :v2, :v3, :v4, :v5, ...... ,:v250) 并绑定 250 个值??

以上是关于Hibernate SQL In 子句使 CPU 使用率达到 100%的主要内容,如果未能解决你的问题,请参考以下文章

在本机 sql 查询中使用 IN 子句

Hibernate:在子选择查询中使用 IN 子句时出现错误

PL/SQL - 如何在 IN 子句中使用数组

如何保持 Spring Data JPA 或 Hibernate 中“in”子句中提供的顺序 [重复]

如何使用 IN 修复 Hibernate Named Native Query 的 Where 子句

“java.sql.SQLSyntaxErrorException:'on 子句'中的未知列'*'”在休眠条件下加入表时