Управление последовательностью
Управление последовательностью решает задачу согласованности данных в процессе регистрации их изменений и последующей доставки, обмена данными, между узлами интеграции по принципу FIFO. Механизм управления последовательностью основан на понятии “векторных часов”. Платформа 1С:Предприятие 8 не имеет адекватных средств для решения этой задачи. Особенно это касается высоко нагруженных и конкурентных условий эксплуатации. Однако, на уровне СУБД этот механизм может быть реализован при помощи специальных объектов ядра - генераторов последовательностей (SEQUENCE).
Важно! Данная технология гарантирует последовательсность согласно очерёдности выполнения команд INSERT при записи в соответствующую таблицу СУБД без учёта последовательности завершения транзакций, в которых они участвуют. Таким образом последовательность гарантируется в разрезе уникальных ключей таблицы СУБД, например, кластерного индекса. Данная технология хорошо подходит для объектного обмена данными или их изменениями, характерного, например, для платформы 1С:Предприятие 8.
На заметку: если требуется обмен данными, основанный на последовательности именно завершённых транзакций (репликация транзакций), то следует использовать технологию CDC (change data capture), которая имеет доступ к LSN (log sequence number) журнала транзакций соответствующей СУБД.
Для управления последовательностью DaJet Script реализует набор команд и функций, которые позволяют работать с объектами SEQUENCE ядра СУБД: создавать и удалять, применять и отзывать для соответствующих объектов 1С:Предприятие 8, а также получать следующее значение последовательности в запросах.
На уровне СУБД счётчик последовательности имеет тип данных
bigint
.
CREATE SEQUENCE
Команда для создания именованного объекта последовательности базы данных в случае его отсутствия.
CREATE SEQUENCE <identifier>
<identifier> - имя объекта последовательности.
Имя нового объекта последовательности указывается без одинарных кавычек.
Создание последовательности so_my_sequence
USE 'mssql://server/database'
CREATE SEQUENCE so_my_sequence
END
На уровне СУБД выполняется следующий код:
-- SQL Server
IF NOT EXISTS(SELECT 1 FROM sys.sequences WHERE name = 'so_my_sequence')
BEGIN
CREATE SEQUENCE my_sequence AS bigint START WITH 1 INCREMENT BY 1;
END;
-- PostgreSQL
CREATE SEQUENCE IF NOT EXISTS so_my_sequence AS bigint INCREMENT BY 1 START WITH 1 CACHE 1;
DROP SEQUENCE
Команда для удаления именованного объекта последовательности базы данных. В случае его отсутствия генерируется ошибка СУБД.
DROP SEQUENCE <identifier>
<identifier> - имя объекта последовательности.
Имя удаляемого объекта последовательности указывается без одинарных кавычек.
Удаление последовательности so_my_sequence
USE 'mssql://server/database'
TRY
DROP SEQUENCE so_my_sequence
CATCH
PRINT ERROR_MESSAGE()
END -- TRY
END -- USE
На уровне СУБД выполняется следующий код:
-- SQL Server
DROP SEQUENCE so_my_sequence;
-- PostgreSQL
DROP SEQUENCE so_my_sequence;
APPLY SEQUENCE
Команда применяет ранее созданную последовательность к указанному измерению, например, регистра сведений, который используется в качестве очереди исходящих сообщений. Работа команды абсолютно прозрачна для 1С:Предприятие 8. Это означает, что код прикладного решения, выполняющего запись в регистр сведений, не меняется. Приращение значений соответствующего измерения будет осуществляться автоматически при добавлении новых записей в коде 1С:Предприятие 8.
Совет: используйте для измерения регистра сведений тип данных ЧИСЛО(15,0).
Команда имеет необязательную опцию RECALCULATE. Эта опция позволяет выполнить пересчёт значений указанного измерения для существующих на данный момент в регистре сведений записей по порядку, начиная с текущего значения последовательности. Оригинальный порядок записей регистра при этом сохраняется таким же, какой он был до начала выполнения команды.
Команда имеет следующий синтаксис:
APPLY SEQUENCE <identifier> ON <table>(<column>) [RECALCULATE]
<identifier> - имя объекта последовательности СУБД.
<table> - полное имя объекта метаданных 1С:Предприятие 8.
<column> - имя реквизита объекта метаданных 1С:Предприятие 8.
Рассмотрим использование команды APPLY SEQUENCE на практическом примере. Допустим, что у нас есть регистр сведений “ИсходящиеСообщения” для регистрации изменений и дальнейшей их передачи во внешние узлы интеграции, имеющий следующую структуру:
Реквизит | Назначение | Тип данных | Описание |
---|---|---|---|
НомерСообщения | Измерение | ЧИСЛО(15,0) | Автоматически генерируемый СУБД последовательный номер сообщения |
ТипСообщения | Ресурс | СТРОКА(1024) | Имя типа сообщения |
ТелоСообщения | Ресурс | СТРОКА(0) | Тело сообщения в строковом формате, например, JSON или XML |
Теперь выполним следующий код 1С:Предприятие 8:
В результате выполнения в пустом регистре будет создана одна запись и получена следующая ошибка:
Теперь выполним следующий код DaJet Script:
USE 'mssql://server/database'
CREATE SEQUENCE so_my_sequence -- На всякий случай, если объект последовательности отсутствует
TRY
APPLY SEQUENCE so_my_sequence ON РегистрСведений.ИсходящиеСообщения(НомерСообщения)
CATCH
PRINT ERROR_MESSAGE()
END
END
Заново выполним тот же самый код 1С:Предприятие 8 (на этот раз без ошибок), предварительно изменив уже записанное значение измерения “НомерСообщения” в регистре на любое другое отличное от нуля значение. Получим следующий результат:
На уровне СУБД команда APPLY SEQUENCE выполняет следующий код:
-- SQL Server
IF OBJECT_ID('_inforg134_instead_of_insert', 'TR') IS NULL
EXECUTE('CREATE TRIGGER _inforg134_instead_of_insert ON _InfoRg134 INSTEAD OF INSERT NOT FOR REPLICATION AS
INSERT _InfoRg134(_Fld135, _Fld136, _Fld137)
SELECT NEXT VALUE FOR so_my_sequence, i._Fld136, i._Fld137
FROM INSERTED AS i;');
-- PostgreSQL
CREATE FUNCTION fn_inforg98_before_insert()
RETURNS trigger AS $BODY$
BEGIN
NEW._fld99 := nextval('so_my_sequence');
RETURN NEW;
END $BODY$ LANGUAGE 'plpgsql';
CREATE TRIGGER tr_inforg98_before_insert
BEFORE INSERT ON _inforg98 FOR EACH ROW
EXECUTE PROCEDURE fn_inforg98_before_insert();
В случае использования опции RECALCULATE команды APPLY SEQUENCE дополнительно выполняется следующий код:
-- SQL Server
BEGIN TRANSACTION;
SELECT _Fld135, NEXT VALUE FOR so_my_sequence OVER (ORDER BY _Fld135 ASC) AS sequence_value
INTO #COPY_InfoRg134 FROM _InfoRg134 WITH (TABLOCKX, HOLDLOCK);
UPDATE T SET T._Fld135 = S.sequence_value FROM _InfoRg134 AS T
INNER JOIN #COPY_InfoRg134 AS S ON T._Fld135 = S._Fld135;
DROP TABLE #COPY_InfoRg134;
COMMIT TRANSACTION;
-- PostgreSQL
BEGIN TRANSACTION;
LOCK TABLE _inforg98 IN ACCESS EXCLUSIVE MODE;
WITH cte AS (SELECT _fld99, nextval('so_my_sequence') AS sequence_value
FROM _inforg98 ORDER BY _fld99 ASC)
UPDATE _inforg98 SET _fld99 = cte.sequence_value FROM cte
WHERE _inforg98._fld99 = cte._fld99;
COMMIT TRANSACTION;
REVOKE SEQUENCE
Команда отменяет действие соответствующей команды APPLY SEQUENCE. После её успешного выполнения автоматическая генерация значений для указанного ранее измерения регистра сведений 1С:Предприятие 8 прекращается. Объект последовательности не удаляется - для этого следует использовать команду DROP SEQUENCE. Команда имеет следующий синтаксис:
REVOKE SEQUENCE <identifier> ON <table>
<identifier> - имя объекта последовательности СУБД.
<table> - полное имя объекта метаданных 1С:Предприятие 8.
Пример использования команды REVOKE SEQUENCE:
USE 'mssql://server/database'
TRY
REVOKE SEQUENCE so_my_sequence ON РегистрСведений.ИсходящиеСообщения
CATCH
PRINT ERROR_MESSAGE()
END
END
На уровне СУБД команда REVOKE SEQUENCE выполняет следующий код:
-- SQL Server
IF OBJECT_ID('_inforg134_instead_of_insert', 'TR') IS NOT NULL
DROP TRIGGER _inforg134_instead_of_insert;
-- PostgreSQL
DROP FUNCTION IF EXISTS fn_inforg98_before_insert CASCADE;
DROP TRIGGER IF EXISTS tr_inforg98_before_insert ON _inforg98;
Функция VECTOR
Функция VECTOR возвращает следующее значение счётчика именованной последовательности. Гарантированно, что одно и тоже значение счётчика не может быть получено при повторном вызове этой функции, а также при одновременном вызове параллельно в разных потоках выполнения. Функция VECTOR всегда выполняется в контексте соответствующей базы данных, то есть требует использования “внутри” команды USE. Вызов функции имеет следующий синтаксис:
VECTOR('<sequence_name>')
<sequence_name> - имя объекта последовательности. Имя заключено в одинарные кавычки - это строковой литерал, константа.
1. Обращение к функции VECTOR вне контекста СУБД генерирует ошибку:
DECLARE @current number
SET @current = SELECT VECTOR('so_my_sequence')
-- Результат выполнения скрипта (ошибка):
-- Parent UseStatement is not found
2. Указание несуществующей последовательности генерирует ошибку:
DECLARE @current number
USE 'mssql://server/database'
SET @current = SELECT VECTOR('so_my_sequence')
END
-- Результат выполнения скрипта (ошибка):
-- SQL Server: Invalid object name 'so_my_sequence'
-- PostgreSQL: relation "so_my_sequence" does not exist
3. Пример целевого использования функции VECTOR (без ошибок):
DECLARE @next number
DECLARE @current number
USE 'mssql://server/database'
-- 1. Вариант использования, @current = 0
SET @current = SELECT VECTOR('so_my_sequence')
-- 2. Вариант использования
SELECT VECTOR('so_my_sequence') INTO @current
-- Два вызова функции: @current = 2, @next = 0
SET @next = @current + 1
-- Фиксируем значения в логе программы
PRINT 'Значения so_my_sequence:'
PRINT '- текущее = ' + @current
PRINT '- следующее = ' + @next
-- 3. Целевой вариант использования (запишем значение 2)
INSERT РегистрСведений.ВходящиеСообщения
SELECT НомерСообщения = @current
, ТипСообщения = 'тест'
, ТелоСообщения = 'test'
-- 4. Целевой вариант использования (запишем значение 3)
INSERT РегистрСведений.ВходящиеСообщения
SELECT НомерСообщения = VECTOR('so_my_sequence')
, ТипСообщения = 'тест'
, ТелоСообщения = 'test'
END
-- Результат выполнения скрипта
[2024-10-07 21:46:30] Значения so_my_sequence:
[2024-10-07 21:46:30] - текущее = 2
[2024-10-07 21:46:30] - следующее = 3