При работе с крупными базами данных перед разработчиком нередко возникает задача массового изменения одного или нескольких реквизитов у огромного количества документов. Например, когда требуется скорректировать дату или статус у нескольких миллионов объектов. Стандартные методы перебора и последовательной перезаписи «в лоб» здесь не подходят: процесс может растянуться на десятки часов, парализовав работу пользователей и перегрузив сервер СУБД.
В этой статье мы подробно рассмотрим, как решить эту задачу максимально быстро, разберем доступные архитектурные подходы, взвесим все риски использования прямых запросов к базе данных и изучим современные платформенные возможности 1С.
---
Если ваша база данных работает на SQL-сервере (MS SQL, PostgreSQL и др.), самым быстрым техническим способом изменения реквизита является выполнение прямого SQL-запроса UPDATE. Вместо создания миллионов объектов в памяти 1С и генерации отдельных транзакций на запись, СУБД выполнит одну массовую операцию за секунды или минуты.
Рассмотрим пример простого SQL-запроса для СУБД MS SQL Server, изменяющего реквизит «Дата» у определенного вида документа:
-- Внимание! Перед выполнением запроса обязательно сделайте резервную копию базы данных!
UPDATE _Document300
SET _Date_Time = '2023-12-31 23:59:59'
WHERE _Date_Time < '2023-01-01 00:00:00'
Несмотря на колоссальную скорость, этот метод несет в себе критические риски. Проанализируем, почему прямое обновление базы в обход платформы 1С может привести к серьезным проблемам:
rphost. При выполнении прямого запроса в СУБД сервер 1С не узнает об этих изменениях. Пользователи и фоновые задания будут продолжать видеть старые значения реквизитов, пока не произойдет автоматический сброс кэша или перезапуск службы сервера 1С.UPDATE в СУБД не регистрирует изменения объектов на планах обмена. Если ваша база данных обменивается информацией с другими узлами, измененные документы никогда не улетят в другие базы.ПередЗаписью и ПриЗаписи. Также полностью игнорируется механизм версионирования объектов (БСП) — в истории изменений документов не останется и следа от вашей корректировки.Важная рекомендация: Если вы все же решили использовать этот метод из-за критических требований к скорости, перед выполнением запроса обязательно остановите службу сервера 1С:Предприятие. После успешного выполнения SQL-запроса запустите службу снова — это гарантирует очистку серверного кэша и исключит рассинхронизацию данных.
---
Если мы хотим гарантировать целостность данных и стабильность работы системы, нам необходимо выполнять запись исключительно штатными средствами платформы 1С, но с максимальной оптимизацией кода. Разберем по шагам ключевые правила ускорения пакетной записи:
ОбменДанными.Загрузка = Истина. Это отключает выполнение прикладных проверок в модулях объектов, предотвращает проведение документов и значительно ускоряет физическую запись в СУБД..ldf). Оптимальный размер порции для одной транзакции составляет от 1 000 до 5 000 объектов.Рассмотрим шаблон кода, реализующий порционную запись в транзакции:
Выборка = Документы.РеализацияТоваровУслуг.Выбрать();
НачатаТранзакция = Ложь;
Счетчик = 0;
Порция = 2000; // Оптимальный размер порции
Пока Выборка.Следующий() Цикл
Если Не НачатаТранзакция Тогда
НачатьТранзакцию();
НачатаТранзакция = Истина;
КонецЕсли;
// Получаем объект документа для изменения
ДокументОбъект = Выборка.ПолучитьОбъект();
ДокументОбъект.Дата = НоваяДата;
// Включаем режим обмена для ускорения записи
ДокументОбъект.ОбменДанными.Загрузка = Истина;
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
Счетчик = Счетчик + 1;
// Фиксируем транзакцию по достижении размера порции
Если Счетчик % Порция = 0 Тогда
ЗафиксироватьТранзакцию();
НачатаТранзакция = Ложь;
КонецЕсли;
КонецЦикла;
// Не забываем зафиксировать остаток
Если НачатаТранзакция Тогда
ЗафиксироватьТранзакцию();
КонецЕсли;
---
Если вы работаете на современных версиях платформы 1С:Предприятие, вам доступны новые мощные механизмы, которые кардинально меняют подход к производительности массовых операций:
ВыбратьПоСсылкам() (начиная с 8.3.26). Ранее для изменения реквизита приходилось использовать конструкцию Ссылка.ПолучитьОбъект(), что инициировало отдельный SQL-запрос на чтение для каждого документа. Метод ВыбратьПоСсылкам() у менеджеров объектов позволяет за одно обращение к СУБД порционно считать состояние сразу множества объектов по переданному массиву или таблице ссылок. Это снижает накладные расходы на чтение в разы.;). Это минимизирует количество сетевых задержек (round-trips) между сервером 1С и сервером базы данных.---
Для достижения максимальной скорости выполнения на многопроцессорных серверах задачу необходимо распараллелить. Вместо того чтобы вручную открывать несколько сеансов 1С или самостоятельно писать сложные механизмы распределения задач, воспользуемся готовым функционалом Библиотеки стандартных подсистем (БСП).
В БСП предусмотрена функция ДлительныеОперации.ВыполнитьПроцедуруВНесколькоПотоков(). Она автоматически разбивает массив документов (или список видов документов) на порции и распределяет их выполнение по фоновым заданиям.
Выясним, сколько фоновых заданий (потоков) целесообразно создавать. Практика показывает, что оптимальное количество потоков равно количеству логических ядер процессора на сервере приложений (минус 1-2 ядра, чтобы оставить вычислительные ресурсы для текущей работы других пользователей).
При параллельной работе нескольких потоков критически важно избегать взаимных блокировок (Deadlocks). Для этого перед записью очередной порции документов в коде фонового задания необходимо явно накладывать управляемую блокировку данных:
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Документ.РеализацияТоваровУслуг");
ЭлементБлокировки.УстановитьЗначение("Ссылка", МассивСсылокПорции);
Блокировка.Заблокировать();
---
Подводя итог, выберем оптимальную стратегию в зависимости от условий:
ОбменДанными.Загрузка = Истина.ВыбратьПоСсылкам().