www.mamboteam.com
ДС бибилиотека  
Главная arrow Статьи по 1С arrow "Гибкие" блокировки в 1С SQL
18.06.2018 г.
 
 
Главное меню
Главная
Каталог книг
Ссылки
Поиск
Правообладателям
Статьи по 1С
1С для начинающих
Rambler's Top100
"Гибкие" блокировки в 1С SQL Печать E-mail
Рейтинг: / 0
ХудшаяЛучшая 
Автор Administrator   
24.09.2008 г.
На специфике реализации блокировок в 1С остановлюсь подробнее.

Механизм блокировок в 1С максимально простой - блокируется все и по максимуму.

Интересней всего, конечно, реализация блокировок на документы т.к. как правило это и является самым узким местом системы, а также документы и являются источником конфликтов.
В MS SQL есть понятие блокировок и подсказок блокировок. Основное предназначение - избежать проблем некорректного(грязное чтение, чтение фантомов и т.п.) чтения информации.

Для 1С значимы следующие виды блокировок

Holdlock – Захватывает разделяемую блокировку до завершения транзакции.

Nolock - Применима только к оператору select . Читает все…

Tablock - Используется блокировка на уровне таблиц.

Tablockx - Используется монопольная блокировка таблицы.

Отдельно выделю подсказки блокировки, которые «горячие» головы рекомендуют применять

Rowlock - блокировка на уровне строк

Updlock - блокировка на изменение

Readpast - Применима только для Select . Пропускаются строки блокированные( rowlock ) другими транзакциями.

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

На специфике реализации блокировок в 1С остановлюсь подробнее.

Механизм блокировок в 1С максимально простой - блокируется все и по максимуму.

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

Начнем с того, что в 1С существует специальная таблица _1 sjourn, где хранятся внутренние идентификаторы всех документов. При записи, проведении и т.п. операциях с документами 1С накладывает блокировку на таблицу _1 sjourn и соответственно в системе в один момент времени может проводиться не более одного документа. То есть _1 sjourn выступает в роли своеобразного семафора. До тех пор, пока не завершиться транзакция и соответственно не будет снята блокировка с таблицы, все остальные клиенты будут ждать разблокировки и, самое интересное, как это они будут делать. В момент ожидания 1С загружает ресурсы сервера, т.к. непрерывно сканирует таблицу на вопрос разблокировки и поэтому загружает процессор по максимуму.

Удалить механизм блокировок можно путем изменения хранимых процедур, через которые 1С проверяют таблицы на блокировки. А точнее, удалить хинты на блокировку таблиц.

Например, в хранимой процедуре _1 sp __1 SJOURN _ TLockX в конструкции select @i=1 from _1SJOURN(TABLOCKX HOLDLOCK) where 0=1 необходимо удалить TABLOCKX HOLDLOCK.

Также необходимо удалить соответствующие хинты(подсказки блокировки) в остальных таблицах, относящихся к документам. Если этого не сделать, то будут возникать deadlock -и – взаимоблокировки транзакций на уровне SQL Server .

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

Создадим конфигурацию : справочник - Товары, документ – ПриходнаяНакладная, документ – РасходнаяНакладная, регистр – ОстаткиТовара Приходная накладная нужна только для того что бы сделать одно единственное движение. Больше логической нагрузки она нести не будет.

В модуле расходной накладной реализуем следующую логику : 
Запрос = СоздатьОбъект("Запрос");
 ТекстЗапроса =
 "//{{ЗАПРОС(Сформировать)
 |Товар = Регистр.Остатки.Товар;
 |Количество = Регистр.Остатки.Количество;
 |Функция КоличествоКонОст = КонОст(Количество);
 |Группировка Товар;
 |"//}}ЗАПРОС
 ;

 Если Запрос.Выполнить(ТекстЗапроса) = 0 Тогда
  Возврат;
 КонецЕсли;

 Сообщить(НомерДок);
 Если Число(НомерДок)=1 Тогда
 Для Сч6=1 По 10000 Цикл 
  Сообщить("Ждем второго. Вообще то здесь может идти
  | какой-то анализ или обработка данных.
  |Чем больше интервал между получением остатков и их
  | анализом с последующей записью движений -
  |тем больше вероятность возникновения отрицательных остатков");
 КонецЦИкла;
 КонецЕсли;


 Запрос.Группировка(1);
 Если (Запрос.КоличествоКонОст<0) Тогда  
  Сообщить("Ошибка!!!");
  Возврат;  
 КонецЕсли; 
 
 Если Запрос.КоличествоКонОст>=Количество Тогда
  Регистр.Остатки.Товар=Товар;
  Регистр.Остатки.Количество=Количество;
  Регистр.Остатки.ДвижениеРасходВыполнить();
 Иначе
 
  Сообщить("Не хватает!");
  Возврат;
 КонецЕсли;

 

Товар это у меня реквизит шапки, и поэтому я использую одномерную (например использую Запрос.Группировка(1) зная что движение по одному товару) модель. Для многомерной модели ничего принципиально не меняется…

Логика вкратце сводится к тому, что необходимо, перед тем как списывать товар, провести анализ , а хватает ли его на складе? Казалось бы, при такой модели не должно возникать отрицательных остатков – ведь стоит проверка. Это возникающее на первый взгляд впечатление ошибочно. Для доказательства этого я вставил в середине модуля обработку, которая эмулирует задержку между получением остатков и их последующим анализом и записью движений. Получается следующая ситуация: когда два пользователя одновременно пытаются списать товара в сумме больше, чем есть на складе(в хронологическом порядке)
Первый пользователь получает остатки 
Второй пользователь получает остатки 
У первого пользователя идет какая либо обработка.(в моем случае выводится сообщение) 
Второй пользователь проводит проверку остатков и списывает товар. Конец проведения. 
Первый пользователь проводит анализ остатков и тут возникает самое интересное – информация по остаткам уже устарела! Но первый пользователь об этом не подозревает , проходит проверка остатка и списание. КонецПроведения. 

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

Как избежать этой неприятной ситуации? – Реализовать свой, гибкий механизм блокировок.

Вот пример:
Перем глСКЛ Экспорт;

Процедура ПроверкаБлокировкиРесурса(Ресурс) Экспорт;  
глСКЛ.Закрыть();
глСКЛ.Соединиться(0);
//глСКЛ.ВыполнитьЗапрос("set lock_timeout 20000");
  Состояние("Проверка блокировки...");
Если Ресурс = "Регистр.ОстаткиТовара" Тогда
Если (глСКЛ.ВыполнитьЗапрос("exec MyRA17_TLockX") = 1) тогда  
 
Иначе 
  Сообщить("Ждет Занят MyRA17_TLockX");
КонецЕсли;
Если(глСКЛ.ВыполнитьЗапрос("exec MyRG17_TLockX") = 1) тогда  
 
Иначе 
  Сообщить("Ждет Занят MyRG17_TLockX");
КонецЕсли;
КонецЕсли;
глСКЛ.Закрыть();  

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

В данном случае я блокирую по объекту метаданных Регистр.ОстаткиТовара.

А хранимые процедуры MyRG17_TLockX и MyRA17_TLockX я полностью дублирую текстом старых RG17_TLockX и RA17_TLockX. 

Вставляя процедуру ПроверкаБлокировкиРесурса( «Регистр.ОстаткиТовара» ) в начало модуля проведения, мы добиваемся того, что второй пользователь ожидает, пока первый не завершит транзакцию и только после этого он может прочитать остатки товара. При этом не возникает неоправданной нагрузки процессора, и документы проводятся последовательно, но только в контексте движения по Регистр.Остатки.

Возникает вопрос: а что делать, если у нас происходит движение по бухгалтерским итогам? В этом случае движение затрагивает одну таблицу, и она становится узким местом системы.

Возникает соблазн использовать конструкцию rowlock - ведь судя по названию она и сможет устанавливать блокировку не монопольную на таблицу, а построчную на изменяемые записи.

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

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

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

Для ответа на этот вопрос нужно во-первых понимать, что SQL Server сам расставляет блокировки объектов при изменении данных. Убедиться в этом можно, изменив в транзакции запись таблицы(как правило на индексированные) и запустив sp _ lock . По результатам sp _ lock мы увидим что данная запись заблокирована, как будто если бы мы в явном виде поставили подсказку rowlock .

Вкратце концепция изменения агрегированных данных(например регистра остатков) 1С следующая –

При движении по регистру вызывается ряд хранимых процедур но показательные из них две это _1sp_GetNextPeriod - обеспечивает последовательное обновление агрегированных данных и _1sp_RG17_Change – собственно изменят конкретную запись. Для этого используется следующая конструкция:

Update RG17 set SP19=Case When ABS(SP19+@p3)>9999999999 Then 9999999999 Else SP19+@p3 End where PERIOD=@per AND SP18=@p1 AND SP24=@p2 if @@ROWCOUNT=0 insert into RG17 values(@per,@p1,@p2,@p3) 

Как мы видим, в конструкции Update нет хинта rowlock . Но по большому счету он и не нужен т.к. сервер сам расставляет блокировки по измененным записям. В этом опять-таки не сложно убедиться, если провести ряд тестов, вкратце их смысл - искусственно создать ситуацию, при которой одна сессия будет изменять запись и другая ее будет изменять, но на основании прочитанных ранее устаревших данных. Вставка новых записей в агрегационную таблицу по документу происходит, как правило не более одной(если конечно точка актуальности не установлена абы как) и соответственно ситуация когда один документ делает вставку( insert into RG 17 values (@ per ,@ p 1,@ p 2,@ p 3) ) а другой ее не видит практически невозможна(кроме того, стоит ограничение на уровне уникальности индекса, для возможной повторной вставки).

Теперь что касается грязного чтения. С этим дело обстоит хуже и к сожалению никакие установки rowlock в этом не помогут. Во-первых, информация по изменению должна пройти по всем агрегационным записям в пределах транзакции(хоть и маловероятно что в этот момент возникнет конфликт). Во-вторых, 1С в запросах использует хинт nolock , что вообще не оставляет шансов на создание универсального блокировочного механизма. Да и нужен ли он?

Обращаясь к примеру с созданием процедуры ПроверкаБлокировкиРесурса продолжая аналогию, можно прийти к выводу о достаточности реализации блокировки объекта данных. Ну например, если мы хотим , что бы блокировалась не целиком таблица проводок, а например, только информация, связанная со счетом 41 и 004, то нам достаточно заблокировать объект СчетПоКоду(«41») и СчетПоКоду(«004»). То есть если мы знаем, что расходная накладная использует данные по 41 и 004 счетам нам достаточно вставить в начале модуля проведения процедуру ПроверкаБлокировкиОбъекта(« СчетПоКоду(«41») ») и ПроверкаБлокировкиОбъекта(« СчетПоКоду(«004») ») .

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

Собственно, а как все вышеперечисленное реализовать. Вариантов несколько. Но раз мы уже остановились на классическом блокировочном механизме MS SQL, то можно воспользоваться процедурой sp_getapplock. Смысл ее в следующем – блокировка произвольного ресурса. Запустив sp _ lock мы можем убедиться, что это всего лишь еще одна запись. То есть например блокировку по складу(в контексте остатков по складу) можно сделать следующим образом sp_getapplock 'Остатки**Склад**Основной', 'Exclusive', 'transaction' . Соответственно аналогичная процедура выполненная из Сессии №2 сессии будет ждать завершения транзакции открытой Сессией №1 если в ней была запущенна sp_getapplock такими же параметрами. Процедура ПроверкаБлокировкиОбъекта всего лишь преобразует объект в уникальный идентификатор и передает его в качестве параметра в sp_getapplock в открытой сессии 1С.

В конце статьи хочется отметить, что для того чтобы реализовать механизм «гибких» блокировок, необходимы какие-либо навыки в MSSQL Server, а также понимание внутренней структуры 1С. Например снимая хинты с процедуры _1sp__1SSYSTEM_TLock вы тем самим можете попасть в ситуацию когда два проведенных документа перемещающие ТА проведутся одновременно и один из них окажется позже точки актуальности или наоборот сняв хинты с _1sp__1SJOURN_TLock и не сняв их с _1sp_DT???_TLockX вы можете создать deadlock -и.

 
« Пред.   След. »
Новые поступления

1С:Предприятие 8. Руководство разработчика 8.2
 

1С:Предприятие 8. Руководство администратора 8.2
 

Интернет-Курс Расчет зарплаты и управление персоналом в 1С Предприятии 8.0
 

1С:Предприятие 7.7 Регламентированная отчетность 1С:Предприятие 7.7 Регламентированная отчетность за I квартал 2009 года
 

Документация по 1С 7.7
 

Самые скачиваемые
Документация по 1С 7.7 (67203)
1С:Предприятие 8. Руководство администратора 8.2 (10303)
Армагеддон (6423)
1С:Предприятие 7.7 Регламентированная отчетность 1С:Предприятие 7.7 Регламентированная отчетность за I квартал 2009 года (3963)
Интернет-Курс Расчет зарплаты и управление персоналом в 1С Предприятии 8.0 (3556)
Единая Справочно - Информационная Система 1С:Предприятие 8.0 (3196)
1С:Предприятие. Бухгалтерский учет. Секреты работы (2848)
1С Бухгалтерия. Пошаговые инструкции для начинающих (2023)
Профессиональная разработка в системе 1С:Предприятие 8 (1755)
1С:Предприятие 8.0. Полное описание функциональных возможностей и приемов работы (1753)
Rambler's Top100
 
Top! Top!