Работа с высоконагруженными системами на платформе 1С:Предприятие 8.3 требует от разработчика глубокого понимания внутренних механизмов управления данными. Одной из самых важных тем является использование управляемых блокировок и оптимизация индексов. В этой статье мы подробно разберем, как платформа взаимодействует с СУБД, зачем нужны разделяемые блокировки и как правильно индексировать данные для достижения максимальной производительности.
Рассмотрим, как именно платформа 1С управляет транзакциями. В режиме управляемых блокировок клиент-серверная база данных работает на уровне изоляции READ COMMITTED SNAPSHOT. Проанализируем, что это дает нам на практике: в этом режиме читающие транзакции не блокируют пишущие, а пишущие — читающие. Это достигается за счет версионирования строк в СУБД.
Однако уровня Snapshot недостаточно для обеспечения логической целостности бизнес-процессов (например, при контроле остатков). Здесь в игру вступает Менеджер блокировок 1С. Это программный слой на уровне сервера приложений 1С, который управляет «виртуальными» блокировками, не накладывая реальных блокировок на уровне строк в SQL (кроме моментов непосредственной записи). Выясним разницу между режимами блокировок:
Разберем типичную ситуацию: программист считывает объект через ПолучитьОбъект(), меняет реквизит и записывает его. Почему в типовых конфигурациях (ERP, УТ 11, БП 3.0) перед этим часто встречается явная блокировка? Посмотрим на пример кода:
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Документ.ЭтапПроизводства2_2");
ЭлементБлокировки.УстановитьЗначение("Ссылка", Выборка.Ссылка);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();
ДокументОбъект = Выборка.Ссылка.ПолучитьОбъект();
// Манипуляции с данными
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
Проанализируем ситуацию: метод ПолучитьОбъект() просто считывает данные. Если между чтением и записью другая транзакция изменит этот же объект, первая транзакция при вызове Записать() получит ошибку «Объект изменен или удален». Чтобы гарантировать, что с момента начала нашей работы объект никто не «перехватит», мы устанавливаем управляемую блокировку ДО получения объекта в память сервера приложений.
Выясним, как платформа создает индексы в СУБД. В 1С нет возможности напрямую использовать конструкцию INCLUDE (покрывающие столбцы), поэтому платформа полагается на композитные (составные) индексы. Рассмотрим основные правила:
_IDRRef (Ссылка). Это означает, что физически данные в таблице упорядочены по GUID ссылки.Реквизит + Ссылка. Поле Ссылка добавляется автоматически, так как оно является ключом кластерного индекса и необходимо СУБД для поиска остальных полей строки (Key Lookup).Index Seek вместо Index Scan.Обратим внимание на нюанс с Разделителем областей данных (в режиме SaaS). Если база работает в модели разделения, первым полем во всех индексах становится ОбластьДанных. Это может приводить к тому, что в плане запроса мы видим Index Seek, но фактически СУБД сканирует весь диапазон данных внутри одной области (одного клиента), что при большом объеме данных может быть медленным.
Рассмотрим важную техническую деталь при работе с коллекциями. Часто возникает желание передать НаборЗаписей регистра непосредственно в блокировку. Посмотрим на стандартную ошибку:
// ОШИБОЧНЫЙ КОД
Набор = РегистрыСведений.Цены.СоздатьНаборЗаписей();
Набор.Отбор.Номенклатура.Установить(Ссылка);
Блокировка = Новый БлокировкаДанных;
Элемент = Блокировка.Добавить("РегистрСведений.Цены");
Элемент.ИсточникДанных = Набор; // Здесь возникнет ошибка "Несоответствие типов"
Выясним причину: свойство ИсточникДанных принимает только РезультатЗапроса, ТаблицуЗначений или ТабличнуюЧасть. Чтобы заблокировать данные на основе набора записей, необходимо либо установить значения фильтров вручную через УстановитьЗначение(), либо выгрузить набор в таблицу:
ТаблицаБлокировки = Набор.Выгрузить();
Элемент.ИсточникДанных = ТаблицаБлокировки;
Элемент.ИспользоватьИзИсточникаДанных("Номенклатура", "Номенклатура");
Помимо СУБД, важно оптимизировать работу в оперативной памяти сервера. Разберем два инструмента:
Соответствие (Map): работает по принципу хэш-таблицы. Поиск ключа в соответствии имеет сложность O(1). Платформа вычисляет хэш от объекта (например, ссылки) и мгновенно находит значение. Если вам нужно часто искать данные по ключу в памяти, используйте Соответствие вместо поиска в ТаблицеЗначений.
Индексы Таблицы Значений: Мало кто знает, но ТаблицаЗначений поддерживает индексирование. Рассмотрим пример:
ТЗ.Индексы.Добавить("Артикул");
Строка = ТЗ.Найти(ЗначениеАртикула, "Артикул");
Проанализируем эффект: после добавления индекса метод Найти() и НайтиСтроки() начнет работать значительно быстрее на больших таблицах (десятки тысяч строк), так как вместо полного перебора будет использоваться внутреннее дерево поиска. Однако помните, что любой индекс требует времени на построение и перестраивается при изменении данных в колонке.
Подводя итог, можно сказать, что управляемые блокировки — это мощный инструмент в руках разработчика, позволяющий тонко настраивать параллельность работы системы. Сочетание грамотных блокировок с правильным индексированием таблиц и использованием оптимальных коллекций в памяти позволяет создавать решения, стабильно работающие при любом количестве пользователей.