Зачем разработчику использовать stable-функции, в чём их преимущества?
Stable-функции предназначены для чтения согласованных данных, операции изменения данных в них запрещены. Однако внутри можно вызывать volatile-функции, на которых это ограничение не распространяется.
Помимо ограничений stable-функции обладают одной особенностью, которую sql-разработчики обязаны знать - запросы, выполненные непосредственно внутри этих функции, "видят" только данные, которые были зафиксированы (в том числи параллельными транзакциями) на момент вызова stable-функции. Получается, что в момент запуска stable-функции PostgreSQL как бы замораживает БД для этой функции и все запросы к базе данных происходят на одних и тех же данных. Эта особенность stable-функции очень полезна для отчетов и прочих функции, которые агрегируют данные нескольких таблиц и важно, чтобы целостность этих данных не была нарушена.
Перед выполнением любого выражения plpgsql-интерпретатор анализирует, в контексте какой функции происходит выполнение выражения. Если выполнение происходит внутри volatile-функции, то обновляется информация о текущем снапшоте и выполнение выражения происходит с учетом всех зафиксированных на момент выполнения выражения транзакций. Если же выполнение выражения происходит внутри stable-функции, то используется снапшот, который был сделан на момент запуска самой функции.
Алгоритм можно представлен в виде псевдокода:
for expression in expression_list: # список выполняемых выражений внутри хранимой процедуры
if not context.readonly: # проверка, в контексте какого типа процедуры выполняется выражение
snapshot = get_current_snapshot() # Вычисление текущего снапшота
snapshot_stack.push(snapshot) # Текущий снапшот кладется на стек и становится активным (стек используется для поддержки иерархических вызовов и ...)
execute(expression) # Выполняется выражение
if not context.readonly:
snapshot_stack.pop() # Возвращем текущий снапшот на место
Из приведенного псевдокода можно даже предположить, что выполнение stable-функций происходит немного быстрее volatile-функции за счет отсутствия необходимости вычисления текущего снапшота перед выполнение каждого выражения.
Ниже рассмотрены 4 варианта вложенных вызовов хранимых процедур.
Данный вариант является самым простым - при выполнении любых выражений происходит вычисление активного снапшота
На момент выполнения stable_func происходит вычисление активного снапшота, который и используется для всех выражений внутри stable-функции
На момент выполнения родительской stable-функции происходит вычисление активного снапшота. А так как вызов вложенной stable-функции происходит в readonly-контексте, то вычисление активного снапшота для вложенной stable-функции не происходит и она наследует снапшот родительской функции
Предупреждение
Этот вариант самый коварный. В момент запуска volatile-функция наследует снапшот родительской stable-функции, но для всех выражений внутри volatile-функции вычисляется активный снапшот. В момент возврата управления в stable-функцию снапшот, действующий на момент запуска stable-функции, восстанавливается. Поэтому родительская stable-функция никогда не увидит изменений, сделанных вложенной volatile-функцией.