Необходимость в такой доработке возникла после того, как такая регулярная операция, как перепроведение месяца, перестала укладываться в ночь. Начальник IT сказал, что полгода назад всё было ОК, а значит, надо откатить все изменения, сделанные предыдущим программистом (я тогда только-только устроился на эту работу) за последние полгода, благо бэкап есть.
Идея интересная, чтобы не сказать больше, но я решил пойти другим путём: запустил замер производительности, перепровёл день и посмотрел, кто вылез в топ. Одним из рекордсменов оказалась процедура ОбщегоНазначения.УдалитьЗаписанныеДвиженияДокумента, которая вызывалась перед проведением каждого документа. Процедура записывает пустые наборы записей в каждый регистр, в котором проводимый документ является регистратором, что не есть хорошо, потому что обычно в большинстве этих регистров данный документ и так не делал движений.
Список регистров для очистки процедура получала от функции ПроведениеДокументов.МассивРегистровНужноОчистить, поэтому оставалось сделать так, чтобы эта процедура возвращала только те регистры, в которых есть движения.
Для этого в конфигурацию я добавил:
- регистр сведений ГМ_НепустыеДвижения (по старой традиции этой конфигурации, истоков которой никто не помнит, все нетиповые объекты должны содержать префикс "ГМ_") с измерениями Документ (ведущее, основной отбор) типа ДокументСсылка и НомерРегистра (основной отбор) типа Число(3,0,Неотрицательное).
- параметр сеанса ГМ_РегистрыПоНомеру типа ФиксированныйМассив
- параметр сеанса ГМ_НомераРегистровПоИмени типа ФиксированноеСоответствие
- справочник ГМ_НомераРегистров с кодом типа Число длиной 3 (должно совпадать с длиной измерения НомерРегистра)
Затем подправил процедуру ПроведениеДокументов.МассивРегистровНужноОчистить:
Функция МассивРегистровНужноОчистить(ДокументОбъект) Экспорт МассивРегистров = Новый Массив(); //Коровин 09.09.2014 лствИмена=ПараметрыСеанса.ГМ_РегистрыПоНомеру; лНабор=РегистрыСведений.ГМ_НепустыеДвижения.СоздатьНаборЗаписей(); лНабор.Отбор.Документ.Установить(ДокументОбъект.Ссылка); лНабор.Прочитать(); Для каждого ТекЗапись Из лНабор Цикл МассивРегистров.Добавить("РегистрНакопления."+лствИмена[ТекЗапись.НомерРегистра-1]); КонецЦикла; лфлНетИнформацииОДвижениях=МассивРегистров.Количество()=0;//Мы, вероятно, в том периоде, где регистр ГМ_НепустыеДвижения пуст //Коровин 09.09.2014 - конец Для Каждого Движение ИЗ ДокументОбъект.Метаданные().Движения Цикл //Коровин 09.09.2014 //МассивРегистров.Добавить(Движение.ПолноеИмя()); Если лфлНетИнформацииОДвижениях ИЛИ Найти(Движение.ПолноеИмя(),"РегистрНакопления.")=0 Тогда МассивРегистров.Добавить(Движение.ПолноеИмя()); КонецЕсли; //Коровин 09.09.2014 - конец КонецЦикла; Возврат МассивРегистров; КонецФункции
И добавил в привилегированный модуль процедуру, которую потом вызвал в начале процедуры ПередНачаломРаботыСистемы модуля обычного приложения:
Процедура ПодготовитьНомераРегистров() Экспорт лмИменаРегистров=Новый Массив; лствНомераПоИмени=Новый Соответствие; лЗапрос=Новый Запрос("ВЫБРАТЬ Код, Наименование ИЗ Справочник.ГМ_НомераРегистров УПОРЯДОЧИТЬ ПО Код"); лВыборка=лЗапрос.Выполнить().Выбрать(); лТекКод=0; Пока лВыборка.Следующий() Цикл лТекКод=лТекКод+1; Пока лВыборка.Код>лТекКод Цикл //На случай, которого не должно быть - когда коды идут не подряд лмИменаРегистров.Добавить(); лТекКод=лТекКод+1; КонецЦикла; лмИменаРегистров.Добавить(лВыборка.Наименование); лствНомераПоИмени.Вставить(лВыборка.Наименование,лТекКод); КонецЦикла; Для каждого ТекРегистр Из Метаданные.РегистрыНакопления Цикл Если лствНомераПоИмени[ТекРегистр.Имя]=Неопределено Тогда //Новый регистр лЭлемент=Справочники.ГМ_НомераРегистров.СоздатьЭлемент(); лЭлемент.Наименование=ТекРегистр.Имя; Если лЭлемент.Наименование<>ТекРегистр.Имя Тогда ВызватьИсключение "Наименование регистра "+ТекРегистр.Имя+" слишком длинное ("+СтрДлина(ТекРегистр.Имя)+" символов) и не влезает в справочник ГМ_НомераРегистров"; КонецЕсли; лЭлемент.Записать(); лмИменаРегистров.Добавить(ТекРегистр.Имя); лствНомераПоИмени.Вставить(ТекРегистр.Имя,лЭлемент.Код); КонецЕсли; КонецЦикла; ПараметрыСеанса.ГМ_РегистрыПоНомеру=Новый ФиксированныйМассив(лмИменаРегистров); ПараметрыСеанса.ГМ_НомераРегистровПоИмени=Новый ФиксированноеСоответствие(лствНомераПоИмени); КонецПроцедуры
И, наконец, добавил подписку на событие ПриЗаписи всех регистров накопления:
Процедура ПриЗаписиРегистраНакопления(Источник,Отказ,Замещение) Экспорт Если НЕ Отказ Тогда лНабор=РегистрыСведений.ГМ_НепустыеДвижения.СоздатьНаборЗаписей(); лНабор.Отбор.Документ.Установить(Источник.Отбор.Регистратор.Значение); лНабор.Отбор.НомерРегистра.Установить(ПараметрыСеанса.ГМ_НомераРегистровПоИмени[Источник.Метаданные().Имя]); Если Источник.Количество() Тогда лЗапись=лНабор.Добавить(); лЗапись.Документ=лНабор.Отбор.Документ.Значение; лЗапись.НомерРегистра=лНабор.Отбор.НомерРегистра.Значение; лНабор.Записать(); ИначеЕсли Замещение Тогда //Как минимум РасчетСебестоимостиВыпуска в регистр НезавершенноеПроизводство пишет движения порциями, //так что без учёта флага Замещение есть риск сбросить признак, если последний дописанный набор был пустым лНабор.Записать(); КонецЕсли; КонецЕсли; КонецПроцедуры
В общем, работает всё так: при запуске 1С процедура ПодготовитьНомераРегистров заполняет параметры сеанса именами и номерами регистров накопления, создавая при необходимости нужные элементы в справочнике ГМ_НомераРегистров. При каждой записи непустого набора в какой-либо регистр накопления подписка на событие ПриЗаписи добавляет для этого регистра и регистратора набора запись в регистр ГМ_НепустыеДвижения. При записи пустого набора с установленным флагом Замещение такая запись, наоборот, удаляется.
При очистке движений документа изменённая процедура ПроведениеДокументов.МассивРегистровНужноОчистить читает из регистра ГМ_НепустыеДвижения все записи по этому документу и возвращает массив регистров, для которых записи есть. Если нет ни одной записи, то работает старый алгоритм - на тот случай, если перепроводимый документ был проведён до того, как в конфигурацию были внесены эти изменения. Это также означает, что из регистра ГМ_НепустыеДвижения можно смело вычищать записи по документам из закрытых периодов, которые трогать не предполагается, так как в случае необходимости перепроведение этих документов пройдёт нормально без всяких дополнительных действий.
Upd
В комментариях мне указали на то, что переименование регистра приведёт к проблемам. Тут я вижу три способа с этим разобраться:
1. Непосредственно перед сохранением изменений конфигурации, включающих в себя переименование регистра, переименовать этот регистр в справочнике ГМ_НомераРегистров. Самый простой и быстрый способ, который, однако, требует помнить о такой необходимости, потому что если сохранить изменения в конфигурацию базы данных, воспользоваться этим способом уже не выйдет.
2. Очистить регистр ГМ_НепустыеДвижения. Способ на случай, когда не воспользовались первым. Справочник ГМ_НомераРегистров тоже можно очистить (я бы очистил), но необязательно.
3. Доработать алгоритм так, чтобы в справочнике ГМ_НомераРегистров также хранилось имя таблицы базы данных соответствующего регистра. При переименовании регистра имя таблицы не поменяется и это можно будет использовать в процедуре ПодготовитьНомераРегистров.
Я эту проблему решать не стал, потому что не помню, чтобы хоть раз менял имя регистра (разве что с ИмяРегистра на УдалитьИмяРегистра, но к этому моменту в регистре, как правило, всё равно не было записей), но эта проблема на самом деле не единственная, есть и более вероятная и неприятная: некоторые обновления релиза включают в себя какую-нибудь обработку записей регистров. При этом могут быть перезаписаны наборы записей в том периоде, для которого в регистре ГМ_НепустыеДвижения нет записей и тогда при перепроведении тех документов этого периода, которые делали движения по перезаписанному регистру, движения по всем остальным регистрам задвоятся. Неприятность этой второй проблемы в том, что вы не узнаете о ней по матюкам программы, как непременно узнаете о первой. Зато если вы про неё в курсе, решить её можно способом №2 из решений первой проблемы.
В качестве профилактической меры можно после каждого обновления релиза запускать обработку, которая бы удаляла записи из регистра ГМ_НепустыеДвижения до определённой даты. Такую обработку по-любому стоит иметь для того, чтобы удалять заведомо старые записи, ибо маленькие регистры быстрее работают.