【问题标题】:eclipselink jpa generates count queries using COUNT(id) instead COUNT(*)eclipselink jpa 使用 COUNT(id) 而不是 COUNT(*) 生成计数查询
【发布时间】:2016-02-02 12:02:50
【问题描述】:

我正在使用 Eclipselink、Spring Data 和 Postgresql。在我的项目中,我注意到在使用 SpringData 存储库提供的分页结果时,会出现如下查询:

SELECT COUNT(id) 
FROM table 
WHERE [part generated according to specification]

其中“id”是“table”的主键。用解释挖掘我注意到对于一个非常大的表, COUNT(id) 比 COUNT() 慢大约 10 倍(count(id) 在“id”列中查找非空值,而 count( ) 只返回匹配条件的行数),count(*) 也可以使用索引,而 count(id) - 不是。

我跟踪了 SpringData 基本存储库类,似乎只有 JPA 实现负责此查询生成。

  1. 使用 count(id) 而不是更快的 COUNT(* ) 的原因是什么?
  2. 我能否改变这种行为(无论如何 - 甚至增强现有组件)?

任何帮助表示赞赏

-- [编辑]--

有一张桌子:

\d ord_order
                                       Table "public.ord_order"
         Column          |           Type            |                       Modificators
-------------------------+--------------------------+----------------------------------------------------------
 id                      | integer                  | NOT NULL DEFAULT nextval('ord_order_id_seq'::regclass)
 test_order              | boolean                  | DEFAULT false
...
Indexes:
    "pk_order" PRIMARY KEY, btree (id)
    "idx_test_order" btree (test_order)



# explain SELECT COUNT(*) FROM ord_order WHERE (test_order = false);
                                QUERY PLAN
--------------------------------------------------------------------------
 Aggregate  (cost=89898.79..89898.80 rows=1 width=0)
   ->  Index Only Scan using idx_test_order on ord_order  (cost=0.43..85375.37 rows=1809366 width=0)
         Index Cond: (test_order = false)
         Filter: (NOT test_order)
(4 wiersze)



# explain SELECT COUNT(id) FROM ord_order WHERE (test_order = false);
                                QUERY PLAN
--------------------------------------------------------------------------
 Aggregate  (cost=712924.52..712924.53 rows=1 width=4)
   ->  Seq Scan on ord_order  (cost=0.00..708401.10 rows=1809366 width=4)
         Filter: (NOT test_order)
(3 wiersze)

现在的区别是 ~90k 与 ~713k 以及索引扫描与全扫描

【问题讨论】:

  • 请发布执行计划 - 如果id 真的是表的主键,这很难相信。通常的神话是 count(id)count(*) 快所以也许 EclipseLink 开发人员相信这个神话(我从未见过它是真的)
  • 在主要描述中添加了查询计划。 count(id) 如何比 count() 快(根据 w3schools:w3schools.com/sql/sql_func_count.asp) count() 只返回匹配条件的行数,而 count(id) 返回匹配条件的行数并且具有非空值。它做了一些额外的事情,所以它不能更快​​......也许一些特定的索引可以加速它,但不会超过 count(*) 的速度(至少我认为是这样)
  • test_order 列是如何定义的?以及索引idx_test_order 究竟是如何定义的?运行后计划有变化吗analyze ord_order
  • test_order 只是布尔列,默认 = false (允许空值,但仅通过模式并且该列中没有空值),idx_test_order 是这一列(test_order)的简单索引,无法分析现在的表。将不得不等待维护窗口。
  • 我希望来自 JPA 或 Hibernate 的人发表评论或提供解决方案。这真是一个巨大的无赖,我最近也被它击中了。

标签: java postgresql hibernate jpa spring-data-jpa


【解决方案1】:

我设法提供了自定义 Spring Data Repository 基类实现和使用该实现的工厂。结果生成的计数查询现在具有以下形式:

SELECT COUNT(1) FROM table

与 COUNT(*) 具有相同的计划。这似乎是一个很好的解决方案,并且适用于应用程序中所有已定义的存储库。

我不知道如何生成 COUNT(*),COUNT(1) 更容易,因为 COUNT 函数需要一些表达式作为参数,我可以提供静态值 - 1

【讨论】:

    【解决方案2】:

    count(*) 可以使用索引,因为查询中只引用了一个列 (test_order)。 count(id) 引用两列,因此 Postgres 必须选择 id test_order 列才能构建结果。

    正如我已经提到的,有些人认为count(id)count(*) 快 - 当查询没有限制时。对于任何具有良好优化器的 DBMS 来说,这是一个从未有过的神话。我想这就是你的混淆层使用count(id) 而不是count(*) 的原因。

    假设您不想摆脱 ORM(重新获得对您的应用程序正在使用的 SQL 的控制),我能看到的唯一解决方法是创建 Postgres 可以使用的部分索引:

    create index on ord_order (id)
    where test_order = false;
    

    【讨论】:

    • 这是我已经考虑过的解决方法,并且可能会在短期内遵循。问题在于它是“本地”解决方案——仅适用于这个单一的表和这个单一的查询。正如我所提到的,查询是动态构建的,因此条件可能会发生变化(我在这里放置的是最常见的)......我宁愿找到改变 QueryBuilder 组件的方法,以便它始终使用 count(* )......
    • 好的。测试了索引,不幸的是它不起作用:在 ord_order (id) 上创建索引 idx_order_count_opt 其中 test_order=false; # 解释 SELECT COUNT(id) FROM ord_order WHERE (test_order = false);聚合 (cost=713178.79..713178.80 rows=1 width=4) -> Seq Scan on ord_order (cost=0.00..708605.89 rows=1829158 width=4) Filter: (NOT test_order) 现在 - 问题是大多数记录有 test_order=false,所以规划器不使用索引来访问值,因为这不会有任何改进。所以我必须在 ORM 层面制定解决方案。
    • @redguy:你vacuum analyze创建索引后的表了吗?
    • 我确实分析了表格,没有效果。
    • 应该使用带有该索引的Index Only Scan。您的确切 Postgres 版本是什么?不幸的是,Postgres 中的索引扫描不如 e.g.在 Oracle 中,有时它只在一段时间后使用它(当 所有 使用该表的事务/会话完成时)。
    猜你喜欢
    • 2011-05-24
    • 1970-01-01
    • 2016-02-27
    • 2021-04-24
    • 2019-07-02
    • 1970-01-01
    • 2016-10-11
    • 2013-10-11
    • 1970-01-01
    相关资源
    最近更新 更多