Как ускорить запись большого объема данных в регистр сведений 1С на мобильном терминале (ТСД)?

Программист 1С v8.3 (Управляемые формы) Управленческий учет Торговля и дистрибуция
← На главную

Рассмотрим распространенную проблему: медленная запись значительного объема данных в регистр сведений на мобильном терминале сбора данных (ТСД). В нашем случае, пользователь сталкивается с тем, что получение остатков номенклатуры из ERP происходит очень быстро (около секунды), но последующая запись 1800 строк в регистр сведений на ТСД занимает критически долгие 53 секунды. Эта проблема обусловлена неоптимальным подходом к обработке данных и спецификой работы платформы 1С:Предприятие на мобильных устройствах с базой данных SQLite. Выясним причины и разберем по шагам, как значительно ускорить этот процесс.

1. Оптимизация получения и создания ссылочных данных: Избегаем запросов и создания объектов в цикле

Основная причина низкой производительности часто кроется в так называемой проблеме N+1 запроса – выполнении множества запросов к базе данных (или создании объектов) внутри цикла. Для каждой строки данных из ERP мы выполняем проверку наличия и/или создание номенклатуры, склада, серии, характеристики. Это может означать тысячи обращений к базе данных, что крайне неэффективно.

Суть проблемы:

В оригинальном коде, представленном автором, функции типа ПолучитьНоменклатуру() вызываются для каждой строки в цикле:


Для каждого СтрВн Из Таблица Цикл //в таблице 1800 строк
    Номенклатура = ПолучитьНоменклатуру(СтрВн.Номенклатура, СтрВн.НоменклатураНаименование, СтрВн.НоменклатураАртикул, СтрВн.ВидНоменклатурыНаименование).Ссылка;
    // ...
    СкладЗаказа = ПолучаемСклад(СтрВн.Склад, СтрВн.СкладНаименование).Ссылка;
    // ...
    Если СтрВн.Серия <> Справочники.СерииНоменклатуры.ПустаяСсылка И СтрВн.Серия <> NULL И СтрВн.ВидНоменклатурыНаименование <> "Тара" Тогда
        Серия = ПолучитьСерию(СтрВн.Серия, СтрВн.СерияНаименование,Номенклатура).Ссылка;
    КонецЕсли;
    // ...
    Если СтрВн.Характеристика <> Справочники.ХарактеристикиНоменклатуры.ПустаяСсылка И СтрВн.Характеристика <> NULL Тогда
        Характеристика = ПолучитьХарактеристику(СтрВн.Характеристика, СтрВн.ХарактеристикаНаименование, Номенклатура).Ссылка;
    КонецЕсли;
    // ...
КонецЦикла;

Пример функции ПолучитьНоменклатуру демонстрирует создание или обновление элемента справочника:


Функция ПолучитьНоменклатуру(Номенклатура, Наименование, Артикул, ВидНоменклатуры)
    // ...
    Если ОбъектНеНайден > 0 Тогда // Предполагается, что здесь проверяется наличие по УИД
        ДанныеОбъект = Справочники.Номенклатура.СоздатьЭлемент;
        ДанныеОбъект.УстановитьСсылкуНового(Номенклатура.Ссылка); // Номенклатура здесь уже ссылка из ERP
        // ... заполнение полей
        ДанныеОбъект.Записать;
        Возврат ДанныеОбъект.Ссылка;
    Иначе
        ДанныеОбъект = Номенклатура.Ссылка.ПолучитьОбъект; // Получаем объект по ссылке из ERP
        // ... обновление полей
        ДанныеОбъект.Записать;
        Возврат ДанныеОбъект.Ссылка;
    КонецЕсли;
КонецФункции

Такой подход приводит к множественным операциям чтения/записи к базе данных внутри цикла.

Решение 1.1: Пакетная обработка и предварительный поиск существующих данных.

  1. Подготовка исходных данных: Всю таблицу значений, полученную от веб-сервиса (Таблица), сначала преобразуем в структуру, удобную для запроса. Это может быть временная таблица в запросе или структура данных, которую можно передать в качестве параметра запроса.

  2. Запрос с временными таблицами: Создаем один SQL-запрос (или серию пакетных запросов 1С), в который передаем данные из нашей Таблица во временную таблицу. Затем в этом же запросе (или последующих пакетах) выполняем левое соединение с существующими справочниками на ТСД (Справочники.Номенклатура, Справочники.СерииНоменклатуры, Справочники.ХарактеристикиНоменклатуры, Справочники.Склады). Это позволяет одним махом определить, какие элементы уже существуют в базе ТСД, а какие нужно создать.

    
    // Предполагаем, что "Таблица" содержит колонки с УИДами и наименованиями
    // например, Номенклатура_УИД, Номенклатура_Наименование и т.д.
    
    Запрос = Новый Запрос;
    Запрос.Текст =
    "ВЫБРАТЬ
        |    ТД.Номенклатура_УИД КАК Номенклатура_УИД,
        |    ТД.Номенклатура_Наименование КАК Номенклатура_Наименование,
        |    ТД.Номенклатура_Артикул КАК Номенклатура_Артикул,
        |    ТД.ВидНоменклатуры_Наименование КАК ВидНоменклатуры_Наименование,
        |    ТД.Склад_УИД КАК Склад_УИД,
        |    ТД.Склад_Наименование КАК Склад_Наименование,
        |    ТД.Серия_УИД КАК Серия_УИД,
        |    ТД.Серия_Наименование КАК Серия_Наименование,
        |    ТД.Характеристика_УИД КАК Характеристика_УИД,
        |    ТД.Характеристика_Наименование КАК Характеристика_Наименование,
        |    ТД.ВНаличииОстаток КАК Остаток
        |ПОМЕСТИТЬ ВремИсходныеДанные
        |ИЗ
        |    &ТаблицаДанных КАК ТД
        |;
        |
        |////////////////////////////////////////////////////////////////////////////////
        |ВЫБРАТЬ
        |    ВИД.Номенклатура_УИД,
        |    ВИД.Номенклатура_Наименование,
        |    ВИД.Номенклатура_Артикул,
        |    ВИД.ВидНоменклатуры_Наименование,
        |    ВИД.Склад_УИД,
        |    ВИД.Склад_Наименование,
        |    ВИД.Серия_УИД,
        |    ВИД.Серия_Наименование,
        |    ВИД.Характеристика_УИД,
        |    ВИД.Характеристика_Наименование,
        |    ВИД.Остаток,
        |    Ном.Ссылка КАК СсылкаНоменклатуры,
        |    Скл.Ссылка КАК СсылкаСклада,
        |    Сер.Ссылка КАК СсылкаСерии,
        |    Хар.Ссылка КАК СсылкаХарактеристики
        |ИЗ
        |    ВремИсходныеДанные КАК ВИД
        |        ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Ном
        |        ПО ВИД.Номенклатура_УИД = Ном.УникальныйИдентификатор
        |        ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Склады КАК Скл
        |        ПО ВИД.Склад_УИД = Скл.УникальныйИдентификатор
        |        ЛЕВОЕ СОЕДИНЕНИЕ Справочник.СерииНоменклатуры КАК Сер
        |        ПО ВИД.Серия_УИД = Сер.УникальныйИдентификатор
        |        ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ХарактеристикиНоменклатуры КАК Хар
        |        ПО ВИД.Характеристика_УИД = Хар.УникальныйИдентификатор
        |;
        |";
    
    Запрос.УстановитьПараметр("ТаблицаДанных", Таблица); // Передаем исходную ТЗ
    Выборка = Запрос.Выполнить().Выбрать();
    
  3. Создание недостающих элементов: После выполнения запроса проходим по Выборке. Если для какого-либо элемента (например, Выборка.СсылкаНоменклатуры) значение Неопределено или NULL, это означает, что его нет в базе ТСД. Только в этом случае мы создаем новый элемент справочника. Все найденные ссылки сразу используем.

    
    // Инициализация списков для пакетного создания
    СписокНоменклатурыДляСоздания = Новый СписокЗначений;
    СписокСкладовДляСоздания = Новый СписокЗначений;
    // ... аналогично для серий и характеристик
    
    СоответствиеНоменклатураУИДСсылка = Новый Соответствие;
    СоответствиеСкладУИДСсылка = Новый Соответствие;
    // ...
    
    Пока Выборка.Следующий() Цикл
        Если Выборка.СсылкаНоменклатуры = Неопределено Тогда
            Если Не СписокНоменклатурыДляСоздания.СодержитЗначение(Выборка.Номенклатура_УИД) Тогда
                СписокНоменклатурыДляСоздания.Добавить(Выборка.Номенклатура_УИД, Выборка.Номенклатура_Наименование);
            КонецЕсли;
        Иначе
            СоответствиеНоменклатураУИДСсылка.Вставить(Выборка.Номенклатура_УИД, Выборка.СсылкаНоменклатуры);
        КонецЕсли;
        // Аналогично для складов, серий, характеристик
    КонецЦикла;
    
    // После цикла создаем все недостающие элементы справочников в отдельных циклах,
    // возможно, с использованием ОбменДанными.Загрузка = Истина (см. ниже)
    // и заполняем Соответствия созданными ссылками.
    

Решение 1.2: Функции получения должны возвращать Ссылку.

Если мы все же используем вспомогательные функции для получения ссылок (например, для простых случаев или когда пакетный запрос слишком сложен), убедитесь, что они возвращают именно ссылку на элемент справочника, а не объект. Обращение через .Ссылка к объекту, который только что был получен из базы или создан, может вызвать лишние чтения.


// Неоптимально (может быть лишнее чтение объекта):
// Функция ПолучитьНоменклатуру(...)
//     Объект = Справочники.Номенклатура.НайтиПоКоду(...).ПолучитьОбъект(); // Получаем объект
//     Возврат Объект.Ссылка; // Здесь опять обращение, чтобы получить ссылку
// КонецФункции;
//
// Номенклатура = ПолучитьНоменклатуру(...).Ссылка; // Ещё одно разыменование, если функция вернула объект

// Оптимально:
Функция ПолучитьНоменклатуру(УИД, Наименование, Артикул, ВидНоменклатуры) Экспорт
    // Сначала ищем по УИД
    Ссылка = Справочники.Номенклатура.НайтиПоУникальномуИдентификатору(УИД);
    Если Ссылка <> Неопределено Тогда
        Возврат Ссылка;
    КонецЕсли;

    // Если не нашли, создаем новый
    НовыйЭлемент = Справочники.Номенклатура.СоздатьЭлемент();
    НовыйЭлемент.УстановитьСсылкуНового(УИД); // Если УИД известен из ERP
    НовыйЭлемент.Наименование = Наименование;
    НовыйЭлемент.Артикул = Артикул;
    // ...
    НовыйЭлемент.Записать();
    Возврат НовыйЭлемент.Ссылка;
КонецФункции;

// Тогда в цикле:
// Номенклатура = ПолучитьНоменклатуру(СтрВн.Номенклатура_УИД, ...); // Сразу получаем ссылку

2. Принципиальная оптимизация работы со справочниками на ТСД: Использование УИДов (GUID) из ERP

Зачастую на ТСД нам не нужны полноценные объекты справочников со всей их логикой и реквизитами. Если данные об остатках используются преимущественно для отображения и не участвуют в сложных движениях по регистрам на самом терминале, есть более радикальное и эффективное решение.

Суть проблемы: Поддержание полной копии справочников ERP на ТСД требует постоянной синхронизации, создания элементов, проверки уникальности, что замедляет работу.

Решение: Хранение УИДов и наименований вместо ссылок.

Измените структуру регистра сведений таким образом, чтобы вместо ссылок на справочники ТСД (например, СправочникСсылка.Номенклатура) он хранил строковые представления уникальных идентификаторов (GUID) и/или наименований из ERP. Это полностью исключает необходимость создания и поддержания справочников на ТСД.

  1. Изменяем измерения регистра: Для измерений, которые ранее были ссылками, создаем новые измерения типа "Строка" достаточной длины (например, 36 символов для УИД и 100-200 для наименования).

    
    // В конфигураторе, для регистра сведений "Остатки":
    // Вместо измерения "Номенклатура" типа СправочникСсылка.Номенклатура
    // Создаем:
    // Измерение: НоменклатураУИД, Тип: Строка, Длина: 36
    // Измерение: НоменклатураНаименование, Тип: Строка, Длина: 100
    // Аналогично для Склад, Серия, Характеристика.
    
  2. Передача и запись данных: Теперь из ERP передаем не ссылки на объекты ERP, а их УИДы и наименования в виде строк. В регистр сведений на ТСД записываем эти строковые значения напрямую.

    Преимущества этого подхода:

    • Значительное ускорение: Полное отсутствие операций создания и поиска элементов справочников на ТСД.
    • Уменьшение размера базы: База ТСД не разрастается за счет справочников.
    • Упрощение логики: Код синхронизации становится проще, так как не нужно заботиться о поддержании ссылочной целостности и актуальности справочников.
    • Надежность: Меньше потенциальных ошибок, связанных с несоответствием данных справочников.

    Этот подход особенно актуален для легких конфигураций ТСД, где справочники являются лишь «виртуальными» и нужны для отображения информации пользователю.

3. Оптимизация записи набора записей регистра сведений

Даже после оптимизации работы со справочниками, сам процесс добавления 1800 записей в НаборЗаписей и последующая запись набора требуют оптимизации.

Суть проблемы: Построчное добавление записей через НаборЗаписей.Добавить() и запись с параметром по умолчанию может быть медленной.

Решение 3.1: Использование метода Загрузить().

Вместо того чтобы в цикле использовать НаборЗаписей.Добавить() для каждой записи, гораздо быстрее подготовить таблицу значений (ТаблицаЗначений), структура колонок которой полностью совпадает с измерениями и ресурсами регистра, и затем использовать метод НаборЗаписей.Загрузить(ПодготовленнаяТаблица). Этот метод позволяет платформе 1С выполнить массовую вставку данных более эффективно, так как он оптимизирован на уровне внутреннего движка.

  1. Формирование таблицы значений для загрузки:

    
    ТаблицаДляЗагрузки = Новый ТаблицаЗначений;
    ТаблицаДляЗагрузки.Колонки.Добавить("Номенклатура"); // Тип колонки должен соответствовать типу измерения регистра
    ТаблицаДляЗагрузки.Колонки.Добавить("Склад");
    ТаблицаДляЗагрузки.Колонки.Добавить("Серия");
    ТаблицаДляЗагрузки.Колонки.Добавить("Характеристика");
    ТаблицаДляЗагрузки.Колонки.Добавить("Остаток");
    
    // После выполнения пакетного запроса и, при необходимости,
    // создания недостающих элементов справочников,
    // формируем ТаблицуДляЗагрузки
    Пока Выборка.Следующий() Цикл
        НоваяСтрокаТЗ = ТаблицаДляЗагрузки.Добавить();
        // Здесь используем уже найденные ссылки (или УИДы/наименования, если изменили регистр)
        НоваяСтрокаТЗ.Номенклатура = СоответствиеНоменклатураУИДСсылка.Получить(Выборка.Номенклатура_УИД); // или Выборка.СсылкаНоменклатуры
        НоваяСтрокаТЗ.Склад = СоответствиеСкладУИДСсылка.Получить(Выборка.Склад_УИД); // или Выборка.СсылкаСклада
        НоваяСтрокаТЗ.Серия = СоответствиеСерияУИДСсылка.Получить(Выборка.Серия_УИД); // или Выборка.СсылкаСерии
        НоваяСтрокаТЗ.Характеристика = СоответствиеХарактеристикаУИДСсылка.Получить(Выборка.Характеристика_УИД); // или Выборка.СсылкаХарактеристики
        НоваяСтрокаТЗ.Остаток = Выборка.Остаток;
    КонецЦикла;
    
  2. Загрузка и запись набора записей:

    
    НаборЗаписей = РегистрыСведений.Остатки.СоздатьНаборЗаписей();
    // Если требуется удалить старые записи по определенному отбору, установите его
    // Например, удалить все остатки для определенного склада перед записью новых
    // НаборЗаписей.Отбор.Склад.Установить(СсылкаНаСклад);
    
    НаборЗаписей.Загрузить(ТаблицаДляЗагрузки);
    НаборЗаписей.Записать(Истина); // Используем параметр Истина для замещения
    

Решение 3.2: Отключение проверок и замещение при записи.

При записи набора записей используйте параметр НаборЗаписей.Записать(Истина). Флаг «Истина» (замещение) работает быстрее, чем попытка сопоставления каждой записи. Система просто очищает старые записи по указанному отбору и записывает новые. Это особенно эффективно, если вы хотите полностью перезаписать данные для определенного набора измерений (например, для всех остатков на конкретном складе).

4. Специфика мобильной платформы (SQLite) и транзакции

На ТСД используется база данных SQLite, которая имеет свои особенности производительности. Без явного управления транзакциями, каждая отдельная операция записи (даже запись одного объекта или строки) может оборачиваться в неявную транзакцию. Это крайне медленно, особенно при работе с дисковой подсистемой мобильного устройства.

Решение: Оборачиваем весь процесс в одну явную транзакцию.

Обязательно оберните весь цикл обработки данных – от подготовки данных и создания справочников (если от них не удалось отказаться) до финальной записи набора записей регистра – в одну явную транзакцию. Это значительно снижает накладные расходы на операции с диском и может ускорить процесс в десятки раз.


НачатьТранзакцию();
Попытка
    // Здесь весь оптимизированный код:
    // 1. Пакетный запрос для определения существующих/недостающих элементов справочников.
    // 2. Создание недостающих элементов справочников (если требуется).
    // 3. Формирование ТаблицыЗначений для загрузки в регистр.
    // 4. Загрузка ТаблицыЗначений в НаборЗаписей.
    // 5. НаборЗаписей.Записать(Истина);

    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
    ВызватьИсключение "Ошибка при записи остатков: " + ОписаниеОшибки();
КонецПопытки;

Использование транзакции гарантирует, что все операции будут либо успешно завершены вместе, либо полностью отменены, что также повышает надежность.

5. Дополнительные методы оптимизации для ТСД

Помимо основных шагов, существуют дополнительные методы, которые могут улучшить производительность.

  1. Отключение прикладных проверок при создании справочников:

    При программном создании новых элементов справочников (если вы все же их создаете) устанавливайте свойство Объект.ОбменДанными.Загрузка = Истина перед записью объекта. Это отключает выполнение большинства прикладных проверок, обработчиков событий ПередЗаписью и ПриЗаписи, которые могут быть ресурсоемкими и замедлять процесс. Не забудьте сбросить флаг на Ложь после записи.

    
    НовыйЭлемент = Справочники.Номенклатура.СоздатьЭлемент();
    НовыйЭлемент.ОбменДанными.Загрузка = Истина;
    НовыйЭлемент.Наименование = "Новый товар";
    НовыйЭлемент.Записать();
    НовыйЭлемент.ОбменДанными.Загрузка = Ложь; // Важно сбросить флаг!
    
  2. Блокировка обновления интерфейса:

    Если код выполняется на стороне клиента (на самом ТСД), перед началом длительного цикла обработки данных стоит временно отключить обновление интерфейса с помощью УстановитьОбновлениеИнтерфейса(Ложь). Это предотвратит трату ресурсов системы на перерисовку форм или списков в процессе создания тысяч объектов или записи большого объема данных, что может быть заметно на слабых устройствах. После завершения операции обязательно включите обновление обратно.

    
    УстановитьОбновлениеИнтерфейса(Ложь);
    Попытка
        // Весь код операции записи
    Исключение
        // Обработка ошибок
    КонецПопытки;
    УстановитьОбновлениеИнтерфейса(Истина);
    
  3. Место хранения базы данных ТСД:

    Производительность SQLite очень сильно зависит от скорости дисковой подсистемы. Убедитесь, что база данных ТСД расположена во внутренней памяти устройства, а не на медленной SD-карте. Внутренняя память обычно имеет гораздо более высокую скорость чтения/записи.

  4. Индексы регистра сведений:

    Проанализируйте структуру регистра сведений на ТСД. Каждое индексированное измерение или дополнительный индекс замедляет операцию записи, так как при добавлении новой записи необходимо актуализировать все индексы. Для ТСД рекомендуется оставлять только жизненно необходимые индексы. Если данные нужны преимущественно для отображения, возможно, стоит минимизировать количество индексированных измерений.

  5. Формат обмена и сжатие данных (Web-сервис):

    Если ваш веб-сервис передает данные в формате XML, рассмотрите переход на JSON. JSON разбирается мобильным устройством значительно быстрее и занимает меньше памяти. При передаче 1800+ строк стоит также использовать сжатие данных (например, ZIP) на стороне веб-сервиса, чтобы уменьшить объем передаваемых данных и время на их десериализацию на ТСД.

  6. Архитектурный подход «Виртуальных данных» (если применимо):

    Если данные об остатках нужны исключительно для справочной информации и не участвуют в сложных движениях по регистрам на самом ТСД (например, при выбытии товаров), рассмотрите возможность хранения их не в типизированном регистре сведений 1С, а во внутренней таблице SQLite (через внешние источники данных 1С) или вовсе в JSON-файле в оперативной памяти (если объем позволяет). Работа с "сырыми" данными часто оказывается быстрее, чем с типизированными объектами 1С, которые накладывают дополнительные накладные расходы.

  7. Использование фонового задания:

    Для улучшения пользовательского опыта на мобильной платформе можно запустить процесс синхронизации данных в фоновом режиме. Это не ускорит саму запись, но «разблокирует» интерфейс для пользователя. Оператор ТСД сможет начать работу, пока остатки дописываются в базу в фоновом потоке. Это позволяет создать ощущение более быстрой работы системы.

Подведем итог: Для кардинального ускорения записи данных в регистр сведений на ТСД необходимо комплексно подойти к решению проблемы. Наиболее критически важными шагами являются отказ от запросов и создания объектов в цикле в пользу пакетной обработки, использование одной явной транзакции для всей операции записи, и, при возможности, использование УИДов из ERP вместо полноценных справочников на ТСД. Применение метода НаборЗаписей.Загрузить() и флага ОбменДанными.Загрузка = Истина дополнит эти меры, обеспечивая существенный прирост производительности.

← На главную