Работа с платформой 1С:Предприятие 7.7 в условиях высокой нагрузки (более 200 одновременно работающих пользователей) неизбежно сталкивает разработчика с проблемой ожиданий на блокировках. Штатный механизм 1С 7.7 использует достаточно грубые блокировки на уровне всей таблицы, особенно для системных таблиц _1SJOURN (журнал документов) и _1SSYSTEM (системные установки и точка актуальности). Когда один пользователь проводит «тяжелый» документ, вся база фактически выстраивается в очередь.
В данной статье мы разберем продвинутый метод внедрения так называемых «гибких блокировок», который позволяет распараллелить процессы проведения документов и записи данных, минимизируя простои системы. Рассмотрим пошагово, как обойти ограничения платформы, используя возможности MS SQL Server.
Проанализируем стандартное поведение 1С 7.7 при проведении документа. Платформа открывает транзакцию и накладывает блокировки на таблицы в следующем порядке:
_1SJOURN для записи состояния документа._1SSYSTEM для обновления Точки Актуальности (ТА).
При большом количестве пользователей попытка записи в _1SSYSTEM становится «бутылочным горлышком». Даже если документы относятся к разным организациям или складам, они все равно конфликтуют за одну и ту же запись в системной таблице. Наша задача — сделать эти блокировки более гранулярными.
Первым делом необходимо «разлочить» основные таблицы: _1SJOURN, _1SSYSTEM, _1SUIDCTL, а также справочники и документы. Это делается путем изменения параметров работы компоненты 1С++ или другими программными методами, позволяющими избежать штатного TABLOCKX. Однако просто снять блокировки нельзя — мы рискуем получить дубликаты ID и нарушение логической целостности ТА.
При параллельной работе нескольких сессий получение максимального ID (хранимки _1sp__1SJOURN_MaxID и _1sp__1SUIDCTL_GetMaxID) должно быть строго последовательным, но очень коротким по времени. Рассмотрим, как оптимизировать эти процедуры с помощью SQL-хинтов UPDLOCK, ROWLOCK и HOLDLOCK.
Поскольку эти хранимые процедуры вызываются в момент создания объекта (метод Новый()) и выполняются миллисекунды, блокировка на уровне строки не вызовет заметных задержек, но гарантирует отсутствие дублей. Мы модифицируем штатные процедуры получения ID так, чтобы они блокировали только конкретную строку с кодом типа объекта, а не всю таблицу целиком.
Самая сложная часть — обновление Точки Актуальности в _1SSYSTEM. Штатно 1С пытается выставить блокировку ДО выполнения кода ОбработкаПроведения, а обновляет ее ПОСЛЕ. Если мы уберем блокировку, то другие документы могут «вклиниться» и записать свои данные, из-за чего ТА может прыгнуть назад или возникнет ошибка «Существуют более ранние документы».
Для решения этой проблемы применим триггер типа INSTEAD OF UPDATE на таблицу _1SSYSTEM. Это позволит нам программно контролировать, куда и когда сдвигается ТА, не блокируя всю таблицу на время проведения документа. Посмотрим на пример реализации такого триггера:
TRIGGER _1SSYSTEM_Control
ON [dbo].[_1SSYSTEM]
INSTEAD OF UPDATE
AS
SET NOCOUNT ON
UPDATE [dbo].[_1SSYSTEM]
SET CURDATE = newVals.CURDATE
, CURTIME = case
when coalesce(lastDoc.docTime, newVals.CURTIME) > newVals.Curtime
then coalesce(lastDoc.docTime, newVals.CURTIME)
else newVals.CURTIME
end
, EVENTIDTA = case
when coalesce(lastDoc.docTime, newVals.CURTIME) >= newVals.Curtime
and coalesce(lastDoc.docID, newVals.EVENTIDTA) > newVals.EVENTIDTA
then coalesce(lastDoc.docID, newVals.EVENTIDTA)
else newVals.EVENTIDTA
end
-- остальные поля (DBSIGN, DBSETUUID и т.д.) просто переносим из inserted
FROM
[dbo].[_1SSYSTEM] as sysTable
cross join (select top 1 * from inserted) as newVals
outer apply (
select top 1
cast(dbo.StrToID(SUBSTRING(DATE_TIME_IDDOC, 9, 6)) as int) as docTime
, IDDOC as docID
from [dbo].[_1SJOURN] with (nolock)
where [DATE_TIME_IDDOC] between convert(char(8), newVals.[CURDATE], 112)
and (convert(char(8), newVals.[CURDATE], 112) + 'ZZZ')
and [CLOSED]&1 = 1
order by [DATE_TIME_IDDOC] desc
) as lastDoc
Этот триггер при любой попытке обновления _1SSYSTEM автоматически находит последний реально проведенный документ в журнале (используя NOLOCK для чтения) и устанавливает ТА на него. Таким образом, даже если документы проводятся параллельно, ТА всегда будет указывать на самый поздний из них.
Чтобы избежать логических ошибок (например, когда два документа одновременно списывают один и тот же товар в минус), необходимо реализовать механизм софтовых блокировок. Вместо блокировки таблиц SQL мы используем системную процедуру sp_getapplock.
Рассмотрим алгоритм:
ОбработкаПроведения формируем список ресурсов, которые нужно заблокировать (например, ИД товара + ИД склада).sp_getapplock для каждого ресурса.Такой подход позволяет двум пользователям одновременно проводить документы, если они не пересекаются по конкретным товарам.
Выясним причину еще одного узкого места — таблицы 1SUSERS и поля NETCHGCN. Это счетчик изменений, который 1С инкрементирует при каждой записи данных. При 200 пользователях запись в эту таблицу становится серьезной преградой.
Проанализируем ситуацию: этот счетчик нужен только для уведомления других сеансов об обновлении списков (журналов). Если вы используете внешние компоненты, такие как 1CPP и класс ТабличноеПоле, штатные механизмы обновления списков вам не нужны. В этом случае можно использовать триггер, который будет игнорировать обновления этой таблицы или выполнять их значительно реже, снимая нагрузку с транзакций проведения.
Для корректной работы ТА в самом конце ОбработкаПроведения (когда все движения уже записаны, но транзакция еще не закрыта) рекомендуется принудительно вызвать обновление системной таблицы через собственную хранимую процедуру. Это гарантирует, что ваша сессия «застолбит» место для ТА.
// Пример вызова в конце ОбработкаПроведения
Позиция = ПолучитьПозицию();
ДатаДок = Сред(Позиция, 2, 8);
ВремяДок = СокрЛП(Сред(Позиция, 10, 10));
ИдДок = _IdToStr(СокрЛП(Сред(Позиция, 20)));
ТекстЗапроса = "exec [dbo].[_1sp__1SSYSTEM_ToLastDoc] '"+ДатаДок+"', "+ВремяДок+", '"+ИдДок+"'";
ЗапросSQL.Выполнить(ТекстЗапроса);
Внутри хранимой процедуры _1sp__1SSYSTEM_ToLastDoc мы выполняем обычный UPDATE таблицы _1SSYSTEM, который перехватывается нашим триггером и корректно устанавливает Точку Актуальности на текущий документ или более поздний, если кто-то уже успел завершить транзакцию раньше нас.
Рассмотрим важные параметры MS SQL Server, которые помогут закрепить результат:
TempDB на несколько файлов (по количеству ядер процессора) и вынесите их на самый быстрый диск (NVMe). 1С 7.7 крайне активно использует временные таблицы._1SJOURN, особенно по полю DATE_TIME_IDDOC, так как высокая интенсивность вставок приводит к быстрой фрагментации.READ COMMITTED SNAPSHOT. Это позволит отчетам строиться по моментальным снимкам данных, не блокируя пишущие транзакции проведения.Внедрение вышеописанных методов позволяет превратить «уставшую» систему на 1С 7.7 в высокопроизводительный комплекс, способный переваривать сотни документов в минуту без заметных ожиданий для пользователей. Главное — помнить, что гибкие блокировки накладывают повышенную ответственность на разработчика в части контроля логической целостности данных.