Избыточные данные в операторах обновления
Спящий генерирует UPDATE
операторы, которые включают все столбцы, независимо от того, меняю ли я значение в этих столбцах, например:
tx.begin();
Item i = em.find(Item.class, 12345);
i.setA("a-value");
tx.commit();
выдает это UPDATE
заявление:
update Item set A = $1, B = $2, C = $3, D = $4 where id = $5
поэтому столбцы B, C, D обновляются, а я их не менял.
Скажем, элементы часто обновляются, и все столбцы индексируются. Вопрос заключается в следующем: имеет ли смысл оптимизировать часть Hibernate примерно так:
tx.begin();
em.createQuery("update Item i set i.a = :a where i.id = :id")
.setParameter("a", "a-value")
.setParameter("id", 12345)
.executeUpdate();
tx.commit();
Больше всего меня смущает то, что EXPLAIN
Планы "неоптимизированной" и "оптимизированной" версии запроса идентичны!
2 ответа
Благодаря PostgreSQL MVCC, UPDATE
эффективно почти как DELETE
плюс INSERT
- с заметным исключением поджаренных значений. Увидеть:
Чтобы быть точным, "удаленная" строка просто невидима для любой транзакции, начинающейся после того, как удаление было совершено, и очищенной позже. Следовательно, на стороне базы данных, включая манипулирование индексами, между этими двумя утверждениями фактически нет различий. (Исключения применяются, продолжайте чтение.) Это немного увеличивает сетевой трафик (в зависимости от ваших данных) и требует небольшого разбора.
После изучения @ araqnid я изучил обновления HOT и провел несколько тестов. Обновления в столбцах, которые на самом деле не изменяют значение, не имеют никакого значения, если речь идет об обновлениях HOT. Мой ответ верен. Подробности смотрите ниже.
Это также относится к атрибутам toasted, так как они также не затрагиваются, если значения не изменяются.
Однако, если вы используете триггеры для каждого столбца (введено в pg 9.0), это может иметь нежелательные побочные эффекты!
Я цитирую руководство по триггерам:
... команда, такая как
UPDATE ... SET x = x ...
сработает триггер на колонкеx
даже если значение столбца не изменилось.
Жирный акцент мой.
Слои абстракции для удобства. Они полезны для неграмотных разработчиков SQL или если приложение должно быть переносимым между различными RDBMS. С другой стороны, они могут снизить производительность и ввести дополнительные точки отказа. Я избегаю их везде, где это возможно.
Относительно обновлений HOT (Heap-only tuple)
Кучи только для кучи были введены в Postgres 8.3, с важными улучшениями в 8.3.4 и 8.4.9.
Примечания к выпуску Postgres 8.3:
UPDATE
с иDELETE
s оставляют мертвые кортежи, как и неудачныеINSERT
s. Только ранееVACUUM
может вернуть пространство, занятое мертвыми кортежами. С HOT мертвое пространство кортежей может быть автоматически восстановлено во времяINSERT
или жеUPDATE
если не внесены изменения в индексированные столбцы. Это учитывает более последовательную работу. Кроме того, HOT избегает добавления повторяющихся записей индекса.
Акцент мой. И "без изменений" включает в себя случаи, когда столбцы обновляются с тем же значением, которое они уже содержат. Я действительно проверял это только сейчас, поскольку я не был уверен.
Поджаренные колонны также не мешают горячим обновлениям. Обновлённый HOT кортеж просто ссылается на один и тот же неизменный кортеж (ы) в тост-форке отношения. Обновления HOT даже работают с всплывающими значениями в списке целей (фактически изменены или нет). Если тостовые значения изменены, это, очевидно, влечет за собой запись в форк отношения тоста. Я это тоже проверял.
Вы не должны поверить на мое слово. Убедитесь сами, Postgres предоставляет несколько функций для проверки статистики. Запустить свой UPDATE
с и без всех столбцов и проверьте, если это имеет какое-либо значение.
-- Number of rows HOT-updated in table:
SELECT pg_stat_get_tuples_hot_updated('table_name'::regclass::oid)
-- Number of rows HOT-updated in table, in the current transaction:
SELECT pg_stat_get_xact_tuples_hot_updated('table_name'::regclass::oid)
Или используйте pgAdmin. Выберите вашу таблицу и проверьте вкладку "Статистика" в главном окне.
Имейте в виду, что обновления HOT возможны только в том случае, если на той же странице основной ветки отношения есть место для новой версии кортежа. Один простой способ форсировать это условие - протестировать с небольшой таблицей, которая содержит всего несколько строк. Размер страницы обычно составляет 8 КБ, поэтому на странице должно быть свободное место.
Вы можете использовать аннотацию hibernate @Entity:
@org.hibernate.annotations.Entity(dynamicUpdate = true)
public class Item
Это обновит только измененные поля.