Каталог решений - Золотые костыли

Описание

Золотые костыли

Мне кажется это название лучше всего подходит к тому, чем мы все занимаемся. Статья «Удачный пример блокировки», получилась небольшая, поэтому следующие заметки добавляю сюда же.

10. МенеджерЗаписи или НаборЗаписей ?

Какой способ записи в регистр выбрать ? Преимущество набора записей в том, несколько несколько строк добавляется в одной транзакции, реже обращения к СУБД. Однако, запись менеджера записи тоже можно объединить с помощью начатьтранзакцию-зафиксироватьтранзакцию, это благотворно отражается в ожиданиях СУБД Writelog. Источник, минута 34.

9. Особенности SQL

Если в таблице Таблица1 сто миллионов строк, запрос вида "Выбрать Сумма(1) ИЗ Таблица1" выдает ошибку переполнения.

Ошибка при выполнении операции над данными:
Microsoft OLE DB Provider for SQL Server: Ошибка арифметического переполнения при преобразовании numeric к типу данных numeric.
HRESULT=80040E57, SQLSrvr: SQLSTATE=22003, state=8, Severity=10, native=8115, line=1

Однако, запрос вида "Выбрать Сумма(1/1000000) ИЗ Таблица1" покажет результат )))

8. Поиск на инфостарте.

Для сложных условий поиска можно пользоваться сторонними средствами.

Пример поисковой строки в Yandex.

site:infostart.ru "РегистрНакопления.Продажи"&&"РегистрСведений.ЦеныНоменклатуры"

Постоянно пользуюсь, дает очень точные результаты.

7. Группировка документов по произвольным реквизитам.

В типовой 1С версии 11.4 есть возможность создавать документ Приобретения на основании массива Заказов поставщику. При этом, само собой, происходит проверка что ключевые реквизиты для всех Заказов поставщику совпадают. Проверка выполняется в модуле ЗакупкиВызовСервера.СформироватьДанныеЗаполненияПоступления, обратите внимание какой громоздкий запрос возвращает ТекстЗапросаПоЗаказам(). Допустим, у нас есть массив ЗаказыПоставщику (различающихся документов Заказ поставщику), мы хотим этот массив перегруппировать в несколько массивов одинаковых документов и создать несколько документов Приобретения. Перечень реквизитов — массив со строками например "Контрагент, Договор, Соглашение". Имя документа — строка "Документ.ЗаказПоставщику". Чтобы было можно обрабатывать документы любых типов.

Функция РазбитьПоПризнакам(ЗаказыПоставщику, ПереченьРеквизитов, ИмяДокумента) Экспорт
	
	РезультатФункции = Новый Массив;
	
	ТекстРеквизиты = "";	
	
	Для каждого Реквизит Из ПереченьРеквизитов Цикл
		НакоплениеСтроки(ТекстРеквизиты, Реквизит);
	КонецЦикла;

	РеквизитыВТаблице = РеквизитыВТаблице(ЗаказыПоставщику, ТекстРеквизиты, ИмяДокумента);	
	НаборыРеквизитов = РеквизитыВТаблице.Скопировать();	
	НаборыРеквизитов.Свернуть(ТекстРеквизиты,"");
	
	Для каждого НаборРеквизитов Из НаборыРеквизитов Цикл
		
		ОтборРеквизитов = Новый Структура;
		Для каждого Элемент Из ПереченьРеквизитов Цикл
			ОтборРеквизитов.Вставить( Элемент, НаборРеквизитов[Элемент] );
		КонецЦикла; 
		
		ЗаказыПоставщикуРазбитые = Новый Массив;
		
		РеквизитыСтроки = РеквизитыВТаблице.НайтиСтроки(ОтборРеквизитов);
		Для каждого Элемент из РеквизитыСтроки Цикл
			ЗаказыПоставщикуРазбитые.Добавить(Элемент.Ссылка);
		КонецЦикла;
		
		РезультатФункции.Добавить(ЗаказыПоставщикуРазбитые);
		
	КонецЦикла;
	
	Возврат РезультатФункции;

КонецФункции

Функция РеквизитыВТаблице(ЗаказыПоставщику, ПереченьРеквизитов, ИмяДокумента)

	Запрос = Новый Запрос;
	Запрос.Текст = 
	"ВЫБРАТЬ
	|	ЗаказПоставщику.Ссылка КАК Ссылка
	|ИЗ
	|	Документ.ЗаказПоставщику КАК ЗаказПоставщику
	|ГДЕ
	|	ЗаказПоставщику.Ссылка В(&ЗаказыПоставщику)";
	
	Запрос.Текст = СтрЗаменить(Запрос.Текст, "Документ.ЗаказПоставщику", "Документ."+ИмяДокумента);
	Запрос.Текст = СтрЗаменить(Запрос.Текст, "КАК Ссылка", ","+ПереченьРеквизитов);

	Запрос.УстановитьПараметр("ЗаказыПоставщику", ЗаказыПоставщику);
	
	Возврат Запрос.Выполнить().Выгрузить();	

КонецФункции

Процедура НакоплениеСтроки(Основа, Добавление)

	Если Основа = "" Тогда
		Основа = Строка(Добавление);
	Иначе
		Основа = Основа + "," +Добавление;
	КонецЕсли;	

КонецПроцедуры

6. Проверка html средствами 1С.

Похожие задачи дают в ЕГЭ, на первом курсе некоторых институтов. Так что сын-студент мне помог. В нашей базе для каждого товара содержится небольшой html-текст, возникла потребность проверять, чтобы количество открытых и количество закрытых тегов были равны на каждом уровне вложенности. Процедура ПроверитьПолеHTML вызывается при записи номенклатуры. Теги <br /> ,<hr /> являются исключениями: их проверять не нужно. Штатная процедура проверки работает немного иначе, для решения не подходит. Код для обычного (не-управляемого) приложения.

Процедура ПроверитьПолеHTML(Отказ)
	
	СуществующиеТеги = СуществующиеТеги();
	СуществующиеТеги.Свернуть("ИмяТега, Уровень", "Знак");
	
	ТекстОшибки = "Не совпадает количество открытых/закрытых тегов.";
	
	Для каждого Тег Из СуществующиеТеги Цикл
		
		Если Тег.Знак <> 0 Тогда
			
			ТекстОшибки = ТекстОшибки  + Символы.ПС + "Имя тега " + Тег.ИмяТега + " Уровень " + Тег.Уровень;
			
		КонецЕсли;	
		
	КонецЦикла; 
	
	Если СтрЧислоСтрок(ТекстОшибки) > 1 Тогда
		
		ОбщегоНазначения.СообщитьОбОшибке( ТекстОшибки, Отказ);
		
	КонецЕсли;	

КонецПроцедуры

Функция СуществующиеТеги() 
	
	РезультатФункции = Новый ТаблицаЗначений;
	РезультатФункции.Колонки.Добавить("ИмяТега");
	РезультатФункции.Колонки.Добавить("Уровень");
	РезультатФункции.Колонки.Добавить("Знак");
	
	ТекущийСимвол = "";
	ПозицияВСтроке = 1;
	
	Пока ПозицияВСтроке < СтрДлина(ДополнительноеОписаниеНоменклатурыHTML) Цикл
		
		ПрошлыйСимвол = ТекущийСимвол;		
		ТекущийСимвол = Сред(ДополнительноеОписаниеНоменклатурыHTML,ПозицияВСтроке,1);
		
		Если ПрошлыйСимвол = "<" 
			И ТекущийСимвол = "/" Тогда
			
			ПозицияВСтроке = ПозицияВСтроке+1;
			ДобавитьТег(ПозицияВСтроке, РезультатФункции, -1);
			
		ИначеЕсли ПрошлыйСимвол = "<" 
			И ТекущийСимвол <> "/" Тогда
			
			ДобавитьТег(ПозицияВСтроке, РезультатФункции, 1);
			
		Иначе
			
			ПозицияВСтроке = ПозицияВСтроке + 1;			
			
		КонецЕсли;
		
	КонецЦикла; 
	
	Возврат РезультатФункции;
	
КонецФункции

Процедура ДобавитьТег(ПозицияВСтроке, РезультатФункции, Знак)

	ИмяТега = "";
	
	Пока ПозицияВСтроке <= СтрДлина(ДополнительноеОписаниеНоменклатурыHTML) Цикл
		
		ТекущийСимвол = Сред(ДополнительноеОписаниеНоменклатурыHTML,ПозицияВСтроке,1);
		ПозицияВСтроке = ПозицияВСтроке + 1;		
		
		Если ТекущийСимвол = ">" Или ТекущийСимвол = " " Тогда
			
			Если Найти(ИмяТега, "br") + Найти(ИмяТега, "hr") = 0 Тогда

				НоваяСтрока = РезультатФункции.Добавить();
				НоваяСтрока.ИмяТега = ИмяТега;
				НоваяСтрока.Уровень = ?(Знак = 1, РезультатФункции.Итог("Знак"), РезультатФункции.Итог("Знак")-1);
				НоваяСтрока.Знак = Знак;
				
			КонецЕсли;
			
			Возврат;			
			
		Иначе
			
			ИмяТега = ИмяТега + ТекущийСимвол;
			
		КонецЕсли;
		
	КонецЦикла;	

КонецПроцедуры

 

5. Пример отладки.

После обновления конфигурации при первом запуске вышла ошибка, с комментарием ОбщийМодуль.ДлительныеОперации.Модуль(376): Недопустимое значение параметра. По этому адресу находится код

ТекстОшибки = КраткоеПредставлениеОшибки(Задание.ИнформацияОбОшибке);
ВызватьИсключение(ТекстОшибки);

В отладчике настроил остановку, удалось получить более подробную информацию из переменной Задание.ИнформацияОбОшибке. Оказывается, ошибка происходила в модуле ОбновлениеИнформационнойБазы, код

ПланыОбмена.ЗарегистрироватьИзменения(Узел, Данные);

Тут не подходит безусловная остановка или остановка с условием. Останавливаться по каждой ошибке – тоже не подходит: слишком много ошибок завершения фоновых заданий. В отладчике настроил остановку по ошибке 

Нашел данные, на которых происходит ошибка регистрации и исключил их из обработки.

4. Потерянное регламентное задание.

После обновления конфигурации в журнале регистрации появились записи "{ОбщийМодуль.ОбщегоНазначения.Модуль(2385)}: Регламентное задание недоступно по функциональным опциям или не поддерживает работу в текущем режиме работы программы. Выполнение прервано.". Еще в сообщении есть название регламентного задания, но в диспетчере регламентных заданий такого задания нет. В режиме конфигуратора не удается найти, откуда задание запускается.

По названию задания нашел связанную с ним функциональную опцию. Остановил регламентные задания в консоли кластера. Включил опцию. Задание появилось в диспетчере задач. Отключил выполнение регламентного задания. Все просто. Для осознания этой простоты мне потребовалось всего пару дней. )))

Список функциональных опций для регламентных заданий находится модуль РегламентныеЗаданияПереопределяемый, процедура ПриОпределенииНастроекРегламентныхЗаданий(Настройки)

3. Нестандартное использование замера производительности.

Бывает, что код плохо структурирован, в нем трудно разобраться. )) Например, нам нужно быстро найти, какой макет используется в печатной форме. Запускаем замер производительности, выполняем печать, завершаем замер. Получаем листинг выполненных команд. Выполняем поиск (Ctrl+F) слов "Макет", выбираем нужную строку.

2. Попытка, еще попытка…

Используйте конструкцию Попытка … Исключение … КонецПопытки правильно. ))

Между Исключение … КонецПопытки обязательно должна находиться команда ЗаписьЖурналаРегистрации(); и возможно Сообщить(); ВызватьИсключение; Программа не должна прятать происходящие ошибки, их обязательно выводить в журнал. Кто-то должен ежедневно анализировать и устранять причины ошибок. Казалось бы, это банально, но многие ли из нас начинают день просмотром журнала регистрации ? Кстати, даже некоторые «коробочные» решения не всегда используют ЗаписьЖурналаРегистрации(). Поэтому приходится мучительно долго искать причину очередного "отказа программы".

Далее, не нужно создавать такую ситуацию, когда конструкция выполняется в транзакции, а внутри ее прячется еще одна транзакция, например

Процедура ПриЗаписи(Отказ)

Попытка

ДокументПродажи = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
...
ДокументПродажи.Записать();

Исключение
....
КонецПопытки.

...
КонецПроцедуры;

В этом случае конструкция "Попытка … Исключение … КонецПопытки" бесполезна и вредна. В случае, если во вложенной транзакции произойдет ошибка, внешняя транзакция тоже откатится, но причины никто не увидит. Это фатум.

 

1. Удачный пример блокировки

В нашей информационной системе есть некоторые неоптимальности. Куда же без них. Как указал С.Носков, идеальных программ не бывает. Процесс создания и печати документов у нас потреблял довольно много ресурсов, возникали взаимоблокировки с процессом перепроведения документов. Программа «вылетала» или прерывала пакетную печать. Очень неприятно. Но решить эту проблему «в лоб» долгое время не удавалось. Взаимоблокировки смотрел в технологическом журнале, для регистров накопления поставил галочки «разрешить разделение итогов» (это помогает производить параллельную запись в регистр, если регистр в модуле проведения используется только для записи) – стало немного лучше. Остались взаимоблокировки из-за разного порядка записи в регистры. Оба процесса используют много тысяч строк кода и сделать порядок записи регистров одинаковым практически невозможно. Постепенно пришло понимание, что на момент печати нужно приостанавливать перепроведение документов (выполняется регламентным заданием).

Вариант 1. Сделать блокировку чего-либо на время печати документов кодом типа

Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить();
ЭлементБлокировки.Область = …
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.УстановитьЗначение…
Блокировка.Заблокировать(); 

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

Вариант 2. Записывать в регистр сведений информацию о том, что регламентное задание должно ожидать некоторое время – тоже отпадает. Как обеспечить корректную работу такого регистра в случае ошибок, сбоев и аварийного завершения программы ?

Вариант 3. На помощь пришли объектные пессимистические блокировки, которые не используют транзакции. Когда пользователь начинает печатать – устанавливается блокировка. Регламентное задание перед тем как начать перепроведение – проверяет что установленных блокировок нет (для всех пользователей). Примеры кода:

Функция УстановитьБлокировку()
	Попытка
		ФлагБлокировки = ПараметрыСеанса.ТекущийПользователь.ПолучитьОбъект();
		ФлагБлокировки.Заблокировать();
		Возврат ФлагБлокировки;
	Исключение
		Возврат Неопределено;
	КонецПопытки;
КонецФункции

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

Функция ЕстьБлокировка(Пользователь)
Попытка
	Пользователь.ПолучитьОбъект().Заблокировать();
Исключение
	Возврат Истина;
КонецПопытки;
Возврат Ложь;
КонецФункции

Функция проверяет для выбранного пользователя, не установлена ли по нему блокировка. Если пользователей много – вызывайте в цикле. Штатная функция Заблокирован() почему-то не отрабатывает. Возможно, фича моей платформы 8.3.11.

Таким способом, удалось избавиться от взаимоблокировок в работе. Всем успехов !

has been added to your cart:
Оформление заказа