EDIT: на основании некоторых моих отладок и протоколирования я думаю, что вопрос сводится к тому, что DELETE FROM table WHERE id = x
намного быстрее, чем DELETE FROM table WHERE id IN (x)
, где x
- всего лишь один идентификатор.
Недавно я тестировал пакетное удаление по сравнению с удалением каждой строки один за другим и заметил, что пакетное удаление было намного медленнее. В таблице были триггеры для удаления, обновления и вставки, но я тестировал их с триггерами и без них, и каждый раз, когда пакетное удаление было медленнее. Может ли кто-нибудь пролить свет на то, почему это так, или поделиться советами о том, как я могу отладить это? Из того, что я понимаю, я не могу реально уменьшить количество срабатываний триггера, но я изначально что уменьшение количества запросов "удалить" поможет в производительности.
Я включил некоторую информацию ниже, пожалуйста, дайте мне знать, если я упустил что-то важное.
Удаление производится партиями по 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);
В таблице есть два индекса, которые не используются ни в одном из удалений. Никакой каскадной операции не будет.
Вот пример ANPLYIN 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
Я пылесосил и переиндексировал каждый раз, когда я перезагружал свои данные для тестирования, и мои тестовые данные содержат 386 660 строк.
Тест состоит в том, чтобы удалить все строки, и я не использую TRUNCATE
, потому что обычно есть критерии выбора, но для целей тестирования я сделал критерии, включающие все строки. С включенными триггерами удаление каждой строки по одному занимает 193,616 мс, тогда как пакетное удаление занимает 285 558 мс. Затем я отключил триггеры и получил 93 793 мс для удаления одной строки и 181 537 мс для пакетного удаления. Триггер идет и суммирует значения и обновляет другую таблицу - в основном бухгалтерию.
Я играл с меньшими размерами партии (100 и 1), и все они выглядят хуже.
EDIT: Включено ведение журнала 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 идентификаторов, если 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
EDIT: на основе 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 (?)
Оба являются идентификаторами, которые существуют в таблице и должны быть последовательными.
ОБЪЯСНЕНИЕ АНАЛИЗА 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 ANALYZE 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 против 0,452 считали значительную разницу?
EDIT:
Установите размер партии до 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)
....