Обработка элементов ссылочных типов порциями

11.02.19

Разработка - Запросы

Небольшое дополнение к рекомендации от 1С по оптимизации использования оперативной памяти (https://its.1c.ru/db/v8std#content:2149184374:hdoc).

В рекомендации от 1С Оптимизация использования оперативной памяти предлагается получать данные порциями при потенциально неограниченных выборках.

В качестве примера приводится такой участок кода:

ВсеОбработано = Ложь;
Пока Истина Цикл
	Запрос = Новый Запрос;
	Запрос.Текст = 
	"ВЫБРАТЬ ПЕРВЫЕ 1000
	|	Номенклатура.Ссылка КАК Ссылка,
	|	Номенклатура.Наименование КАК Наименование,
	|	Номенклатура.ВидНоменклатуры КАК ВидНоменклатуры
	|ИЗ
	|	Справочник.Номенклатура КАК Номенклатура
	|ГДЕ
	|	<условие выборки необработанных записей>";
	
	РезультатЗапроса = Запрос.Выполнить();
	ВсеОбработано = РезультатЗапроса.Пустой();
	Если ВсеОбработано Тогда
		Прервать;
	КонецЕсли;
	
	// Обход порции результата запроса
	ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
	Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
		// Обработка элемента выборки
		// ...
	КонецЦикла;
	
КонецЦикла;

Но зачастую бывает сложно подобрать это самое "условие выборки необработанных записей".

Я предлагаю простое решение для выборки ссылочных данных: использовать в качестве указателя на порцию данных ссылку.

Для этого необходимо внести следующие изменения:

  • Перед погружением в цикл запросов получаем пустую ссылку (она всегда меньше любой непустой ссылки) и записываем ее в переменную указывающую на последнюю полученную ссылку (ПоследняяСсылка).
  • В запросе упорядочиваем выборку по ссылке (не включая автоупорядочивание, т.к. это приведет к сортировке по представлению) и добавляем отбор по ссылке (Ссылка > &ПоследняяСсылка).
  • На каждом проходе выборки обновляем переменную ПоследняяСсылка, присваивая ей текущую обрабатываемую ссылку.

Получим следующий код:

ВсеОбработано = Ложь;
ПоследняяСсылка = Справочники.Номенклатура.ПустаяСсылка();
Пока Истина Цикл
	Запрос = Новый Запрос;
	Запрос.Текст = 
	"ВЫБРАТЬ ПЕРВЫЕ 1000
	|	Номенклатура.Ссылка КАК Ссылка,
	|	Номенклатура.Наименование КАК Наименование,
	|	Номенклатура.ВидНоменклатуры КАК ВидНоменклатуры
	|ИЗ
	|	Справочник.Номенклатура КАК Номенклатура
	|ГДЕ
	|	Номенклатура.Ссылка > &ПоследняяСсылка
	|	И <условия отбора требуемые для прикладной задачи>
	|
	|УПОРЯДОЧИТЬ ПО
	|	Ссылка";
	Запрос.УстановитьПараметр("ПоследняяСсылка",ПоследняяСсылка);	
	РезультатЗапроса = Запрос.Выполнить();
	ВсеОбработано = РезультатЗапроса.Пустой();
	Если ВсеОбработано Тогда
		Прервать;
	КонецЕсли;
	
	// Обход порции результата запроса
	ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
	Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
		ПоследняяСсылка = ВыборкаДетальныеЗаписи.Ссылка;
		// Обработка элемента выборки
		// ...
	КонецЦикла;
	
КонецЦикла;

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

П.С. Я намеренно не стал проводить рефакторинг кода с ИТС, чтобы добавленный код был более заметен. На мой взгляд создавать запрос стоит перед циклом, а не внутри него и нет необходимости создавать переменную ВсеОбработано.

Код после рефакторинга
 

См. также

Инструментарий разработчика Роли и права Запросы СКД Программист Руководитель проекта Платформа 1С v8.3 Управляемые формы Запросы Система компоновки данных Платные (руб)

Инструменты для разработчиков 1С 8.3: Infostart Toolkit. Автоматизация и ускорение разработки на управляемых формах. Легкость работы с 1С.

15500 руб.

02.09.2020    184668    1029    403    

967

Обновление 1С Запросы Программист Платформа 1С v8.3 1С:ERP Управление предприятием 2 Абонемент ($m)

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

2 стартмани

06.02.2025    2198    17    XilDen    26    

36

Запросы Программист Платформа 1С v8.3 Запросы 1C:Бухгалтерия Бесплатно (free)

В статье приведена удобная возможность отладки исполняемого запроса динамического списка.

03.12.2024    5731    artemusII    11    

23

Запросы Программист Бесплатно (free)

Увидел cheatsheet по SQL и захотелось нарисовать подобное, но про запросы.

18.10.2024    13135    sergey279    18    

66

Запросы Программист Платформа 1С v8.3 Запросы 1C:Бухгалтерия Бесплатно (free)

Столкнулся с интересной ситуацией, которую хотел бы разобрать, ввиду её неочевидности. Речь пойдёт про использование функции запроса АВТОНОМЕРЗАПИСИ() и проблемы, которые могут возникнуть.

11.10.2024    8215    XilDen    36    

90

СКД Механизмы типовых конфигураций Запросы Программист Платформа 1С v8.3 1С:Зарплата и кадры государственного учреждения 3 1С:Зарплата и Управление Персоналом 3.x Россия Бесплатно (free)

Работая с типовыми отчетами в конфигурациях «Зарплата и управление персоналом, редакция 3», «Зарплата и кадры государственного учреждения, редакция 3» и подобных, в схемах компоновки данных можно встретить конструкции запросов, которые обращаются к некоторым виртуальным таблицам.

20.08.2024    3191    PROSTO-1C    0    

23

Запросы Программист Запросы Бесплатно (free)

Отлаживая взаимодействие с базой данных, мы регулярно сталкиваемся с зависающими или подозрительно долго выполняющимися обращениями, негативно влияющими на производительность. О том, как в PostgreSQL выявить подозрительные запросы, основываясь на доступной о них информации, расскажем в статье.

16.08.2024    10797    user1840182    5    

29
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. A_Max 20 11.02.19 14:55 Сейчас в теме
Это конечно хорошо, но ВНЕЗАПНО может появиться объект с УИД меньше последнего обработанного.
Почему:
1. Никто вам не обещал и гарантирет того, что уиды генерируются последовательно.
2. Обмены

Поэтому и создаются регистры сведений ОчередьХХХХХХХХ
Артано; dsdred; Irwin; +3 Ответить
2. triviumfan 100 11.02.19 18:11 Сейчас в теме
(1) такие вещи делают в монопольном режиме имхо
Plotks2017; +1 1 Ответить
7. lunjio 67 11.02.19 20:53 Сейчас в теме
(1) Речь просто о необходимости порционно обрабатывать информацию, в случае что вы описываете, есть ещё необходимость последовательности, ну хорошо, в этом случае используем МоментВремени() ссылки и вопрос решен. Регистры же, как правило создают для отложенной обработки, в статье же в первом предложении говорится, что порционно при неограниченно больших данных.
8. Eleepod 63 12.02.19 00:27 Сейчас в теме
(1)Согласен, в случае если во время обработки в системе могут появится новые данные они могут быть не обработаны. Данный вариант стоит рассматривать как альтернативу запросу, который получает все данные за одну выборку.
dsdred; Plotks2017; +2 Ответить
23. markers 278 14.02.19 04:51 Сейчас в теме
(8) А что изменится если запросить сразу все? При запросе всей информации данный риск даже выше. И данный способ настолько прост и от этого хорош! При этом не надо добавлять реквизиты и пр.
11. Darklight 34 12.02.19 13:15 Сейчас в теме
(1)Просто так такие UID не появляются. У них есть стандарт генерирования (у 1С он свой UUID, у мелкомягких свой - GUID) - и он хронологический. Вариантов появления младших UID только два:
1. Обмен данных с другой системой, формирующих UID на другом компьютере, выполненный параллельно с обработкой
2. UID задаётся вручную строкой не по тем же правилам его формирования - опять таки каким-то параллельным процессом

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

Да, кстати, замечу, что если вообще не заморачиваться - и выбрать все Ссылки разом а потом их обрабатывать - то это так же ни гарантирует, что в процессе обработки не появятся новые Ссылки (в любой "хронологии"), которые останутся не обработанными - проблема не повторяемого чтения (или наоборот - будут удалены - проблема фантомов). Вот для этого и существуют блокировки данных!
Ali1976; dsdred; tulakin_s; acanta; +4 Ответить
3. lemz 131 11.02.19 18:32 Сейчас в теме
Хочется убивать конечно уже за использование запроса в цикле. Ограничение выборки хотя бы по ссылке имеет здравое зерно
6. lunjio 67 11.02.19 19:38 Сейчас в теме
(3) Запросы в цикле не всегда зло, есть таблица 10 гб размером, сервак просто помирает при попытке выбрать из нее, как сделать как ни порционно и в цикле ? Убивать хочется за тупую архитектуру и кривую логику, когда люди вместо того чтобы попытаться найти фунционал или взять удачный пример из типовых или других решений, придумывают велосипеды, да причем очень кустарные.
NoRazum; Артано; Plotks2017; +3 Ответить
10. Shmell 548 12.02.19 11:29 Сейчас в теме
(3) такой подход очень оправдан. При этом у Вас не засоряется кэш объектов вследствии использования ПолучитьОбъект(), и немаловажный момент - что Вы можете параллелить обработку порций объектов разными фоновыми заданиями.
Артано; Plotks2017; +2 Ответить
14. Darklight 34 12.02.19 13:28 Сейчас в теме
(3)Не понимаю к чему Вы так предрались. Запросы в цикле - это не есть абсолютное зло, особенно когда это цикли порционной обработки. При работе с Big Data так вообще без этого НИКАК не обойтись!

Вот создавать каждый раз запрос в цикле - не очень красиво, но для данного примера - абсолютно не критично!
Plotks2017; for_sale; +2 Ответить
4. acanta 11.02.19 18:35 Сейчас в теме
То есть сравнение на больше меньше работает по Гуиду как по строке?
for_sale; +1 Ответить
9. Eleepod 63 12.02.19 01:11 Сейчас в теме
(4) Примерно, но только не в том представлении как оно выводится методом УникальныйИдентификатор. Например ГУИД dee6e178-55bc-11d9-848a-00112f435cbd при ошибке "объект не найден" будет выведен как 848a00112f435cbd11d955bcdee6e178, т.е. вид A-B-C-D-E превращается в DECBA. Думаю в таком виде они и сравниваются.
18. kuzyara 2140 13.02.19 07:41 Сейчас в теме
(9) гуиды имеют тип binary(16) и сравниваются побитово как бинарные данные: binary bit-for-bit comparison behavior
Функция ПолучитьGUIDПоУникальномуИдентификатору(UUID1)
    UUID=ВРЕГ(UUID1);
    ч1 = Сред(UUID,20,4);
    ч2 = Сред(UUID,25,12);
    ч3 = Сред(UUID,15,4);
    ч4 = Сред(UUID,10,4);
    ч5 = Сред(UUID,1,8);
    Возврат "0x" + ч1 + ч2 + ч3 + ч4 + ч5;
КонецФункции
Показать

А если кто-то как Darklight думает что гуиды формируются хронологически - почитайте статью Как формируется GUID?

Гуиды выдаются пулом по 32 штуки для каждого сеанса отдельно, за исключением random based uuid получаемых через Новый УникальныйИдентификатор()
5. lunjio 67 11.02.19 19:36 Сейчас в теме
Боже мой, уже весь мир давно использует, посмотрите процедуры корректировки стоимости списания в УПП, ПЕРВЫЕ 3000 ГДЕ &ПоследнийОбъектОчереди = НЕОПРЕДЕЛЕНО ИЛИ Объект.Ссылка > &ПоследнийОбъектОчереди УПОРЯДОЧИТЬ ПО Объект.Ссылка.
dsdred; Irwin; Shmell; +3 Ответить
12. Darklight 34 12.02.19 13:18 Сейчас в теме
(5)Думаю не весь мир. Решение очевидно понятное (если ссылки сортируются, значит они могут сравниваться), да неприглядное ;-) не привычно вот таки воспринимать ссылки большинству!
13. Darklight 34 12.02.19 13:25 Сейчас в теме

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

Зачем так людей пугаете. Конечно же можно - если поля сортировки будут ниже поля упорядочивания по ссылеке - то никаких проблем (но это бывает редко - когда ссылка это и есть ключевой обрабатываемый объект)
А вот, если поля для сортировки стоят раньше ссылки - то тогда их так же придётся включить в контроллируемые (по классическому подходу) - и пример несколько усложнится, потеряв своё изначальное изящество - но будет работать. Хоршо бы и такой пример привести тоже.



Так же следует учитывать, что сортировка по ссылке не означает сортировку по моменту времени.

Если речь про момент времени документов - то да, Ссылка <> МоментВремени - но там как раз Ссылку лучше заменить на МоментВремени - он так же уникален (и не повторяем) как и Ссылка.
Если же имелся в виду какой-то другой момент времени - то надо уточнить - какой и в чём проблема!
17. Eleepod 63 12.02.19 22:45 Сейчас в теме
(13) Похоже про сортировку я действительно не прав. А насчет ссылок и моментов времени хотел предостеречь от заблуждения что сортировка по ссылке равна сортировке по моменту времени. Уберу пока эти моменты. Спасибо за комментарии!
15. lunjio 67 12.02.19 13:37 Сейчас в теме
Единственный трындец, который может наступить, это допустим когда мы не прямую таблицу типа обрабатываем, допустим справочника номенклатуры или документа, а какой-нибудь регистр сведений и за такое поле принимаем измерение у которого может пустая ссылка в измерении и таких ссылок больше, чем обрабатываемая порция, вот тогда да, конец света )
16. Darklight 34 12.02.19 17:57 Сейчас в теме
(15)У Вас пример, немного из другой оперы, хотя автор в теме статьи не ограничил её только обработкой источников, являющихся только объектными (аля справочников и документов). Да, там где обрабатываемый объект не является ключевым (как ссылка) для выборки - эта техника не сгодится. Но ничего особо сложного - делаете структуру со свойствами - где все ключевые измерения и сохраняеете текующую позицию туда. В запросе по ним всем сортируете, а условие будет на "ИЛИ" по этим измерениям (и так же на ">"), а в цикле обновляете их все из данных выборки в структуре через ЗаполнитьЗначенияСвойств - как -то так - вроде ничего особенного - код сложнее где-то на (10 + количество ключевых полей) процентов.
19. Glebis 13 13.02.19 09:29 Сейчас в теме
Предлагаю ещё немного рефакторинга:
 	Запрос = Новый Запрос;
	Запрос.Текст = 
	"ВЫБРАТЬ ПЕРВЫЕ 1000
	|	Номенклатура.Ссылка КАК Ссылка,
	|	Номенклатура.Наименование КАК Наименование,
	|	Номенклатура.ВидНоменклатуры КАК ВидНоменклатуры
	|ИЗ
	|	Справочник.Номенклатура КАК Номенклатура
	|ГДЕ
	|	Номенклатура.Ссылка > &ПоследняяСсылка
	|	И <условие выборки необработанных записей>
	|
	|УПОРЯДОЧИТЬ ПО
	|	Ссылка";
	Запрос.УстановитьПараметр("ПоследняяСсылка", Справочники.Номенклатура.ПустаяСсылка());
	РезультатЗапроса = Запрос.Выполнить();
	Пока НЕ РезультатЗапроса.Пустой() Цикл
		// Обход порции результата запроса
		ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
		Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
			// Обработка элемента выборки
			// ...
		КонецЦикла;
		Запрос.УстановитьПараметр("ПоследняяСсылка", ВыборкаДетальныеЗаписи.Ссылка);
		РезультатЗапроса = Запрос.Выполнить();
	КонецЦикла;
Показать
20. Glebis 13 13.02.19 09:43 Сейчас в теме
Если есть проблема с новыми ссылками, то почему бы не складывать уже обработанные в массив и проверять вхождение при обработки каждой порции?

Запрос = Новый Запрос;
	Запрос.Текст = 
	"ВЫБРАТЬ ПЕРВЫЕ 1000
	|	Номенклатура.Ссылка КАК Ссылка,
	|	Номенклатура.Наименование КАК Наименование,
	|	Номенклатура.ВидНоменклатуры КАК ВидНоменклатуры
	|ИЗ
	|	Справочник.Номенклатура КАК Номенклатура
	|ГДЕ
	|	НЕ Номенклатура.Ссылка В (&МассивОбработанныхСсылок)
	|	И <условие выборки необработанных записей";
	МассивОбработанныхСсылок = Новый Массив;
	Запрос.УстановитьПараметр("МассивОбработанныхСсылок", МассивОбработанныхСсылок);
	РезультатЗапроса = Запрос.Выполнить();
	Пока НЕ РезультатЗапроса.Пустой() Цикл
		// Обход порции результата запроса
		ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
		Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
			МассивОбработанныхСсылок.Добавить(ВыборкаДетальныеЗаписи.Ссылка);
			// Обработка элемента выборки
			// ...
		КонецЦикла;
		Запрос.УстановитьПараметр("МассивОбработанныхСсылок", МассивОбработанныхСсылок);
		РезультатЗапроса = Запрос.Выполнить();
	КонецЦикла;
Показать
21. Glebis 13 13.02.19 11:03 Сейчас в теме
Если предыдущий вариант со складированием ссылок обработанных объектов в массив не подходит, то можно попробовать ещё один особо извращенский способ: переделать запрос на НЕ вхождение порции в таблицу изменений справочника. Но здесь имеются подводные камни, которые нужно проверять: наложение общей транзакции, отдельный план обмена с ручной регистрацией и ещё куча других вопросов.
22. VmvLer 13.02.19 14:23 Сейчас в теме
если немного подумать то АВТОНОМЕРЗАПИСИ() решает задачу разбиения выборки на порции

размер порции 1000, 10 000,... задаем параметром и дальше простая математика в SQL без шаманских пассов получает порционную выборку
24. user970589 10 14.02.19 10:05 Сейчас в теме
Картинка зачотная!!! пацсталом. ссылочные элементы. порциями. аххах..
25. dsdred 3817 17.02.19 08:40 Сейчас в теме
Старый способ...
уже описывал в статье https://infostart.ru/public/893304/

А вообще способ "декоративный" - не надежный!
Не дураки придумали ПланОбмена и номера сообщений - этот вариант тоже показан в статье https://infostart.ru/public/893304/
26. aleksey2 89 14.01.25 12:54 Сейчас в теме
Как-то не помогает, выборка доходит до 4 гб и падает.
Оставьте свое сообщение