При работе высоконагруженных систем 1С, особенно с параллельно выполняющимися регламентными заданиями и подписками на события, мы часто сталкиваемся с проблемой конкурентного доступа к данным. Один из распространённых сценариев — задвоение документов, например, заказов на перемещение, когда несколько процессов одновременно пытаются создать их по одним и тем же потребностям. Если наш регламент отрабатывает не очень быстро, а алгоритм не оптимален, это может приводить к коллизиям и нарушению целостности данных.
Для решения этой проблемы, вместо "колхозных" признаков выполнения, правильнее использовать механизм управляемых блокировок в 1С:Предприятии. Рассмотрим, как это сделать, и какие нюансы могут возникнуть при взаимодействии с MS SQL Server.
Управляемые блокировки в 1С — это мощный инструмент, который предоставляет нам, как разработчикам, значительно больший контроль над конкурентным доступом к данным по сравнению с автоматическими блокировками, полностью контролируемыми СУБД. Они становятся особенно полезными в многопользовательских системах, где необходимо одновременно обеспечить целостность данных и высокую параллельность работы.
Чтобы применить управляемые блокировки, мы будем использовать объект МенеджерБлокировок. Давайте посмотрим на пример его использования для блокировки записей регистра, по которому читаются потребности:
// Создаем экземпляр менеджера блокировок
МенеджерБлокировок = Новый МенеджерБлокировок;
// Добавляем объект блокировки для регистра накопления "Потребности"
// Важно указать полное имя объекта метаданных
БлокировкаДанных = МенеджерБлокировок.Добавить("РегистрНакопления.Потребности");
// Устанавливаем режим блокировки. "Исключительный" (Exclusive) предотвращает чтение и изменение
// другими транзакциями. "Совместимый" (Shared) позволяет другим транзакциям читать данные,
// но блокирует изменение. Для создания заказов, вероятно, потребуется "Исключительный",
// чтобы гарантировать, что потребности не будут обработаны дважды.
БлокировкаДанных.УстановитьРежим(РежимБлокировкиДанных.Исключительный);
// Устанавливаем значения измерений, по которым мы хотим заблокировать записи.
// Это позволяет заблокировать только конкретные строки, а не весь регистр.
// Мы должны указать все измерения, которые однозначно идентифицируют обрабатываемые потребности.
// Например, если потребности определяются складом и номенклатурой:
БлокировкаДанных.УстановитьЗначение("Склад", СкладПолучатель);
БлокировкаДанных.УстановитьЗначение("Номенклатура", Номенклатура);
// Если в выборку попадает несколько номенклатур для одного склада,
// потребуется добавить несколько объектов БлокировкаДанных,
// или блокировать по менее детализированным измерениям, если это приемлемо.
// В идеале, мы блокируем те записи, которые однозначно определяются нашим запросом потребностей.
// После настройки всех необходимых объектов блокировки, применяем их.
// Это должно быть сделано в начале транзакции, до чтения или изменения данных.
Попытка
МенеджерБлокировок.Заблокировать();
// Начало транзакции должно быть до вызова Заблокировать(),
// или платформа автоматически начнет транзакцию при первой записи.
НачатьТранзакцию();
// Теперь, когда данные заблокированы, мы можем безопасно читать потребности
// и создавать заказы на перемещение.
// Запрос к регистру "Потребности" будет ожидать снятия блокировки,
// если другие процессы уже заблокировали эти же записи.
// ... Выполняем наш "не очень хороший алгоритм", который читает потребности
// и создает заказы. ...
// После успешного создания заказов и записи всех изменений, фиксируем транзакцию.
// Блокировки будут автоматически сняты.
ЗафиксироватьТранзакцию();
Исключение
// В случае ошибки, отменяем транзакцию, и блокировки также будут сняты.
ОтменитьТранзакцию();
ВызватьИсключение; // Передаем исключение дальше по стеку
КонецПопытки;
Важно: блокируйте только те данные, которые вы действительно собираетесь изменять или по которым будет строиться уникальность. Старайтесь минимизировать время удержания блокировки, устанавливая ее непосредственно перед обращением к данным и завершая транзакцию как можно быстрее. Чем меньше данных блокируется и чем короче время блокировки, тем выше будет параллелизм в системе.
При использовании управляемых блокировок, особенно когда мы блокируем много отдельных строк, мы должны учитывать поведение самой СУБД. В нашем случае, это MS SQL Server. SQL Server имеет свой внутренний механизм оптимизации управления блокировками, который называется эскалацией блокировок.
Что такое эскалация блокировок: Это процесс, при котором SQL Server автоматически преобразует большое количество мелких блокировок (например, на уровне строк или страниц) в одну более крупную блокировку (например, на уровне таблицы). Этот механизм является оптимизацией, направленной на снижение накладных расходов на управление блокировками и уменьшение потребления памяти сервером SQL. Меньше блокировок означает меньше ресурсов для их управления.
Причины и последствия эскалации:
INSERT, UPDATE, DELETE), а также при выполнении запросов, которые не могут эффективно использовать индексы и вынуждены сканировать всю таблицу, приобретая большое количество блокировок.Если наш "не очень хороший алгоритм" внезапно начнет обрабатывать тысячи потребностей за одну транзакцию, он рискует вызвать эскалацию блокировок в SQL Server, и это может заблокировать всю таблицу регистра, по которому читаются потребности, что приведет к остановке всей системы.
Мы выяснили, что эскалация блокировок может стать проблемой. Разберем, как мы можем предотвратить нежелательную эскалацию, которая потенциально может "повесить" продуктивную систему.
Этот флаг является глобальной настройкой и полностью отключает эскалацию блокировок для всего экземпляра SQL Server.
Действие: При включении флага -t1211 SQL Server перестает укрупнять блокировки. Он предотвращает эскалацию как по достижении предельного количества блокировок, так и по достижении предельного размера памяти, занятой для хранения информации о них.
Потенциальные риски: Использование этого флага может привести к значительному росту потребления памяти сервером SQL для хранения информации о большом количестве мелких блокировок. Если памяти, выделяемой для структур блокировок, не хватает, могут возникать ошибки 1204 ("SQL Server не может получить ресурс LOCK в данный момент"). Поэтому его применение должно быть тщательно протестировано и обосновано. Мы не рекомендуем его использовать без глубокого понимания последствий и только после консультации с опытным DBA.
Пример включения и отключения трассировочного флага (требует перезапуска SQL Server для постоянного применения, либо работает до перезапуска):
-- Пример включения трассировочного флага для текущего сеанса
DBCC TRACEON (1211);
-- Пример включения трассировочного флага для всех сеансов (глобально)
-- Изменения вступят в силу немедленно, но для сохранения после перезапуска
-- SQL Server, флаг нужно добавить в параметры запуска службы.
DBCC TRACEON (1211, -1);
-- Пример отключения
DBCC TRACEOFF (1211, -1);
LOCK_ESCALATION = DISABLE для конкретной таблицыЭта опция позволяет нам точечно отключить эскалацию блокировок для одной конкретной таблицы, а не для всего экземпляра SQL Server.
Действие: При установке LOCK_ESCALATION = DISABLE SQL Server не будет эскалировать блокировки до уровня таблицы для этой конкретной таблицы, независимо от количества удерживаемых блокировок. Он будет продолжать работать с блокировками на уровне строк или страниц.
Преимущества: Это более локальное и целенаправленное решение проблемы с эскалацией блокировок для конкретной "проблемной" таблицы, сохраняя при этом автоматическое управление блокировками для остальных таблиц. Изменения, внесенные с помощью ALTER TABLE ... SET (LOCK_ESCALATION = DISABLE), являются постоянными до тех пор, пока мы их не отменим вручную.
Риски: Как и в случае с флагом -t1211, отключение эскалации для таблицы может привести к увеличению потребления памяти и потенциально к проблемам с производительностью из-за огромного количества мелких блокировок, если таблица подвергается очень интенсивным операциям. Рекомендуется применять только после тщательного анализа и тестирования в нерабочей среде.
Пример отключения эскалации для таблицы:
USE [ИмяБазы]; -- Укажите имя вашей базы данных
-- _ReferenceChngR12526 - это пример имени таблицы.
-- Нам нужно найти физическое имя таблицы регистра "Потребности" в SQL Server.
ALTER TABLE _ReferenceChngR12526 SET (LOCK_ESCALATION = DISABLE);
Для контроля, какие таблицы имеют отключенную эскалацию, мы можем использовать следующий запрос:
USE [ИмяБазы]; -- Укажите имя вашей базы данных
SELECT lock_escalation, lock_escalation_desc, name
FROM sys.tables
WHERE lock_escalation_desc='DISABLE';
Предостережение: Мы не можем просто так "запилить" это в тиражное решение, особенно Совместимое. Изменения на уровне СУБД, такие как LOCK_ESCALATION = DISABLE, должны быть частью хорошо продуманной стратегии администрирования базы данных и применяться только после тщательного тестирования и анализа производительности.
Помимо прямого управления блокировками и эскалацией, существуют и другие подходы, которые помогут нам поддерживать стабильность и производительность системы:
WHERE (так называемые non-SARGable-условия), которые могут приводить к полному сканированию таблицы и, как следствие, к большому количеству блокировок. Стараемся разбивать большие транзакции на более мелкие, чтобы уменьшить время удержания блокировок. Чем короче транзакция, тем быстрее освобождаются ресурсы.WITH (ROWLOCK), чтобы принудительно использовать блокировки на уровне строк и предотвратить эскалацию. Однако это также требует осторожности и глубокого понимания последствий, так как может усложнить управление блокировками SQL Server.lock_escalation и других событий, связанных с блокировками. Это поможет нам точно определить, когда и почему происходят проблемы. Также полезно отслеживать метрики по количеству таймаутов и дедлоков в системах мониторинга (например, Zabbix), чтобы оперативно реагировать на возникающие проблемы. Нулевое количество дедлоков в рабочее время - это норма.Read Committed Snapshot, а для PostgreSQL — Read Committed. Эти уровни изоляции в большинстве случаев обеспечивают высокую параллельность. Однако в некоторых ситуациях для обеспечения абсолютной целостности данных требуются явные управляемые блокировки.LOCK_ESCALATION = DISABLE), должно осуществляться только опытными администраторами баз данных после тщательного тестирования, поскольку это может иметь серьезные последствия для производительности и стабильности системы.В конечном итоге, правильный подход заключается не только во внедрении управляемых блокировок для решения конкретной задачи задвоения заказов, но и в глубоком анализе причин проблем с производительностью, постоянной оптимизации кода, а также непрерывном мониторинге работы СУБД и самой платформы 1С. Только комплексный подход позволит нам добиться высокой стабильности и производительности системы.