Содержание

Предыдущий раздел

Вредные советы для программистов

Следующий раздел

Константы в приложении

Глобальные переменные для сессий и транзакций

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

Примером подобной информации может быть идентификатор пользователя, от имени которого выполняется хранимая процедура. Так так в трехуровневых приложениях к серверу базы данных подключается не клиентское приложение, а сервер приложений, то переменная SESSION_USER не несет в себе информации об имени пользователе, от имени которого выполняется запрос. В подобной ситуации, если вся бизнес-логика располагается на уровне хранимых процедур, то сервер приложений передает в запросе к базе данных идентификатор пользователя (id). По идентификатору пользователя хранимые процедуры проводят авторизацию пользователя, логируют его действия и делают прочие сервисные операции.

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

Получается примерно такая картина - из процедуры в процедуры бесконечно передается идентификатор пользователя

../_images/session_variable_1.png

В PostgreSQL есть возможность хранить любые значение в пределах одной сессии или транзакции

Примечание

До версии 9.2 см. http://www.postgresql.org/docs/9.1/static/runtime-config-custom.html

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

Для того, чтобы создать глобальную переменную необходимо задать её имя с точкой, где до точки будет имя модуля, после точки - имя переменной модуля (см. документацию)

Можно задавать значения по умолчанию по файле postgresql.conf.

Функции для работы с глобальными настройками

current_setting( setting_name ) Возвращает текущее значение настройки/переменной

set_config( setting_name, new_value, is_local ) Устанавливает значение переменной. Если is_local = True, то значение устанавливается только для текущей транзакции, иначе - для всей сессии.

Установка настроек подчиняется правилам транзакций, если транзакция была отменена, то и установленное в этой транзакции значение переменной, не будет сохранено.

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

../_images/session_variable_2.png

Пример простой установки и получения значения переменной для сессии

denis=# SELECT set_config('myapp.var1', '1', False); -- Установка переменной
 set_config
------------
 1
(1 row)


denis=# SELECT current_setting('myapp.var1'); -- Чтение переменной
 current_setting
-----------------
 1
(1 row)


denis=# SELECT set_config('myapp.var1', '2', False); -- Установка переменной
 set_config
------------
 2
(1 row)


denis=# SELECT current_setting('myapp.var1'); -- Чтение нового значение переменной
 current_setting
-----------------
 2
(1 row)

Пример установки значения переменной для транзакции (начало см. выше)

denis=# SELECT set_config('myapp.var1', '3', True); -- Установка значения переменной для текущей транзакции
 set_config
------------
 3
(1 row)

denis=# SELECT current_setting('myapp.var1'); -- В переменной хранится старое значение, так как включен autocommit и транзакция действовала в только рамках предыдущего запроса
 current_setting
-----------------
 2
(1 row)

Что происходит с транзакционной переменной после завершения транзакции

denis=# BEGIN; -- Начало транзакции
BEGIN

denis=# SELECT set_config('myapp.var1', '3', True); -- Установка значения, которое действует только в рамках транзакции
 set_config
------------
 3
(1 row)

denis=# SELECT current_setting('myapp.var1'); -- Значение сохранилось
 current_setting
-----------------
 3
(1 row)

denis=# COMMIT; -- Завершаю транзакцию
COMMIT

denis=# SELECT current_setting('myapp.var1');  -- Установленной внутри транзакции значение переменной не сохранилось. Что и ожидалось
 current_setting
-----------------
 2
(1 row)

Что происходит с сессионной переменной после успешного завершения транзакции

denis=# BEGIN; -- Начало транзакции
BEGIN

denis=# SELECT set_config('myapp.var1', '3', False); -- Установка сессионной переменной
 set_config
------------
 3
(1 row)

denis=# SELECT set_config('myapp.var1', '4', False); -- Установка  другой сессионной переменной
 set_config
------------
 4
(1 row)

denis=# SELECT current_setting('myapp.var1');
 current_setting
-----------------
 4
(1 row)

denis=# COMMIT; -- Завершение транзакции
COMMIT

denis=# SELECT current_setting('myapp.var1');  -- Новое значение переменной сохранилось
 current_setting
-----------------
 4
(1 row)

Что происходит с сессионной переменной после отмены транзакции

denis=# BEGIN; -- Начало транзакции
BEGIN

denis=# SELECT set_config('myapp.var1', '5', False); -- Установка нового значения переменной
 set_config
------------
 5
(1 row)

denis=# SELECT current_setting('myapp.var1');
 current_setting
-----------------
 5
(1 row)

denis=# ROLLBACK; -- Откат транзакции
ROLLBACK

denis=# SELECT current_setting('myapp.var1');  -- Новое значение переменной не сохранилось
 current_setting
-----------------
 4
(1 row)

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

Пример из реального приложения. Включение логирования

В postgresql.conf установлено значение переменной по умолчанию

myapp.debugging = False

Есть функция, которая возвращает значение этой переменной/настройки

CREATE OR REPLACE FUNCTION is_debugging() RETURNS boolean AS
$BODY$
BEGIN
   RETURN current_setting('myapp.debugging')::boolean;
END
$BODY$
LANGUAGE plpgsql VOLATILE;

Функция, которая каким-либо способом анализирует значение этой переменной

CREATE OR REPLACE FUNCTION test_debugging() RETURNS void AS
$BODY$
BEGIN
  IF is_debugging() THEN
    RAISE NOTICE 'is_debugging: True';
  ELSE
    RAISE NOTICE 'is_debugging: False';
  END IF;
  -- OR RAISE NOTICE 'is_debugging: %', is_debugging();
END
$BODY$
LANGUAGE plpgsql;
denis=# SELECT is_debugging();
 is_debugging
--------------
 t
(1 row)

denis=# select test_debugging();
NOTICE:  is_debugging: True
 test_debugging
----------------
(1 row)

denis=# SELECT set_config('myapp.debugging', 'False', False);
 set_config
------------
 False
(1 row)

denis=# select test_debugging();
NOTICE:  is_debugging: False
 test_debugging
----------------
(1 row)

Если хочется оптимизировать вызов is_debugging, надо сменить тип функции с VOLATILE на IMMUTABLE. В этом случае результат первого выполнения функции кешируется и при последующих вызовах результат выполнения будет взят из кеша, даже если значение переменной изменится в рамках текущей сессии. Подробнее об оптимизации читайте статью "Константы в приложении".

comments powered by Disqus