Многократная запись регистров сведений и накопления
страница#std792
Область применения: управляемое приложение, мобильное приложение, обычное приложение.
1. Не рекомендуется записывать наборы записей регистров сведений и накоплений в цикле по одной или нескольким записям. Это в несколько раз медленнее по сравнению с записью регистра одним набором. В большинстве случаев следует ориентироваться на порционную запись наборов из 1000 записей.
2. Рекомендуется избегать перезаписи большого набора, в котором меняется не более 30% записей. Вместо этого следует записывать только изменения.
Перезапись набора приводит к удалению всех записей по отбору набора записей и вставке всех новых записей набора, включая те, которые не менялись. Это в несколько раз медленнее по сравнению с добавлением, обновлением и удалением только измененных записей, если измененных записей не более 30% от всех записей в наборе. Помимо этого, избыточно растет журнал транзакций СУБД (пропорционально количеству удалений и вставки записей).
3. Для оптимальной записи регистров следует применять режимы замещения в сочетании с вычислением измененных записей.
В частном случае, когда нужно только добавить записи в пустой регистр, достаточно в методе Записать набора записей задать параметру РежимЗамещения значение Ложь (добавление). В остальных случаях, рекомендуется использовать режимы замещения Слияние, Удаление (начиная с версии платформы 1С:Предприятие 8.3.26) и Обновление (начиная с версии платформы 1С:Предприятие 8.3.27). Они позволяют обрабатывать записи пакетно, порциями.
4. Примеры.
4.1. Состав добавляемых, обновляемых и удаляемых записей известен и требуется применить эти изменения к базе данных.
Неправильно записывать наборы записей в цикле:
Блокировка = Новый БлокировкаДанных();``ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ПользовательскиеНастройки");``ЭлементБлокировки.УстановитьЗначение("Пользователь", ПользовательСсылка); НачатьТранзакцию(); Попытка Блокировка.Заблокировать(); Для Каждого ЗначениеНастройки Из Настройки Цикл УдаляемыеЗаписи = РегистрыСведений.ПользовательскиеНастройки.СоздатьНаборЗаписей(); УдаляемыеЗаписи.Отбор.ИдентификаторКоманды.Установить(ЗначениеНастройки.ИдентификаторКоманды); УдаляемыеЗаписи.Отбор.Пользователь.Установить(ПользовательСсылка); УдаляемыеЗаписи.Записать(); КонецЦикла; ЗафиксироватьТранзакцию(); Исключение ОтменитьТранзакцию(); ВызватьИсключение; КонецПопытки
Правильно записать изменения в регистр одним набором записей:
Блокировка = Новый БлокировкаДанных();``ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ПользовательскиеНастройки");``ЭлементБлокировки.УстановитьЗначение("Пользователь", ПользовательСсылка);```НачатьТранзакцию(); Попытка Блокировка.Заблокировать(); УдаляемыеЗаписи = РегистрыСведений.ПользовательскиеНастройки.СоздатьНаборЗаписей(); Для Каждого ЗначениеНастройки Из Настройки Цикл УдаляемаяЗапись = УдаляемыеЗаписи.Добавить(); УдаляемаяЗапись.ИдентификаторКоманды = ЗначениеНастройки.ИдентификаторКоманды; УдаляемаяЗапись.Пользователь = ПользовательСсылка; КонецЦикла; УдаляемыеЗаписи.Записать(РежимЗамещения.Удаление); ЗафиксироватьТранзакцию();Исключение ОтменитьТранзакцию(); ВызватьИсключение;``КонецПопытки`
4.2. Заполнение значения нового реквизита (или ресурса) регистра в отложенном обработчике обновления. Обрабатывается порция данных, зарегистрированных для обновления: если значение еще не заполнено, тогда заполнить, иначе не менять.
Неправильно читать и записывать наборы записей в цикле:
Процедура ОбработатьДанныеДляПереходаНаНовуюВерсию(Параметры) Экспорт ПорцияЗначенийИзмерений = ОбновлениеИнформационнойБазы.ДанныеДляОбновленияВМногопоточномОбработчике(Параметры); ПолноеИмяРегистра = Метаданные.РегистрыСведений.ДанныеБизнесПроцессов.ПолноеИмя(); Для Каждого Данные Из ПорцияЗначенийИзмерений Цикл Блокировка = Новый БлокировкаДанных; ЭлементБлокировки = Блокировка.Добавить(ПолноеИмяРегистра); ЭлементБлокировки.УстановитьЗначение("Владелец", Данные.Владелец); НачатьТранзакцию(); Попытка Блокировка.Заблокировать(); НаборЗаписей = РегистрыСведений.ДанныеБизнесПроцессов.СоздатьНаборЗаписей(); НаборЗаписей.Отбор.Владелец.Установить(Данные.Владелец); НаборЗаписей.Прочитать(); // Установка новых значений в набор записей. ... ОбновлениеИнформационнойБазы.ЗаписатьНаборЗаписей(НаборЗаписей); ЗафиксироватьТранзакцию(); Исключение ОтменитьТранзакцию(); ... КонецПопытки; КонецЦикла; ... КонецПроцедуры
Правильно рассчитать изменения одним запросом и записать изменения в регистр одним набором записей:
Процедура ОбработатьДанныеДляПереходаНаНовуюВерсию(Параметры) Экспорт ПорцияЗначенийИзмерений = ОбновлениеИнформационнойБазы.ДанныеДляОбновленияВМногопоточномОбработчике(Параметры); ПолноеИмяРегистра = Метаданные.РегистрыСведений.ДанныеБизнесПроцессов.ПолноеИмя(); Блокировка = Новый БлокировкаДанных; ЭлементБлокировки = Блокировка.Добавить(ПолноеИмяРегистра); ЭлементБлокировки.ИсточникДанных = ПорцияЗначенийИзмерений; ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Владелец", "Владелец"); Запрос = Новый Запрос; Запрос.УстановитьПараметр("ПорцияЗначенийИзмерений", ПорцияЗначенийИзмерений); Запрос.Текст = "ВЫБРАТЬ | *, | ЗНАЧЕНИЕ(Перечисление.СостоянияБизнесПроцессов.Активен) КАК Состояние |ИЗ | РегистрСведений.ДанныеБизнесПроцессов КАК ДанныеБизнесПроцессов |ГДЕ | ДанныеБизнесПроцессов.Состояние = ЗНАЧЕНИЕ(Перечисление.СостоянияБизнесПроцессов.ПустаяСсылка) | И (ДанныеБизнесПроцессов.Владелец) В (&ПорцияЗначенийИзмерений)"; НачатьТранзакцию(); Попытка БлокировкаДанных.Заблокировать(); Выгрузка = Запрос.Выполнить().Выгрузить(); НаборЗаписей = РегистрыСведений.ДанныеБизнесПроцессов.СоздатьНаборЗаписей(); НаборЗаписей.Загрузить(Выгрузка); ОбновлениеИнформационнойБазы.ЗаписатьНаборЗаписей(НаборЗаписей, РежимЗамещения.Обновление); ЗафиксироватьТранзакцию(); Исключение ОтменитьТранзакцию(); ... КонецПопытки; ... КонецПроцедуры 4.3. Обновление части записей в независимом регистре сведений. При записи карточки объекта данные меняются частично.
Объект уже записан в базу данных (транзакция может быть еще не зафиксирована), поэтому применяем запрос, чтобы:
- вычислить новые записи без загрузки их в сервер 1С:Предприятия из СУБД;
- сравнить новые записи с существующими записями в обновляемом регистре сведений;
- выбрать только различия:
- лишние записи - для удаления;
- недостающие новые записи и новую версию измененных записей - для слияния.
Например, неправильно:
Процедура ОбновитьСоставыИерархическихГруппПользователей(ГруппыПользователей) Экспорт ТекстЗапроса = "ВЫБРАТЬ РАЗЛИЧНЫЕ | ГруппыДляОбновления.Родитель КАК ГруппаПользователей, | ГруппыПользователейСостав.Пользователь КАК Пользователь, | НЕ ГруппыДляОбновления.Родитель.ПометкаУдаления | И НЕ ГруппыПользователейСостав.Пользователь.ПометкаУдаления | И НЕ ГруппыПользователейСостав.Пользователь.Недействителен КАК Используется |ИЗ | РегистрСведений.ИерархияГруппПользователей КАК ГруппыДляОбновления | ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ИерархияГруппПользователей КАК НижестоящиеГруппы | ПО (НижестоящиеГруппы.Родитель = ГруппыДляОбновления.Родитель) | И (ГруппыДляОбновления.ГруппаПользователей В (&ГруппыПользователей)) | ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ГруппыПользователей.Состав КАК ГруппыПользователейСостав | ПО (ГруппыПользователейСостав.Ссылка = НижестоящиеГруппы.ГруппаПользователей)"; Запрос = Новый Запрос; Запрос.Текст = ТекстЗапроса; Запрос.УстановитьПараметр("ГруппыПользователей", ГруппыПользователей); Блокировка = Новый БлокировкаДанных; Для Каждого ГруппаПользователей Из ГруппыПользователей Цикл ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.СоставыГруппПользователей"); ЭлементБлокировки.УстановитьЗначение("ГруппаПользователей", ГруппаПользователей); КонецЦикла; НачатьТранзакцию(); Попытка Блокировка.Заблокировать(); Выгрузка = Запрос.Выполнить().Выгрузить(); Для Каждого ГруппаПользователей Из ГруппыПользователей Цикл НаборЗаписей = РегистрыСведений.СоставыГруппПользователей.СоздатьНаборЗаписей(); НаборЗаписей.Отбор.ГруппаПользователей.Установить(ГруппаПользователей); Отбор = Новый Структура("ГруппаПользователей", ГруппаПользователей); НайденныеСтроки = Выгрузка.НайтиСтроки(Отбор); НаборЗаписей.Загрузить(Выгрузка.Скопировать(НайденныеСтроки)); НаборЗаписей.Записать(); // Замена всех записей группы на новые. КонецЦикла; ЗафиксироватьТранзакцию(); Исключение ОтменитьТранзакцию(); ВызватьИсключение; КонецПопытки; КонецПроцедуры
Правильно:
Процедура ОбновитьСоставыИерархическихГруппПользователей(ГруппыПользователей) Экспорт ТекстЗапроса = "ВЫБРАТЬ РАЗЛИЧНЫЕ | СоставыГруппПользователей.ГруппаПользователей КАК ГруппаПользователей, | СоставыГруппПользователей.Пользователь КАК Пользователь |ИЗ | РегистрСведений.ИерархияГруппПользователей КАК ГруппыДляОбновления1 | ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.СоставыГруппПользователей КАК СоставыГруппПользователей | ПО (СоставыГруппПользователей.ГруппаПользователей = ГруппыДляОбновления1.Родитель) | И (ГруппыДляОбновления1.ГруппаПользователей В (&ГруппыПользователей)) | ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ИерархияГруппПользователей КАК ГруппыДляОбновления2 | ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ИерархияГруппПользователей КАК НижестоящиеГруппы | ПО (НижестоящиеГруппы.Родитель = ГруппыДляОбновления2.Родитель) | И (ГруппыДляОбновления2.ГруппаПользователей В (&ГруппыПользователей)) | ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ГруппыПользователей.Состав КАК ГруппыПользователейСостав | ПО (ГруппыПользователейСостав.Ссылка = НижестоящиеГруппы.ГруппаПользователей) | ПО (ГруппыДляОбновления2.Родитель = СоставыГруппПользователей.ГруппаПользователей) | И (ГруппыПользователейСостав.Пользователь = СоставыГруппПользователей.Пользователь) |ГДЕ | ГруппыПользователейСостав.Пользователь ЕСТЬ NULL |; | |//////////////////////////////////////////////////////////////////////////////// |ВЫБРАТЬ РАЗЛИЧНЫЕ | ГруппыДляОбновления.Родитель КАК ГруппаПользователей, | ГруппыПользователейСостав.Пользователь КАК Пользователь, | НЕ ГруппыДляОбновления.Родитель.ПометкаУдаления | И НЕ ГруппыПользователейСостав.Пользователь.ПометкаУдаления | И НЕ ГруппыПользователейСостав.Пользователь.Недействителен КАК Используется |ИЗ | РегистрСведений.ИерархияГруппПользователей КАК ГруппыДляОбновления | ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ИерархияГруппПользователей КАК НижестоящиеГруппы | ПО (НижестоящиеГруппы.Родитель = ГруппыДляОбновления.Родитель) | И (ГруппыДляОбновления.ГруппаПользователей В (&ГруппыПользователей)) | ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ГруппыПользователей.Состав КАК ГруппыПользователейСостав | ПО (ГруппыПользователейСостав.Ссылка = НижестоящиеГруппы.ГруппаПользователей) | ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СоставыГруппПользователей КАК СоставыГруппПользователей | ПО (СоставыГруппПользователей.ГруппаПользователей = ГруппыДляОбновления.Родитель) | И (СоставыГруппПользователей.Пользователь = ГруппыПользователейСостав.Пользователь) |ГДЕ | (СоставыГруппПользователей.Пользователь ЕСТЬ NULL | ИЛИ ЕСТЬNULL(СоставыГруппПользователей.Используется, ЛОЖЬ) <> (НЕ ГруппыДляОбновления.Родитель.ПометкаУдаления | И НЕ ГруппыПользователейСостав.Пользователь.ПометкаУдаления | И НЕ ГруппыПользователейСостав.Пользователь.Недействителен))"; Запрос = Новый Запрос; Запрос.Текст = ТекстЗапроса; Запрос.УстановитьПараметр("ГруппыПользователей", ГруппыПользователей); РезультатыЗапроса = Запрос.ВыполнитьПакет(); Если РезультатыЗапроса[0].Пустой() И РезультатыЗапроса[1].Пустой() Тогда Возврат; КонецЕсли; Блокировка = Новый БлокировкаДанных; ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.СоставыГруппПользователей"); ЭлементБлокировки.ИсточникДанных = РезультатыЗапроса[0]; ЭлементБлокировки.ИспользоватьИзИсточникаДанных("ГруппаПользователей", "ГруппаПользователей"); ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Пользователь", "Пользователь"); ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.СоставыГруппПользователей"); ЭлементБлокировки.ИсточникДанных = РезультатыЗапроса[1]; ЭлементБлокировки.ИспользоватьИзИсточникаДанных("ГруппаПользователей", "ГруппаПользователей"); ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Пользователь", "Пользователь"); НачатьТранзакцию(); Попытка Блокировка.Заблокировать(); РезультатыЗапроса = Запрос.ВыполнитьПакет(); Если Не РезультатыЗапроса[0].Пустой() Тогда НаборЗаписей = РегистрыСведений.СоставыГруппПользователей.СоздатьНаборЗаписей(); НаборЗаписей.Загрузить(РезультатыЗапроса[0].Выгрузить()); НаборЗаписей.Записать(РежимЗамещения.Удаление); КонецЕсли; Если Не РезультатыЗапроса[1].Пустой() Тогда НаборЗаписей = РегистрыСведений.СоставыГруппПользователей.СоздатьНаборЗаписей(); НаборЗаписей.Загрузить(РезультатыЗапроса[1].Выгрузить()); НаборЗаписей.Записать(РежимЗамещения.Слияние); КонецЕсли; ЗафиксироватьТранзакцию(); Исключение ОтменитьТранзакцию(); ВызватьИсключение; КонецПопытки; КонецПроцедуры
4.4. Обновление части записей в регистрах, подчиненных регистратору. При перепроведении данные меняются частично.
Похоже на обновление записей в независимых регистрах сведениях, но есть существенные отличия:
- В отличие от независимых регистров сведений, наличие регистратора делает ключом строки строго два стандартных поля Регистратор и НомерСтроки, а также исключает режим замещения Слияние. Кроме того, в режиме замещения Удаление происходит автоматическое обновление поля НомерСтроки у оставшихся записей в пределах каждого регистратора.
- Поэтому для оптимального обновления поле НомерСтроки должно рассматриваться исключительно, как счетчик строк в пределах регистратора, при этом порядок строк не должен иметь значения для прикладного использования.
- При вычислении изменений нужно исключать НомерСтроки из сравнения.
- Сначала сделать набор записей (со сброшенным отбором) для записи в режиме замещения Обновление из добавляемых и обновляемых записей в количестве не более количества удаляемых записей с номерами строк удаляемых записей.
- Если после этого остались записи для удаления, тогда сделать набор записей (со сброшенным отбором) для записи в режиме замещения Удаление из ключей строк оставшихся удаляемых записей.
- В противном случае, сделать набор записей (со сброшенным отбором) для записи в режиме замещения Добавление (или Ложь) из оставшихся добавляемых и обновляемых записей (без включения РасширенныеРежимыЗамещения и заполнения поля НомерСтроки – доступно, начиная с версии 1С:Предприятия 8.3.26).