Информационные системы
Архив выпусков рассылки
 

1С:Предприятие

Учебник
Рассылка
Курс лекций
Статьи
Программы
Ссылки

Информационные технологии

Инф. системы
Технология .Net
Модель города
Книги и статьи

ЭРА

Интересное

Психология
Философия
Фантастика
Помощь студенту
Гостевая книга
Ссылки

Форум

mista.ru / Рассылка / Выпуск 80

Волшебство программирования на 1С:Предприятие 7.7 и 8.0
Выпуск 80 / 10.10.2004

«Гибкие» блокировки в 1С 7.7

Автор: Владимир Сердюк

Источник: http://www.softpoint.ru/article_id1.htm

Как-то, помню, на форуме прочитал сообщение типа «а вот знакомый админ удалил Tablockx и поставил rowlock в хранимых процедурах и все закрутилось, завертелось»… Эта мысль по-моему достаточно показательна для многих из 1С программистов и особенно для новичков.

Для того чтобы не наломать дров в вашей ИТ системе необходимо : во первых понимать для чего существуют блокировки в М S SQL server , во вторых понимать как устроен блокировочный механизм в 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) зная что движение по одному товару) модель. Для многомерной модели ничего принципиально не меняется…

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

  1. Первый пользователь получает остатки
  2. Второй пользователь получает остатки
  3. У первого пользователя идет какая либо обработка.(в моем случае выводится сообщение)
  4. Второй пользователь проводит проверку остатков и списывает товар. Конец проведения.
  5. Первый пользователь проводит анализ остатков и тут возникает самое интересное – информация по остаткам уже устарела! Но первый пользователь об этом не подозревает , проходит проверка остатка и списание.
  6. КонецПроведения.
  7. Возникают отрицательные остатки. С точки зрения бизнес логики я думаю не нужно комментировать к чему это может привести.

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

Вот пример:

Перем глТои Экспорт;

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

   Иначе
      Сообщить("Ждет Занят MyRA17_TLockX");
   КонецЕсли;
   Если(глТои.ВыполнитьЗапрос("exec MyRG17_TLockX") = 1) тогда

   Иначе
      Сообщить("Ждет Занят MyRG17_TLockX");
   КонецЕсли;
КонецЕсли;
глТои.Закрыть();

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

Загрузчик = CreateObject("Toy.Loader");
Хэндл = Загрузчик.LoadLibrary("C:Program Files1Cv77BINtoysql21.dll");
глТои = СоздатьОбъект("ToyQuery");

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

А хранимые процедуры 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 -и.

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


Поэма "Многоцветье жизни, или вопросы про цветные запросы"

Автор: Композитор

По мотивам ветки Что такое "черный запрос" в 1С?

Черным бывает банан перезрелый,
Черный, как правило, негр загорелый.
Черной бывает на небе дыра,
Черным быть может гнездо и нора.
Черной была форма немца-фашиста,
В черном мотают срок рецидивисты.
Черным по белому пишет поэт,
Шайба черна, черен Мэн, тот, что Бэт.
В черных трусах выступает Торпедо,
В черных штанах ходят парни-скинхэды.
Черным был, есть и останется кот,
Тот, про которого Жанна поет.
Черный квадрат вдруг прославил Малевича,
Черен Хрущев (частью) на Новодевичьем.
Черный копатель вскрыл скифский курган,
Черною смертью зовут ураган.
Часто волнуется Черное море,
Черными лица становятся в горе.
Черным рисует художник углЕм,
Черная нефть пахнет черным рублем.

Черным любой из предметов быть может.
Только ЗАПРОСУ быть черным негоже.

                  ----------

Сахар зовут часто белою смертью,
Вьюга с утра белый снег круговертит.
В сАаване белом служитель Аллаха
Горца хоронит (тот умер без страха).
Белый баран принесен будет в жертву,
Белым дурманом торгует Антверпен.

Белым бывает любой из предметов,
Есть ли в природе ЗАПРОС белый? Нету.

                  ----------

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

Кровь голубою бывает порой,
Слышал ли кто про ЗАПРОС голубой?

                  ----------

Красной бывает ячейка в таблице
Красная с молодцем ходит девица.
Красная площадь гремит на весь мир.
В красном гробу лежит чей-то кумир.

Солнышко красным встаёт и заходит,
Красным смущенье на лица приходит.
Красному стягу солдатик клянётся,
В точках горячих кровь красная льётся.

Но не теряйте вы сил понапрасну -
Сделать ЗАПРОС не получится красным.

                  ----------

*Выделенное курсивом - Автор: Волшебник.


Смелее задавайте вопросы по 8.0 и 7.7 на форуме www.forum.mista.ru

Используйте поиск и отбор по рубрике - это очень удобно,
а также экономит наше и ваше время.


См. также:
1С:Предприятие 7.7: учебник, курс лекций, статьи, ссылки
Книга "Разработка в системе 1С:Предприятие 8.0" - http://www.v8.1c.ru/book/v8develop.htm

Официальный сайт рассылки - www.mista.ru
Волшебный форум -
www.forum.mista.ru,
Форум по 1С - http://www.forum.mista.ru/index.php?forum=1c
Архив выпусков этой рассылки - http://www.mista.ru/subscribe

Новое!
Раздел форума исключительно по 8.0 -
http://www.forum.mista.ru/index.php?forum=1c&v8=1

С уважением,
Станислав Митичкин (Волшебник)
stasmit@mail.ru


<< Предыдущий выпуск | Список выпусков | Следующий выпуск >>
 

© Станислав Митичкин
www.mista.ru
, 1997-2003
1C:TOP-100
Волшебный форум