При оптимизации высоконагруженных систем на платформе 1С:Предприятие разработчики часто сталкиваются с тем, что привычные инструменты ведут себя не совсем так, как ожидается. Одной из таких загадок является разница в производительности между РегистрСведенийМенеджерЗаписи и РегистрСведенийНаборЗаписей. На первый взгляд кажется, что работа с одной записью через менеджер должна быть быстрее или как минимум не медленнее, чем через набор. Однако замеры в SQL Profiler показывают странную картину: избыточные запросы, дублирующиеся операции чтения и удаления. В этой статье мы подробно разберем, почему так происходит, заглянем «под капот» платформы и выясним, какие механизмы СУБД задействуются при вызове метода Записать().
Для начала проанализируем, что представляет собой РегистрСведенийМенеджерЗаписи. Вопреки распространенному мнению, это не самостоятельный механизм записи в таблицу БД, а высокоуровневая «обертка» над наборами записей. Платформа 1С проектировала менеджер записи в первую очередь для удобной работы в формах (интерфейсе), где пользователю нужно прочитать одну запись, изменить в ней данные (включая ключевые поля) и сохранить результат.
Рассмотрим ключевую особенность: менеджер записи внутри себя всегда инициализирует два объекта типа НаборЗаписей. Это необходимо для корректной обработки ситуации, когда пользователь меняет ключевые поля (измерения). Разберем по шагам, как работает алгоритм при вызове метода Записать():
Прочитать(). Его задача — найти и удалить «старую» версию записи из базы данных.Важный нюанс заключается в том, что платформа не анализирует, менялись ключевые поля или нет. Она всегда следует универсальному алгоритму: «очистить место под старым ключом — записать данные по новому ключу». Если ключи не менялись, мы получаем ту самую избыточность, которую видим в профайлере: система удаляет запись и тут же вставляет ее обратно.
Многих разработчиков смущает последовательность действий на уровне СУБД: сначала выполняется SELECT, а только потом DELETE. Казалось бы, инструкция DELETE в SQL сама по себе умеет находить данные по условию WHERE. Зачем же платформе 1С предварительно читать эти данные? Выясним основные причины этого поведения:
1. Отработка событий в модуле набора записей. Даже когда мы используем менеджер записи, система вызывает стандартные обработчики событий: ПередЗаписью и ПриЗаписи. Для того чтобы в этих обработчиках программист мог проанализировать «старые» значения (которые еще хранятся в базе), платформа обязана их прочитать. Без предварительного SELECT контекст набора записей будет пуст, и бизнес-логика, завязанная на сравнении данных «было/стало», не отработает.
2. Управляемые блокировки. Перед выполнением модификации данных в транзакции, 1С должна установить управляемую блокировку. Чтобы система точно знала, какой диапазон данных блокировать, и чтобы гарантировать целостность, выполняется чтение. Это предотвращает ситуации, когда две разные сессии пытаются одновременно изменить одну и ту же запись, что могло бы привести к логическим ошибкам.
3. Механизм оптимистической блокировки (Optimistic Concurrency Control). Платформа 1С следит за тем, чтобы данные не были изменены другим пользователем в промежутке между чтением и записью. Выполняя SELECT, система проверяет версию данных. Если за время, пока менеджер записи был открыт, кто-то другой успел изменить или удалить эту строку в базе, 1С выдаст ошибку «Данные были изменены или удалены другим пользователем». Если бы выполнялся «слепой» DELETE, мы бы просто затерли чужие изменения, не узнав о конфликте.
Рассмотрим практический пример кода и проанализируем, почему запись через набор будет эффективнее. Представим, что нам нужно обновить ресурс в одной записи регистра сведений.
При использовании РегистрСведенийМенеджерЗаписи код выглядит так:
Менеджер = РегистрыСведений.ЦеныНоменклатуры.СоздатьМенеджерЗаписи();
Менеджер.Номенклатура = Справочники.Номенклатура.НайтиПоНаименованию("Товар 1");
Менеджер.Прочитать();
Если Менеджер.Выбран() Тогда
Менеджер.Цена = 150;
Менеджер.Записать();
КонецЕсли;
В этом случае в SQL Profiler мы увидим цепочку запросов: SELECT (при чтении), а затем при записи — снова SELECT, DELETE, снова SELECT и INSERT. Это происходит из-за работы двух внутренних наборов.
Теперь проанализируем работу через РегистрСведенийНаборЗаписей:
Набор = РегистрыСведений.ЦеныНоменклатуры.СоздатьНаборЗаписей();
Набор.Отбор.Номенклатура.Установить(Справочники.Номенклатура.НайтиПоНаименованию("Товар 1"));
Набор.Прочитать();
Если Набор.Количество() > 0 Тогда
Набор[0].Цена = 150;
Набор.Записать();
КонецЕсли;
Здесь мы работаем с одним объектом. При вызове Записать() платформа использует тот же самый отбор, который уже установлен в наборе. Количество обращений к базе данных сокращается минимум вдвое, так как отсутствует необходимость инициализировать второй «фиктивный» набор для поиска старых ключей.
Если мы внимательно посмотрим на текст SQL-запросов, генерируемых 1С, то увидим, что SELECT часто сопровождается подсказкой UPDLOCK. Выясним, для чего это нужно. Это гарантирует, что считанная строка будет заблокирована для изменения другими транзакциями до конца текущей транзакции. Это предотвращает возникновение взаимных блокировок (Deadlocks) при высокой интенсивности записи. МенеджерЗаписи, будучи «тяжелым» объектом, вынужден выполнять эти проверки более избыточно, чтобы оставаться универсальным инструментом для любых сценариев, включая интерактивное редактирование в формах.
Несмотря на избыточность, менеджер записи незаменим в определенных сценариях. Разберем их:
Однако в обработках проведения документов, фоновых заданиях или циклах массовой загрузки данных использовать менеджер записи категорически не рекомендуется. Здесь избыточные SELECT и DELETE превращаются в серьезную проблему, увеличивая время выполнения транзакции и повышая вероятность конфликтов блокировок.
Подводя итог нашему анализу, можно сделать следующие выводы:
МенеджерЗаписи медленнее, потому что он всегда работает через два внутренних набора записей, выполняя двойную работу по поиску и очистке данных.SELECT в SQL — это не ошибка, а реализация механизмов контроля версий, событийной модели 1С и управляемых блокировок.НаборуЗаписей. Это позволит минимизировать количество запросов к СУБД и сделать вашу систему более отзывчивой.Понимание этих внутренних механизмов позволяет разработчику осознанно выбирать инструмент под конкретную задачу, не допуская деградации производительности там, где этого можно легко избежать.