В PostgreSQL UPDATE обращается к той же таблице через подзапрос: видит ли подзапрос изменения, выполненные ...

В PostgreSQL UPDATE обращается к той же таблице через подзапрос: видит ли подзапрос изменения, выполненные этим оператором?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Нет. В обычном PostgreSQL подзапрос внутри одного UPDATE видит согласованное состояние таблицы на момент начала оператора, а не промежуточные изменения, сделанные этим же UPDATE. Поэтому условия и вычисления через подзапрос обычно основаны на исходных значениях строк.

UPDATE employees AS e SET salary = salary * 1.1 WHERE salary < (SELECT AVG(salary) FROM employees);

Средняя зарплата вычисляется по исходному состоянию таблицы. Повышение зарплаты одной строки не меняет результат подзапроса для других строк в рамках того же оператора.

Исторический контекст

Такое поведение связано с моделью согласованного снимка данных и атомарностью SQL-оператора. Она решает проблему, при которой результат одного и того же условия зависел бы от того, какие строки UPDATE уже успел обработать.

Без единого снимка массовое обновление могло бы последовательно менять собственные критерии отбора. Это усложнило бы оптимизацию и сделало результат зависимым от физического порядка обработки строк.

Постановка проблемы

Подзапрос к изменяемой таблице часто используют для расчёта порога, проверки существования или поиска связанных данных. Ошибочное предположение о том, что подзапрос видит уже обновлённые строки, может привести к неправильному фильтру и неверным итоговым значениям.

Особенно опасна попытка реализовать каскадные изменения одним UPDATE, ожидая, что результат изменения одной строки повлияет на обработку следующей. Для таких задач нужны отдельные операторы, рекурсивный запрос или явно спроектированная процедура.

Подробное решение

В PostgreSQL каждый оператор работает с согласованным снимком. Подзапрос читает строки, видимые в этом снимке, тогда как UPDATE формирует новые версии затронутых строк; эти новые версии не становятся обычным результатом чтения для подзапроса того же оператора.

Это не означает, что UPDATE сначала полностью вычисляет все новые значения в некотором гарантированном порядке. План выполнения может быть разным, но логический результат не должен зависеть от порядка обработки строк.

Если требуется передать данные между действиями, применяют отдельные операторы в транзакции или модифицирующий CTE с RETURNING. При этом модифицирующие действия одного оператора используют общий снимок, а передача результатов выполняется через возвращённые строки, а не через повторное чтение изменённой таблицы.

Конкурентные изменения — отдельная тема. При уровне изоляции READ COMMITTED разные SQL-операторы одной транзакции могут получить разные снимки, а при более строгом уровне изоляции правила видимости отличаются. Поэтому вывод относится к чтению внутри одного обычного UPDATE, а не ко всем взаимодействиям между транзакциями.

Ситуация из практики

Нужно повысить на десять процентов зарплату всем сотрудникам, чья зарплата ниже средней. Вариант с подзапросом вычисляет среднее один раз относительно согласованного состояния и не превращает порог отбора в движущуюся величину.

Можно сначала получить среднюю зарплату отдельным SELECT, а затем передать её в UPDATE. Это делает этапы явными, но требует дополнительной логики и может создать разрыв между чтением среднего и обновлением, если они выполняются не в одной транзакции.

Можно обновлять строки по одной в цикле процедуры, каждый раз пересчитывая среднее. Такой вариант отражает пошаговую бизнес-логику, но медленнее, сложнее для анализа и потенциально даёт другой результат из-за порядка обработки.

Для массового изменения выбран один UPDATE с подзапросом. Он атомарен, работает на согласованном снимке и не зависит от порядка обхода строк.

Что кандидаты часто упускают

  1. Изменится ли результат, если подзапрос читает таблицу, являющуюся целью UPDATE?

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

  2. Можно ли использовать модифицирующий CTE, чтобы следующий подзапрос увидел обновлённые строки обычным SELECT?

    Нет, модифицирующие CTE выполняются с одним снимком, поэтому их изменения не становятся видимыми через повторное чтение той же таблицы в этом операторе. Обмениваться результатами нужно через RETURNING и ссылку на результат CTE; это не то же самое, что повторно читать изменённую таблицу.

  3. Почему нельзя полагаться на порядок обновления строк для построения цепочки изменений?

    SQL не обещает пользователю физический порядок обработки строк. Оптимизатор может выбрать другой план, использовать индекс или выполнять операции параллельно, если это разрешено. Если результат должен зависеть от последовательности, её нужно выразить явно через отдельные шаги, сортировку промежуточных данных или специализированную алгоритмическую логику.