【问题标题】:Hibernate batch-delete vs single deleteHibernate 批量删除与单次删除
【发布时间】:2013-03-02 05:01:26
【问题描述】:

编辑:根据我的一些调试和日志记录,我认为问题归结为为什么 DELETE FROM table WHERE id = xDELETE FROM table WHERE id IN (x) 快得多,其中 x 只是一个 ID。

我最近测试了批量删除与逐行删除的对比,发现批量删除要慢得多。该表有删除、更新和插入的触发器,但我已经测试了有和没有触发器的情况,并且每次批量删除都比较慢。 谁能解释为什么会这样或分享我如何调试它的提示?据我了解,我不能真正减少触发器激活的次数,但我最初有认为减少“删除”查询的数量将有助于提高性能。

我在下面提供了一些信息,如果我遗漏了任何相关信息,请告诉我。

以 10,000 个为单位进行删除,代码如下所示:

private void batchDeletion( Collection<Long> ids ) {
  StringBuilder sb = new StringBuilder();
  sb.append( "DELETE FROM ObjImpl WHERE id IN (:ids)" );

  Query sql = getSession().createQuery( sb.toString() );
  sql.setParameterList( "ids", ids );

  sql.executeUpdate();
}

只删除一行的代码基本上是:

SessionFactory.getCurrentSession().delete(obj);

该表有两个索引,在任何删除中都没有使用。不会发生级联操作。

这是DELETE FROM table where id IN ( 1, 2, 3 );的EXPLAIN ANALYZE的示例:

Delete on table  (cost=12.82..24.68 rows=3 width=6) (actual time=0.143..0.143 rows=0 loops=1)
  ->  Bitmap Heap Scan on table  (cost=12.82..24.68 rows=3 width=6) (actual time=0.138..0.138 rows=0 loops=1)
        Recheck Cond: (id = ANY ('{1,2,3}'::bigint[]))
        ->  Bitmap Index Scan on pk_table  (cost=0.00..12.82 rows=3 width=0) (actual time=0.114..0.114 rows=0 loops=1)
              Index Cond: (id = ANY ('{1,2,3}'::bigint[]))
Total runtime: 3.926 ms

每次我重新加载我的数据进行测试时,我都会清理并重新索引,我的测试数据包含 386,660 行。

测试是删除所有行,我没有使用TRUNCATE,因为通常有一个选择标准,但出于测试目的,我已将标准包括所有行。启用触发器后,逐行删除需要 193,616 毫秒,而批量删除需要 285,558 毫秒。然后我禁用了触发器,单行删除时间为 93,793 毫秒,批量删除时间为 181,537 毫秒。触发器会汇总值并更新另一个表 - 基本上是簿记。

我尝试过使用较小的批量大小(100 和 1),但它们似乎都表现更差。

编辑:打开 Hibernate 日志记录和逐行删除,它基本上是在做:delete from table where id=? 和 EXPLAIN ANALYZE:

Delete on table  (cost=0.00..8.31 rows=1 width=6) (actual time=0.042..0.042 rows=0 loops=1)
  ->  Index Scan using pk_table on table  (cost=0.00..8.31 rows=1 width=6) (actual time=0.037..0.037 rows=0 loops=1)
        Index Cond: (id = 3874904)
Total runtime: 0.130 ms

编辑:很好奇列表是否真的包含 10,000 个 ID,如果 Postgres 会做一些不同的事情:不。

Delete on table  (cost=6842.01..138509.15 rows=9872 width=6) (actual time=17.170..17.170 rows=0 loops=1)
  ->  Bitmap Heap Scan on table  (cost=6842.01..138509.15 rows=9872 width=6) (actual time=17.160..17.160 rows=0 loops=1)
        Recheck Cond: (id = ANY ('{NUMBERS 1 THROUGH 10,000}'::bigint[]))
        ->  Bitmap Index Scan on pk_table  (cost=0.00..6839.54 rows=9872 width=0) (actual time=17.139..17.139 rows=0 loops=1)
              Index Cond: (id = ANY ('{NUMBERS 1 THROUGH 10,000}'::bigint[]))
Total runtime: 17.391 ms

编辑:根据上面的解释分析,我从实际的删除操作中检索了一些日志记录。下面是单行删除的两种变体的日志记录。

这里有一些单独的删除:

2013-03-14 13:09:25,424:delete from table where id=?
2013-03-14 13:09:25,424:delete from table where id=?
2013-03-14 13:09:25,424:delete from table where id=?
2013-03-14 13:09:25,424:delete from table where id=?
2013-03-14 13:09:25,424:delete from table where id=?
2013-03-14 13:09:25,424:delete from table where id=?
2013-03-14 13:09:25,424:delete from table where id=?
2013-03-14 13:09:25,424:delete from table where id=?
2013-03-14 13:09:25,424:delete from table where id=?
2013-03-14 13:09:25,424:delete from table where id=?

这是单个删除的另一种变体(列表只有一项)

2013-03-14 13:49:59,858:delete from table where id in (?)
2013-03-14 13:50:01,460:delete from table where id in (?)
2013-03-14 13:50:03,040:delete from table where id in (?)
2013-03-14 13:50:04,544:delete from table where id in (?)
2013-03-14 13:50:06,125:delete from table where id in (?)
2013-03-14 13:50:07,707:delete from table where id in (?)
2013-03-14 13:50:09,275:delete from table where id in (?)
2013-03-14 13:50:10,833:delete from table where id in (?)
2013-03-14 13:50:12,369:delete from table where id in (?)
2013-03-14 13:50:13,873:delete from table where id in (?)

两者都是表中存在的ID,应该是连续的。


解释DELETE FROM table WHERE id = 3774887;的分析

Delete on table  (cost=0.00..8.31 rows=1 width=6) (actual time=0.097..0.097 rows=0 loops=1)
  ->  Index Scan using pk_table on table  (cost=0.00..8.31 rows=1 width=6) (actual time=0.055..0.058 rows=1 loops=1)
        Index Cond: (id = 3774887)
Total runtime: 0.162 ms

解释DELETE FROM table WHERE id IN (3774887);的分析

Delete on table  (cost=0.00..8.31 rows=1 width=6) (actual time=0.279..0.279 rows=0 loops=1)
  ->  Index Scan using pk_table on table  (cost=0.00..8.31 rows=1 width=6) (actual time=0.210..0.213 rows=1 loops=1)
        Index Cond: (id = 3774887)
Total runtime: 0.452 ms

0.162 vs 0.452 认为有显着差异?

编辑:

将批量大小设置为 50,000,Hibernate 不喜欢这个想法:

java.lang.StackOverflowError
        at org.hibernate.hql.ast.util.NodeTraverser.visitDepthFirst(NodeTraverser.java:40)
        at org.hibernate.hql.ast.util.NodeTraverser.visitDepthFirst(NodeTraverser.java:41)
        at org.hibernate.hql.ast.util.NodeTraverser.visitDepthFirst(NodeTraverser.java:42)
....

【问题讨论】:

  • 似乎当您删除单行时,它会使用其他索引。可以查一下吗?
  • 我添加了有关逐行删除的信息。表的 MAX(id) 是 3774904,所以我只是选择了一个更大的数字。我用低于 3774904 的 ID 尝试了相同的操作,但不在表格中并得到了类似的结果(表格中 ID 的类似结果)。
  • 是的,0.162 与 0.452 显着,因为 0.452 几乎是 0.162 的 3 倍。特别有趣的是,解释表明他们正在做同样的事情。我会向 postgresql 团队提出这个问题。 pgsql-general 可能是个好地方

标签: java hibernate postgresql


【解决方案1】:

好的,首先要注意的是,SQL 必须以某种方式转换为计划。您的 EXPLAIN 结果表明,与 IN(vals) 构造相比,相等性的逻辑根本不同。

WHERE id = 1;

被转换为一个简单的相等过滤器。

WHERE id IN (1);

转化为数组匹配:

WHERE id = ANY(ARRAY[1]);

显然规划器不够聪明,无法注意到在数组只有一个成员的情况下这些在数学上是相同的。所以它正在做的是计划一个任意大小的数组,这就是为什么你得到嵌套循环位图索引扫描。

这里的有趣之处不仅在于它速度较慢,而且在大多数情况下性能表现更好。所以在 in() 子句中有一个成员,它慢 40 倍,而有 10000 个成员,它只慢 170 倍,但这也意味着 10000 成员版本也快 50 倍对 id 进行超过 10000 次单独的索引扫描。

所以这里发生的情况是,计划者正在选择一个计划,该计划在检查大量 id 时性能更好,但在只有少数 id 时性能更差。

【讨论】:

  • 我会尝试增加数组大小并报告发生了什么!是否有选择批量大小的一般准则?
  • 似乎 Hibernate 不喜欢 50,000(出现堆栈溢出错误)
  • 重新阅读您写的内容 - 我尝试了WHERE id = 1;(我们称之为A)和WHERE id IN (1);(我们称之为B),后者速度较慢。如果我在执行 A 时对您的理解正确,则与执行 B 相同,但是在执行 B 时,可以在执行中使用不同的计划(有利于大 ID 列表的计划)(换句话说,B 不等于 A )。我已经执行了 A 和 B 并且它们生成了相同的计划,也许 Hibernate 正在对它做些什么?
【解决方案2】:

如果这里的问题真的归结为“如何尽快删除大量记录?”那么 DELETE ... IN() 方法将优于删除每个单独的行,因此追查 IN(?) 与单个成员的原因似乎比 =?不会帮你的。

可能值得探索使用临时表来保存您要删除的所有 id,然后运行单个删除。

如果成本不是太高,将列表中的 id 按升序排列可能有助于实现非常大的删除性能。如果您必须对它们进行排序,请不要打扰,但如果有办法确保每批删除地址 id 聚集在索引的同一区域中可能会稍微有益。

无论如何,在我看来,在这两种情况下都使用了索引并且生成了相同的计划,所以我想知道这里是否真的存在查询解析和优化问题,而不是删除操作本身的问题。我对内部结构了解得不够多,因此我很害怕。

【讨论】:

    猜你喜欢
    • 2010-12-18
    • 1970-01-01
    • 1970-01-01
    • 2014-12-24
    • 1970-01-01
    • 1970-01-01
    • 2023-03-08
    • 2019-02-28
    • 2019-08-09
    相关资源
    最近更新 更多