【问题标题】:HQL query causing insufficient disk space exceptionHQL查询导致磁盘空间不足异常
【发布时间】:2019-02-22 15:11:58
【问题描述】:

我有以下 SQL 查询,我将其翻译为 HQL:

SELECT f.date,
    f.name,
    SUM(f.seats) 
FROM Foo f 
WHERE EXISTS (  SELECT 1 
                FROM Foo fh 
                WHERE f.start + f.end IN (  SELECT fl.start + fl.end 
                                                            FROM Foo fl 
                                                            WHERE fl.date BETWEEN dateadd(yy,-1,fh.date) 
                                                                AND fh.date 
                                                                AND fl.name = '<name>') 
                    AND f.date = fh.date 
                    AND fh.date >= '2016-01-01'
                    AND fh.name = '<name>' ) 
    AND f.date >= '2016-01-01'
GROUP BY f.date,
    f.name 
ORDER BY f.date ASC,
    SUM(f.seats) DESC

在我的应用程序中,这个查询导致标题中的错误:

Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: Could not allocate a new page for database 'TEMPDB' because of insufficient disk space in filegroup 'DEFAULT'. Create the necessary space by dropping objects in the filegroup, adding additional files to the filegroup, or setting autogrowth on for existing files in the filegroup.
    at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDatabaseError(SQLServerException.java:216)
    at com.microsoft.sqlserver.jdbc.SQLServerResultSet$FetchBuffer.nextRow(SQLServerResultSet.java:4853)
    at com.microsoft.sqlserver.jdbc.SQLServerResultSet.fetchBufferNext(SQLServerResultSet.java:1781)
    at com.microsoft.sqlserver.jdbc.SQLServerResultSet.next(SQLServerResultSet.java:1034)
    at org.apache.commons.dbcp2.DelegatingResultSet.next(DelegatingResultSet.java:191)
    at org.apache.commons.dbcp2.DelegatingResultSet.next(DelegatingResultSet.java:191)
    at org.hibernate.loader.Loader.processResultSet(Loader.java:986)
    at org.hibernate.loader.Loader.doQuery(Loader.java:948)
    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:340)
    at org.hibernate.loader.Loader.doList(Loader.java:2689)

这显然是由于查询的效率低下以及执行的次数以及处理的行数造成的。


让我们解释一下查询的作用。下面的例子:

我有关于优步司机的数据。每行是司机的一个驱动器,包含日期(月份)、司机姓名、司机可用的座位、开始位置和结束位置。

E. g.:

Date        Name    Seats       Start   End
-------------------------------------------
7/1/2019    John    45          A       B

数据按月汇总。所以JohnAB 之间有9 个驱动器,每次他有5 个座位​​可用。当然,也有其他人驾驶同一条路线,因此与John竞争。

Date        Name    Seats       Start   End
-------------------------------------------
7/1/2019    John    45          A       B
7/1/2019    Doe     25          A       A
7/1/2019    Alice   35          A       C
7/1/2019    John    30          A       A
7/1/2019    Doe     25          A       C
7/1/2019    Alice   10          A       B
7/1/2019    Doe     5           A       B
7/1/2019    Alice   15          A       A

所以对于7/1/2019Johns“网络”(所有路线)进行了这场比赛:

Date        Name    Seats   Route
---------------------------------
7/1/2019    John    30      A-A
7/1/2019    Doe     25      A-A
7/1/2019    Alice   15      A-A

7/1/2019    John    45      A-B
7/1/2019    Doe     5       A-B
7/1/2019    Alice   10      A-B

如您所见,在此结果中,路线A-C 没有列出,因为John 根本没有驾驶它。如果我们将示例数据扩展一个新的月份8/1/2019

Date        Name    Seats       Start   End
-------------------------------------------
8/1/2019    John    65          A       C
8/1/2019    Doe     25          A       A
8/1/2019    Alice   35          A       A
8/1/2019    Doe     25          A       B
8/1/2019    Alice   10          A       B
8/1/2019    Doe     5           A       C
8/1/2019    Alice   15          A       C

我们可以看到John 这个月只开车A-C。由于network 应该在过去 1 年的时间跨度内(2018 年 8 月 1 日至 2019 年 8 月 1 日)构建,Johns 网络现在是所有三个路由(A-AA-BA-C),但仅用于计算截至 8/1/2019 的竞争对手。对于7/1/2019Johns 网络保持A-AA-B。所以8/1/2019 的结果是这样的:

Date        Name    Seats   Route
---------------------------------
8/1/2019    John    0       A-A
8/1/2019    Doe     25      A-A
8/1/2019    Alice   35      A-A

8/1/2019    John    0       A-B
8/1/2019    Doe     25      A-B
8/1/2019    Alice   10      A-B

8/1/2019    John    65      A-C
8/1/2019    Doe     5       A-C
8/1/2019    Alice   10      A-C

John 只开过A-C,这就是为什么他在其他路线上被计为 0 座的原因。

由于结果是对座位求和而忽略了路线,所以查询的实际输出如下:

7/1/2019    John    75          <-- 30+45
7/1/2019    Doe     30          <-- 25+5
7/1/2019    Alice   25          <-- 10+15

8/1/2019    John    65          <-- 65+0+0
8/1/2019    Doe     55          <-- 25+25+5
8/1/2019    Alice   55          <-- 35+10+10

在此结果中,我们仅将 A-AA-B 作为 7/1/2019 的路线作为 Johns 竞争对手的路线,因为在该日期之前没有数据。对于8/1/2019Johns 网络是A-AA-BA-C,尽管他只在8/1/2019 中驾驶A-CA-AA-B 在@98765436767 中)。

我希望我提供的数据是可以理解的。如果您需要更多说明,请询问,我会尽力解释更多。


我需要如何更改查询以显着提高性能?

到目前为止,我还没有使用过JOINs,因为我必须加入子查询,而这在 HQL 中是不允许的。


如果您需要更多信息/说明,请随时提问!


编辑:

我知道我也可以在codereview.stackexchange.com 上发帖,但我选择反对它,因为查询本身有效,如果只针对 1 个名称执行并且只针对更多名称失败。我对codereview.stackexchange.com的理解是,应该只有性能提升问题

【问题讨论】:

  • 我建议您修改示例,以便座位加起来,以免混淆这个问题的读者
  • 您是否直接在某些 SQL 客户端中尝试了生成的 SQL 查询并对其进行了解释?如果它会返回大量可以解释内存问题的数据,但如果不是,数据库仍然可以创建一个巨大的临时表(该错误似乎表明了这一点)。所以我会先尝试分析和修复SQL,然后尝试调整HQL查询,让Hibernate生成我想要的SQL。
  • ...事实上,我确信使用窗口函数解决这个问题可能非常“容易”,但是我不想修复您现有的查询,而是希望通过精心设计的示例并从那里开始。很确定你可以忘记 HQL(正如我在上一个问题中已经建议的那样):)
  • @Thomas 如果我只为 1 个名称运行该查询,它返回的行数少于 3k。所以这不是结果数量的问题,而是如果多次运行查询如何被数据库处理(创建巨大的临时表)。我确实发布了 SQL 查询而不是 HQL 查询,因为它比 HQL 更容易修复 SQL
  • @LukasEder 我已经更新了示例,它们现在应该加起来了。正如您在上一个问题中向我解释的那样,我认为我能够从查询中删除所有 carthesian 产品。我怀疑start + end 的计算列能否解决手头的内存问题。如果您需要更多信息或不清楚的地方,请随时提出,我会尽力提供可以理解的解释

标签: java sql sql-server hql


【解决方案1】:

我在发布问题后不久就提出了这个问题:

SELECT f.date,
    f.name,
    SUM(f.seats) 
FROM Foo f 
WHERE f.start + f.end IN (  SELECT fh.start + fh.end 
                            FROM Foo fh 
                            WHERE fh.date BETWEEN DATEADD(yy, -1, f.date) 
                                AND f.date 
                                AND fh.name = '<name>') 
    AND f.date >= '2016-01-01' 
GROUP BY f.date,
    f.name 
ORDER BY f.date ASC,
    SUM(f.seats) DESC

如您所见,我几乎只是删除了WHERE EXISTS 子句。我不太确定这样做是否正确,或者这可能导致什么错误,但它至少解决了手头的错误(磁盘空间不足异常)。

如果您对我的查询有其他想法或意见,请随时分享!

【讨论】:

  • 该查询看起来不错。一个建议:我不会使用start + end 的串联来生成行程类型,因为存在轻微的歧义风险,例如当start 的值为'A', 'AB'end 的值为BCC。要么在值之间使用数据集中不存在的分隔符标记,要么分别比较 f.start = fh.startf.end = fh.end,使用 EXISTS() 而不是 IN
  • 我明白你所说的“歧义风险”是什么意思。我现在切换到EXISTS(),它似乎也提高了抓取性能!
  • 想想我的例子。你有一排有start = 'A' and end = 'BC'。另一行有start = 'AB' and end = 'C'。这两行具有相等的 start + end 值。可能不是你想要的。
猜你喜欢
  • 2016-10-11
  • 1970-01-01
  • 2012-02-15
  • 1970-01-01
  • 2016-06-08
  • 2013-10-09
  • 2015-12-04
  • 2015-12-30
  • 1970-01-01
相关资源
最近更新 更多