www.mamboteam.com
ДС бибилиотека  
Главная
18.01.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 (66509)
1С:Предприятие 8. Руководство администратора 8.2 (10076)
Армагеддон (5507)
1С:Предприятие 7.7 Регламентированная отчетность 1С:Предприятие 7.7 Регламентированная отчетность за I квартал 2009 года (3708)
Интернет-Курс Расчет зарплаты и управление персоналом в 1С Предприятии 8.0 (3387)
Единая Справочно - Информационная Система 1С:Предприятие 8.0 (3041)
1С:Предприятие. Бухгалтерский учет. Секреты работы (2758)
1С Бухгалтерия. Пошаговые инструкции для начинающих (1928)
Профессиональная разработка в системе 1С:Предприятие 8 (1654)
1С:Предприятие 8.0. Полное описание функциональных возможностей и приемов работы (1640)
Кто на сайте?
Сейчас на сайте находятся:
2 гостей
Rambler's Top100
 
Top! Top!