Обновляет ли строка с тем же значением фактическое обновление строки?

28

У меня есть вопрос, связанный с производительностью. Допустим, у меня есть пользователь с именем Майкл. Возьмите следующий запрос:

UPDATE users
SET first_name = 'Michael'
WHERE users.id = 123

Будет ли запрос фактически выполнять обновление, даже если оно обновляется до того же значения? Если так, как я могу предотвратить это?

OneSneakyMofo
источник
1
Почему вы выполняете оператор и одновременно ожидаете, что он не будет выполнен?
Макс Вернон
ORM @MaxVernon Ruby on Rails не обновляет запись, поэтому мне было любопытно, сделал ли PostgreSQL то же самое.
OneSneakyMofo
1
Я бы предположил, что если Ruby on Rails делает это, он, вероятно, сначала делает выборку, чтобы увидеть, нужно ли строке обновить.
Макс Вернон
опубликовано в SO: stackoverflow.com/q/33156712/939860
Эрвин Брандштеттер

Ответы:

35

Из-за MVCC-модели Postgres и в соответствии с правилами SQL, UPDATEзаписывает новую версию строки для каждой строки, которая не исключена в WHEREпредложении.

Это делает имеют более или менее существенное влияние на производительность, прямо и косвенно. «Пустые обновления» имеют ту же цену за строку, что и любое другое обновление. Они запускают триггеры (если они есть), как и любое другое обновление, они должны быть зарегистрированы в WAL, и они генерируют мертвые строки, раздувающие таблицу и вызывающие дополнительную работу на VACUUMпотом, как и любое другое обновление.

Записи индексов и столбцы TOASTed, в которых не изменен ни один из задействованных столбцов, могут остаться прежними, но это верно для любой обновленной строки. Связанный:

Это почти всегда хорошая идея, чтобы исключить такие пустые обновления (когда есть реальный шанс, что это может произойти). Вы не предоставили определение таблицы в своем вопросе (что всегда является хорошей идеей). Мы должны предположить, что first_nameможет иметь значение NULL (что не удивительно для «имени»), поэтому в запросе необходимо использовать NULL-безопасное сравнение :

UPDATE users
SET    first_name = 'Michael'
WHERE  id = 123
AND   first_name IS DISTINCT FROM 'Michael';

Если first_name IS NULLдо обновления тест с just просто first_name <> 'Michael'оценивается как NULL и как таковая исключает строку из обновления. Подлая ошибка. Если столбец определенNOT NULL , используйте простую проверку на равенство, потому что это немного дешевле.

Связанный:

Эрвин Брандштеттер
источник
1
Indexes entries and TOASTed columns where none of the involved columns are changed can stay the sameНо не нужно ли их обновлять, чтобы они указывали на новое местоположение строки?
28
1
@dtgq: Не с обновлениями HOT, где индекс может продолжать указывать на старое местоположение, а выборки кучи должны проходить через цепочку HOT, чтобы получить живой кортеж. Я добавил ссылки на более подробное объяснение выше.
Эрвин Брандштеттер,
1
Как насчет вызовов MVCC для обновления noop, чтобы написать новый кортеж?
jberryman
@jberryman: Не уверен, что понимаю. В любом случае, пожалуйста, задайте свой вопрос как новый вопрос . Вы всегда можете связаться с этим для контекста. И вы можете оставить комментарий здесь, чтобы дать ссылку (и привлечь мое внимание).
Эрвин Брандштеттер
2
@jberryman: На самом деле я не знаю причин, по которым проект пошел по этому пути. Это было установлено давно. Но я предполагаю, что было бы излишне дорого проверять каждую строку на равенство и иметь отдельный путь к коду для неизмененных строк. Обработка идентификаторов транзакций была бы более сложной - специальный корпус для rollbackобработки снимков, управление блокировками, WAL, а что нет ...
Эрвин Брандштеттер,
4

ORM похож на предложение Ruby on Rail с отложенным исполнением, которое помечает запись как измененную (или нет), а затем, когда это необходимо или вызывается, затем передает изменение в базу данных.

PostgreSQL - это база данных, а не ORM. Это снизило бы производительность, если бы потребовалось время, чтобы проверить, совпадает ли новое значение с обновленным значением в вашем запросе.

Поэтому он будет обновлять значение независимо от того, совпадает ли оно с новым значением или нет.

Если вы хотите предотвратить это, вы можете использовать код, предложенный Максом Верноном в своем ответе.

Thronk
источник
2

Вы можете просто добавить к whereпредложению:

UPDATE users
SET first_name = 'Michael'
WHERE users.id = 123
    AND (first_name <> 'Michael' OR first_name IS NULL);

Если first_nameопределяется как NOT NULL, OR first_name IS NULLчасть может быть удалена.

Условие:

(first_name <> 'Michael' OR first_name IS NULL)

также может быть написано более элегантно, как (в ответе Эрвина):

first_name IS DISTINCT FROM 'Michael'
Макс Вернон
источник
Не зная, может ли столбец быть НЕДЕЙСТВИТЕЛЬНЫМ, это может привести к хитрой ошибке.
Эрвин Брандштеттер
1
@ErwinBrandstetter Я обновлял ответ - тогда я увидел комментарий и ваш ответ!
ypercubeᵀᴹ
спасибо за правку, @ypercube - и за комментарий о NULL@erwin
Максе Верноне
1

С точки зрения базы данных

Ответ на ваш вопрос - ДА. Обновление состоится. База данных не проверяет предыдущее значение, она только устанавливает новое значение.

Поскольку это происходит в памяти (и будет записано в файлы данных только после выдачи коммита), производительность не будет проблемой.

С точки зрения ORM

Обычно у вас будет объект, представляющий одну строку базы данных (это может быть намного сложнее, но давайте будем проще). Этот объект управляется в памяти (на уровне сервера приложений), и только последняя зафиксированная версия этого объекта в определенный момент попадет в базу данных.

Это может объяснить другое поведение.

Теперь давайте не будем сравнивать грузовой корабль с 3D-принтером. Тот факт, что вы можете отправлять 3D-принтеры с использованием грузовых кораблей, не означает, что между ними может быть какое-либо сравнение.

Наслаждайтесь!

Я надеюсь, что это прояснило некоторые понятия.

Silvarion
источник
4
Производительность есть и проблема. Каждое обновление должно быть записано на диск (журнал и таблица).
ypercubeᵀᴹ
Это будет зависеть от фактической СУБД, которую вы используете. Но большинство из них не фиксируют каждое обновление, а только последний зафиксированный блок, который они имеют в памяти. Вы никогда не читаете и не пишете ни одной строки в базе данных. Вы читаете / записываете блоки и храните их в памяти до тех пор, пока вам не придется их очищать, чтобы поместить новый блок в то же место. Находясь в памяти, не каждое изменение в строке будет записываться на диск, а только содержимое блока, когда сигнализируется процессу «записи в базу данных» для выгрузки этого блока памяти в файл данных. Так что, нет ... Это не проблема, если ваше приложение слишком долго не блокирует блок.
Сильварион
1
речь идет о Postgres, а не о какой-либо произвольной СУБД. И хотя обновления не все должны записываться один за другим, каждая запись в базу данных должна записываться в журнал. Если изменение не записано в постоянном хранилище, как СУБД выдержит сбой системы?
ypercubeᵀᴹ
Да, он пишет в журналах, а также из памяти во время контрольных точек. Если у вас не очень большое количество одновременно работающих пользователей, это не должно быть проблемой вообще. Журналы также пишутся партиями. Я думаю, что мы говорим о серверах. Если вы говорите о базе данных Postgres на ноутбуке с жестким диском 5400 об / мин, да ... у вас всегда будут проблемы с производительностью. Итак, окончательный ответ будет первым ... Это зависит от слишком многих вещей.
Сильварион