2013-03-14 122 views
10

編輯:基於我的一些調試和日誌記錄,我認爲這個問題歸結爲爲什麼DELETE FROM table WHERE id = xDELETE FROM table WHERE id IN (x)快得多,其中x只是一個單一的ID。Hibernate批量刪除vs單一刪除

我最近測試了批量刪除與刪除每行一個接一個,發現批量刪除是慢得多。該表具有刪除,更新和插入的觸發器,但我已經測試過,沒有觸發器,每次批量刪除都比較慢。 任何人都可以闡明爲什麼這是這種情況或分享提示如何我可以調試呢?根據我的理解,我不能真正減少觸發器激活的次數,但我最初認爲降低「刪除」查詢的數量將有助於提高性能。

我已經包括下面的一些信息,請讓我知道如果我漏掉了任何相關信息。

刪除在10000批次,代碼看起來像做:

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); 

表有兩個指標是不用於任何刪除。不會發生級聯操作。

下面是一個樣本的EXPLAIN分析DELETE FROM table where id IN (1, 2, 3);

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 

我吸塵,每次我重裝我的數據進行測試的時間重新索引和我的測試數據中包含386660行。

測試是刪除所有行,我沒有使用TRUNCATE,因爲通常有一個選擇標準,但出於測試目的,我已經使標準包括所有行。啓用觸發器後,逐行刪除每行193,616ms,批量刪除耗時285,558ms。然後我禁用了觸發器,得到93,793ms的單行刪除和181,537ms的批量刪除。觸發器會彙總值並更新另一個表格 - 基本上是簿記。

我打得四處低批量(100和1),它們似乎都表現較差。

編輯:原來Hibernate的日誌記錄和單行的行刪除,它基本上做:delete from table where id=?和EXPLAIN分析一下:

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 

編輯:很好奇,如果該列表實際上包含萬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 

編輯:基於以上的EXPLAIN ANALYZE,我從實際的刪除操作中檢索了一些日誌記錄。以下是逐行刪除的兩個變體的日誌記錄。

下面是一些單個刪除:

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=? 

這裏是單刪除的其他變化(列表中僅有1項)

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存在於該表,並應是連續的。


EXPLAIN分析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 

EXPLAIN分析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) 
.... 
+0

看來,當你刪除單行它使用其他索引。你能檢查它嗎? – ntalbs 2013-03-14 00:51:01

+0

我已經添加了關於單行逐行刪除的信息。表格的MAX(id)是3774904,所以我選擇了一個更大的數字。我嘗試使用低於3774904的ID,但不在表中,得到了類似的結果(表中的ID類似)。 – nevets1219 2013-03-14 17:23:31

+1

是的,0.162對0.452 *是顯着的,因爲0.452幾乎是0.162的3倍。特別有趣的是,解釋表明他們正在做同樣的事情。我會用postgresql團隊提出這個問題。 [pgsql-general](http://www.postgresql.org/community/lists/)可能是個好地方 – 2013-03-20 09:43:18

回答

5

好吧,你必須首先要注意的事情是,SQL必須被轉換成一個計劃某種程度上來說。您的EXPLAIN結果表明,與IN(vals)結構相比,這裏的邏輯與基本相同。

WHERE id = 1; 

被轉換爲簡單的相等過濾器。

WHERE id IN (1); 

被變換成的陣列匹配:

WHERE id = ANY(ARRAY[1]); 

顯然規劃器是不夠的智能,以通知這些是數學上相同,其中一個陣列具有正好一個成員。所以它在做什麼是規劃一個任何大小的數組,這就是爲什麼你得到嵌套循環位圖索引掃描。

這裏有趣的不僅僅是速度較慢,而且大多數情況下性能更好。因此,in()子句中的一個成員的速度是40倍,並且有10000個成員時,速度只有170倍,但這也意味着10000個成員版本的速度也比單獨索引掃描的10000個成員速度快了在身份證上。

因此,這裏發生的事情是,計劃者正在選擇一個計劃,當有大量的id被選中時表現更好,但只有少數時候表現更差。

+0

我會嘗試增加數組大小並報告發生的情況!是否有選擇批量大小的一般準則? – nevets1219 2013-03-20 16:58:31

+0

似乎Hibernate不喜歡50,000(給堆棧溢出錯誤) – nevets1219 2013-03-21 22:05:09

+0

重新讀你寫的東西 - 我試了兩個'WHERE id = 1;'(我們稱之爲A)和'WHERE id IN(1);' (我們稱之爲B),後者較慢。如果我在執行A時正確理解你,它與執行B相同,但是在執行B時,可以執行一個不同的計劃(一個支持大量ID的列表)(換句話說,B不等於A )。我已經執行了A和B,並且他們生成了相同的計劃,也許Hibernate正在做些什麼? – nevets1219 2013-03-21 23:28:10

4

如果這裏的問題真的歸結爲「如何儘快刪除大量記錄?」那麼DELETE ... IN()方法將優於每個單獨行的刪除,所以追求單個成員的IN(?)似乎比=慢的原因。不會幫助你。

可能值得探索使用臨時表來保存要刪除的所有ID,然後運行一次刪除。

如果代價不是太高,那麼安排該列表中的id按升序排列可能對非常大的刪除性能有所幫助。如果您必須對它們進行排序,請不要打擾,但是如果有一種方法可以確保每批刪除地址id都集中在索引的相同區域中,那麼可能會略微有益。

在任何情況下,它看起來像索引正在使用,並在這兩種情況下生成相同的計劃,所以我不知道是否實際上有一個查詢解析和優化問題,而不是刪除操作本身的問題。我對內部的信息不夠了解,以免擔心。