Когда дело доходит до обновления строки, многие инструменты ORM выдают инструкцию UPDATE, которая устанавливает каждый столбец, связанный с этой конкретной сущностью .
Преимущество заключается в том, что вы можете легко пакетировать операторы обновления, поскольку UPDATE
операторы одинаковы независимо от того, какой атрибут сущности вы меняете. Более того, вы даже можете использовать кэширование операторов на стороне сервера и на стороне клиента.
Итак, если я загружаю сущность и устанавливаю только одно свойство:
Post post = entityManager.find(Post.class, 1L);
post.setScore(12);
Все столбцы будут изменены:
UPDATE post
SET score = 12,
title = 'High-Performance Java Persistence'
WHERE id = 1
Теперь, если предположить, что у нас есть индекс и для title
свойства, не должна ли БД понять, что значение все равно не изменилось?
В этой статье Маркус Винанд говорит:
Обновление во всех столбцах показывает тот же шаблон, который мы уже наблюдали в предыдущих разделах: время отклика увеличивается с каждым дополнительным индексом.
Интересно, почему это происходит из-за того, что база данных загружает связанную страницу данных с диска в память и может выяснить, нужно ли изменить значение столбца или нет.
Даже для индексов это ничего не меняет, поскольку значения индексов для столбцов, которые не изменились, не изменились, но они были включены в ОБНОВЛЕНИЕ.
Нужно ли перемещаться по индексам B + Tree, связанным с избыточными неизмененными столбцами, только для того, чтобы база данных поняла, что значение листа остается прежним?
Конечно, некоторые инструменты ORM позволяют ОБНОВЛЯТЬ только измененные свойства:
UPDATE post
SET score = 12,
WHERE id = 1
Но этот тип UPDATE может не всегда выигрывать от пакетных обновлений или кэширования операторов, когда разные свойства изменяются для разных строк.
источник
UPDATE
практически эквивалентноDELETE
+INSERT
(потому что вы на самом деле создать новый V ersion в ряду). Издержки высоки и растут с увеличением количества индексов , особенно если многие столбцы, которые их содержат, фактически обновлены, а дерево (или что-то еще), используемое для представления индекса, нуждается в значительном изменении. Важно не количество столбцов, которые обновляются, а то, обновляете ли вы часть столбца индекса.Ответы:
Я знаю, что вы в основном озабочены
UPDATE
и в основном производительностью, но как сотрудник службы поддержки «ORM», позвольте мне дать вам еще один взгляд на проблему различия между «измененными» , «нулевыми» и «значениями по умолчанию» , которые три разные вещи в SQL, но, возможно, только одна вещь в Java и в большинстве ORM:Перевод вашего обоснования в
INSERT
заявленияВаши аргументы в пользу пакетности и кэширования операторов верны для
INSERT
операторов так же, как и дляUPDATE
операторов. Но в случаеINSERT
операторов, исключение столбца из оператора имеет другую семантику, чем вUPDATE
. Это значит применятьDEFAULT
. Следующие два семантически эквивалентны:Это не так, поскольку
UPDATE
первые два семантически эквивалентны, а третий имеет совершенно другое значение:Большинство клиентских API баз данных, в том числе JDBC и, как следствие, JPA, не позволяют связывать
DEFAULT
выражение с переменной связывания - в основном потому, что серверы также не допускают этого. Если вы хотите повторно использовать один и тот же оператор SQL по вышеупомянутым причинам пакетности и кеширования операторов, вы должны использовать следующий оператор в обоих случаях (при условии(a, b, c)
, что все столбцы вt
):И поскольку
c
он не установлен, вы, вероятно, связали бы Javanull
с третьей переменной связывания, потому что многие ORM также не могут различать междуNULL
иDEFAULT
( jOOQ , например, здесь исключение). Они видят только Javanull
и не знают, означает ли этоNULL
(как в неизвестном значении) илиDEFAULT
(как в неинициализированном значении).Во многих случаях это различие не имеет значения, но в случае, если ваш столбец c использует любую из следующих функций, утверждение просто неверно :
DEFAULT
пунктВернуться к
UPDATE
заявлениямХотя вышеприведенное верно для всех баз данных, я могу заверить вас, что проблема с триггером верна и для базы данных Oracle. Рассмотрим следующий SQL:
Когда вы запустите выше, вы увидите следующий вывод:
Как видите, оператор, который всегда обновляет все столбцы, всегда будет запускать триггер для всех столбцов, тогда как оператор, обновляющий только измененные столбцы, будет запускать только те триггеры, которые прослушивают такие конкретные изменения.
Другими словами:
Текущее поведение Hibernate, которое вы описываете, является неполным и может даже рассматриваться как неправильное в присутствии триггеров (и, возможно, других инструментов).
Я лично думаю, что ваш аргумент оптимизации кэша запросов переоценен в случае динамического SQL. Конечно, в таком кеше будет еще несколько запросов и немного больше работы по анализу, но обычно это не проблема для динамических
UPDATE
операторов, гораздо меньше, чем дляSELECT
.Пакетирование, безусловно, является проблемой, но, по моему мнению, не следует нормализовать одно обновление, чтобы обновить все столбцы только потому, что существует небольшая вероятность того, что оператор может быть пакетным. Скорее всего, ORM может собирать подпакеты последовательных идентичных операторов и группировать их вместо «целого пакета» (в случае, если ORM даже способен отслеживать разницу между «измененным» , «нулевым» и «стандартным»)
источник
DEFAULT
использования может быть адресован@DynamicInsert
. Ситуация TRIGGER также может быть решена с помощью проверок типаWHEN (NEW.b <> OLD.b)
или просто переключиться на@DynamicUpdate
.Я думаю, что ответ - это сложно . Я попытался написать быстрое доказательство, используя
longtext
колонку в MySQL, но ответ немного неубедителен. Доказательство первое:Таким образом, существует небольшая разница во времени между медленным + измененным значением и медленным + без изменения значения. Поэтому я решил взглянуть на другую метрику, на которой были написаны страницы:
Таким образом, похоже, что время увеличилось, потому что должно быть сравнение, чтобы подтвердить, что само значение не было изменено, что в случае длинного текста 1G занимает время (потому что оно разбито на много страниц). Но сама модификация, похоже, не откатывается через журнал повторов.
Я подозреваю, что если значения являются обычными столбцами, которые находятся внутри страницы, сравнение добавляет лишь немного накладных расходов. И если предположить, что применяется та же оптимизация, то они не работают, когда дело доходит до обновления.
Более длинный ответ
Я на самом деле думаю, что ORM не должен исключать столбцы, которые были изменены ( но не изменены ), так как эта оптимизация имеет странные побочные эффекты.
Рассмотрим следующее в псевдокоде:
Результат, если ORM «оптимизировать» модификацию без изменений:
Результат, если ORM отправил все модификации на сервер:
Тест-кейс здесь опирается на
repeatable-read
изоляция (по умолчанию MySQL), но также существует временное окно дляread-committed
изоляции, когда чтение сессии2 происходит до фиксации сессии1.Другими словами: оптимизация безопасна только в том случае, если вы запускаете a
SELECT .. FOR UPDATE
для чтения строк, за которыми следуетUPDATE
.SELECT .. FOR UPDATE
не использует MVCC и всегда читает последнюю версию строк.Редактировать: Убедитесь, что набор данных теста был 100% в памяти. Скорректированы сроки результатов.
источник