Имя: Пароль:
1C
1С v8
Блокировки в СУБД
0 unit_3q
 
14.04.15
08:49
Доброго дня, люди.

Пишу внешнюю обработку по изменению элементов справочника "Номенклатура", надо выставить всем элементам реквизит "ВестиУчетПоСериям" в "Ложь". Платформа 8.3.5.1119, конфигурация УПП 1.3.12.1, режим клиент-серверный, оборудование.. эээ.. хорошее.
Написал, работает. Но долго, т.к. элементов многовато. Подумал про многопоточность, посмотрел в сторону фоновых заданий. Переделал обработку, благо руки-время есть и информации в сети достаточно. Например, тут, у Гилева - http://xn----1-bedvffifm4g.xn--p1ai/articles/как-ускорить-1с-многопоточность/ .

Ок. Делаю 10 процессов, запускаю. Но из 10 процессов нормально отрабатывает свою порцию данных только 1, остальные 9 завершаются аварийно, с ошибкой "Ошибка при вызове метода контекста (Записать): Ошибка при выполнении обработчика - 'ПриЗаписи': {ОбщийМодуль.МодульСИ.Модуль(210)}: Ошибка при вызове метода контекста (Выполнить): Ошибка выполнения запроса: Конфликт блокировок при выполнении транзакции:
Microsoft SQL Server Native Client 10.0: Транзакция (идентификатор процесса 68) вызвала взаимоблокировку ресурсов блокировка | буфер связи с другим процессом и стала жертвой взаимоблокировки. Запустите транзакцию повторно.
HRESULT=80004005, SQLSrvr: SQLSTATE=40001, state=34, Severity=D, native=1205, line=1"

Предполагаю, что происходит эскалация блокировок. Изменил режим управления блокировками конфигурации с автоматического на управляемый. Не помогло. Может есть у кого еще предположения по устранению причины ошибки?

Код общего неглобального модуля, который выполняет обработку данных:
[code]
// тзНоменклатура - таблица значений:
//  1. Ссылка, ссылка на элемент справочника Номенклатура
//
Процедура УстановитьЗначениеНастройки(тзНоменклатура, ИндексНачала, РазмерПорции) Экспорт
    Для й = 1 По РазмерПорции Цикл
        НоменклатураОбъект = тзНоменклатура[ИндексНачала + й].Ссылка.ПолучитьОбъект();
        
        НоменклатураОбъект.ВестиУчетПоСериям = Ложь;
        НоменклатураОбъект.ВестиУчетПоСериямВНЗП = Ложь;
        НоменклатураОбъект.ВестиПартионныйУчетПоСериям = Ложь;
        
        НоменклатураОбъект.ОбменДанными.Загрузка = Истина;
        
        Попытка
            НоменклатураОбъект.Записать();
        Исключение
            ВызватьИсключение ОписаниеОшибки();
        КонецПопытки;
        
    КонецЦикла;
        
КонецПроцедуры
[/code]

Код обработки, которая создает фоновые задания:
[code]
Перем КоличествоПотоков;
Перем СоответствиеЗаданий;

Процедура КнопкаВыполнитьНажатие(Кнопка)
    
    Сообщить("Запуск: " + Строка(ТекущаяДата()));
    
    // 2. сбрасываем настройку в справочнике "Номенклатура"
    Состояние("Сбрасываем настройку в справочнике ""Номенклатура""..");
    
    Запрос = Новый Запрос;
    Запрос.Текст = "ВЫБРАТЬ
                   |    Номенклатура.Ссылка
                   |ИЗ
                   |    Справочник.Номенклатура КАК Номенклатура
                   |ГДЕ
                   |    (Номенклатура.ВестиУчетПоСериям = ИСТИНА
                   |            ИЛИ Номенклатура.ВестиУчетПоСериямВНЗП = ИСТИНА
                   |            ИЛИ Номенклатура.ВестиПартионныйУчетПоСериям = ИСТИНА)";
    
    тзНоменклатура = Запрос.Выполнить().Выгрузить();
    
    КоличествоНоменклатуры = тзНоменклатура.Количество();
    
    Сообщить("Элементов номенклатуры к обработке - " + Строка(КоличествоНоменклатуры) + ", " + Строка(ТекущаяДата()));
    
    РазмерПорции = Цел(КоличествоНоменклатуры / КоличествоПотоков);
    
    МассивЗаданий = Новый Массив;
    СоответствиеЗаданий = Новый Соответствие;
    
    Для НомерПотока = 1 По КоличествоПотоков Цикл
        // с какого индекса взять
        ИндексНачала = (НомерПотока - 1) * РазмерПорции;
        
        // сколько взять
        Если (НомерПотока = КоличествоПотоков) Тогда
            РазмерПорции = КоличествоНоменклатуры - (КоличествоПотоков * РазмерПорции) + РазмерПорции;
        КонецЕсли;
        
        // берем в обработку
        Параметры = Новый Массив;
        Параметры.Добавить(тзНоменклатура);
        Параметры.Добавить(ИндексНачала);
        Параметры.Добавить(РазмерПорции);
        
        Ключ = Новый УникальныйИдентификатор;
        Сообщить("    Старт потока № " + Строка(НомерПотока) + ", " + Строка(ТекущаяДата()) + ". Обработка записей с " + Строка(ИндексНачала) + ", размер порции " + Строка(РазмерПорции));
        Задание = ФоновыеЗадания.Выполнить("ОчисткаНоменклатуры.УстановитьЗначениеНастройки", Параметры, Ключ, "Поток Номер " + Строка(НомерПотока));
        
        МассивЗаданий.Добавить(Задание);
        СоответствиеЗаданий.Вставить(НомерПотока, Ключ);
    КонецЦикла;
    
    ПодключитьОбработчикОжидания("ВывестиРезультат", 5);
    
КонецПроцедуры

Процедура ВывестиРезультат()
    
    Для каждого элемент Из СоответствиеЗаданий Цикл
        // 1.
        Отбор = Новый Структура;
        Отбор.Вставить("Состояние", СостояниеФоновогоЗадания.Завершено);
        Отбор.Вставить("Ключ", элемент.Значение);
        
        МассивЗаданий = ФоновыеЗадания.ПолучитьФоновыеЗадания(Отбор);
        
        Если МассивЗаданий.Количество() > 0 Тогда
            Сообщить("    Поток № " + элемент.Ключ + " завершен успешно!");
            СоответствиеЗаданий.Удалить(элемент.Ключ);
        КонецЕсли;
        
        // 2.
        Отбор = Новый Структура;
        Отбор.Вставить("Состояние", СостояниеФоновогоЗадания.ЗавершеноАварийно);
        Отбор.Вставить("Ключ", элемент.Значение);
        
        МассивЗаданий = ФоновыеЗадания.ПолучитьФоновыеЗадания(Отбор);
        
        Если МассивЗаданий.Количество() > 0 Тогда
            Сообщить("    Поток № " + элемент.Ключ + " завершен аварийно! " + Строка(МассивЗаданий[0].ИнформацияОбОшибке.Описание), СтатусСообщения.Важное);
            СоответствиеЗаданий.Удалить(элемент.Ключ);
        КонецЕсли;
    КонецЦикла;
    
    Если СоответствиеЗаданий.Количество() = 0 Тогда
        ОтключитьОбработчикОжидания("ВывестиРезультат");
    КонецЕсли;
    
КонецПроцедуры

КоличествоПотоков = 10;
[/code]

Вторая процедура периодически просматривает фоновые задания на предмет завершения (нормального или аварийного) созданных.
1 ДенисЧ
 
14.04.15
08:54
по идее - неправильное поведение.
Но поиграйся с гибкими блокировками
2 shuhard
 
14.04.15
08:56
(0) [Но долго, т.к. элементов многовато.]
поставь загрузка = Истина
и не трогай блокировки, УПП 1.3.12.1 этого не переживёт
3 ДенисЧ
 
14.04.15
08:57
(2) Не проснулся?
НоменклатураОбъект.ОбменДанными.Загрузка = Истина;
4 бомболюк
 
14.04.15
08:59
работа я так понял разовая - может напрямую в SQL тогда? Или, если можно, удаляем реквизит в конфигураторе и создаем заново ;-)
В модуле объекта справочника "Номенклатура" ничего интересного нет, что могло бы вызывать блокировки?
5 unit_3q
 
14.04.15
09:00
Пока играюсь на экземпляре базы для разработки и отладки, так что тут пока можно все. Можно и первый вариант обработки оставить, просто хочу понять в чем проблема.
6 unit_3q
 
14.04.15
10:26
(4) Спасибо за рекомендацию. В модуле объекта справочника номенклатуры даже при записи с параметром ОбменДанными.Загрузка = Истина  осуществлялся заход в обработчик процедуры "ПриЗаписи". А там... код для взаимодействия с базой Oracle, писанный незнамо когда (еще до меня программисты писали). Прописал возврат, если ОбменДанными.Загрузка = Истина. Проблема исчезла.

12 тыс. записей в десяти потоках обрабатываются менее чем за 1 минуту.

Вариант с работы напрямую через SQL не рассматривал. Там еще много чего надо сделать. Вообще задача поставлена отключить учет по сериям номенклатуры и вычистить информацию о сериях из базы. Было желание сделать все в одной обработке. Оно реализовано. А мультипоточность - это типа гимнастики для ума.

Всем спасибо.