"Гибкие" блокировки в 1С 7.7 и MS SQL Server |
Как-то на форуме прочитал сообщение типа" а вот знакомый админ удалил Tablockx и поставил rowlock в хранимых процедурах и все закрутилось, завертелось"… Эта мысль, по-моему, достаточно показательна для многих из 1С-программистов и особенно для новичков. Для того, чтобы не наломать дров в вашей ИТ системе, необходимо : во-первых, понимать, для чего существуют блокировки в МS SQL Server , во-вторых, понимать, как устроен блокировочный механизм в 1С 7.7 и MS SQL. По первой части есть масса литературы, и поэтому не хотелось бы ее пересказывать…Отмечу лишь принципиальные моменты… В MS SQL есть понятие блокировок и подсказок блокировок. Основное предназначение - избежать проблем некорректного(грязное чтение, чтение фантомов и т.п.) чтения информации. Для 1С 7.7 значимы следующие виды блокировок Holdlock – Захватывает разделяемую блокировку до завершения транзакции. Nolock - Применима только к оператору select . Читает все… Tablock - Используется блокировка на уровне таблиц. Tablockx - Используется монопольная блокировка таблицы. Отдельно выделю подсказки блокировки, которые «горячие» головы рекомендуют применять Rowlock - блокировка на уровне строк Updlock - блокировка на изменение Readpast - Применима только для Select . Пропускаются строки блокированные( rowlock ) другими транзакциями. Для того чтобы понять, как действуют эти блокировки, необходимо почитать соответствующую литературу, а еще лучше самим проверить на практике эти блокировки в различных ситуациях… На специфике реализации блокировок в 1С 7.7 остановлюсь подробнее. Механизм блокировок в 1С 7.7 максимально простой - блокируется все и по максимуму. Интересней всего, конечно, реализация блокировок на документы т.к. как правило это и является самым узким местом системы, а также документы и являются источником конфликтов. Начнем с того, что в 1С 7.7 существует специальная таблица _1 sjourn, где хранятся внутренние идентификаторы всех документов. При записи, проведении и т.п. операциях с документами 1С накладывает блокировку на таблицу _1 sjourn и соответственно в системе в один момент времени может проводиться не более одного документа. То есть _1 sjourn выступает в роли своеобразного семафора. До тех пор, пока не завершиться транзакция и соответственно не будет снята блокировка с таблицы, все остальные клиенты будут ждать разблокировки и, самое интересное, как это они будут делать. В момент ожидания 1С 7.7 загружает ресурсы сервера, т.к. непрерывно сканирует таблицу на вопрос разблокировки и поэтому загружает процессор по максимуму.
![]()
![]()
Удалить механизм блокировок можно путем изменения хранимых процедур, через которые 1С 7.7 проверяют таблицы на блокировки. А точнее, удалить хинты на блокировку таблиц. Например, в хранимой процедуре _1 sp __1 SJOURN _ TLockX в конструкции select @i=1 from _1SJOURN(TABLOCKX HOLDLOCK) where 0=1 необходимо удалить TABLOCKX HOLDLOCK. Также необходимо удалить соответствующие хинты(подсказки блокировки) в остальных таблицах, относящихся к документам. Если этого не сделать, то будут возникать deadlock -и – взаимоблокировки транзакций на уровне SQL Server . Предположим, что мы это сделали. Казалось бы, все теперь хорошо , документы параллельно проводятся , процессор не загружается. Но не все так просто. Что бы доказать ошибочность этих выводов, приведем следующий эксперимент. Создадим конфигурацию : справочник - Товары, документ – ПриходнаяНакладная, документ – РасходнаяНакладная, регистр – ОстаткиТовара Приходная накладная нужна только для того что бы сделать одно единственное движение. Больше логической нагрузки она нести не будет. В модуле расходной накладной реализуем следующую логику : Запрос = СоздатьОбъект("Запрос");
Товар это у меня реквизит шапки, и поэтому я использую одномерную (например использую Запрос.Группировка(1) зная что движение по одному товару) модель. Для многомерной модели ничего принципиально не меняется… Логика вкратце сводится к тому, что необходимо, перед тем как списывать товар, провести анализ , а хватает ли его на складе? Казалось бы, при такой модели не должно возникать отрицательных остатков – ведь стоит проверка. Это возникающее на первый взгляд впечатление ошибочно. Для доказательства этого я вставил в середине модуля обработку, которая эмулирует задержку между получением остатков и их последующим анализом и записью движений. Получается следующая ситуация: когда два пользователя одновременно пытаются списать товара в сумме больше, чем есть на складе(в хронологическом порядке)
Возникают отрицательные остатки. С точки зрения бизнес логики, я думаю, не нужно комментировать, к чему это может привести. Как избежать этой неприятной ситуации? – Реализовать свой, гибкий механизм блокировок. Вот пример: Перем глСКЛ Экспорт; В данном случае я блокирую по объекту метаданных Регистр.ОстаткиТовара. А хранимые процедуры MyRG17_TLockX и MyRA17_TLockX я полностью дублирую текстом старых RG17_TLockX и RA17_TLockX. Вставляя процедуру ПроверкаБлокировкиРесурса( «Регистр.ОстаткиТовара» ) в начало модуля проведения, мы добиваемся того, что второй пользователь ожидает, пока первый не завершит транзакцию и только после этого он может прочитать остатки товара. При этом не возникает неоправданной нагрузки процессора, и документы проводятся последовательно, но только в контексте движения по Регистр.Остатки. Возникает вопрос: а что делать, если у нас происходит движение по бухгалтерским итогам? В этом случае движение затрагивает одну таблицу, и она становится узким местом системы. Возникает соблазн использовать конструкцию rowlock - ведь судя по названию она и сможет устанавливать блокировку не монопольную на таблицу, а построчную на изменяемые записи. Тут нужно во-первых понять, как хранит 1С данные и какими запросами получает данные. Я возьму для примера только регистры. Для бухгалтерских итогов концепция будет аналогична. Нужно отметить , что данные по регистрам хранятся в агрегированном виде т.е остатки по измерения хранятся в нескольких записях соответствующим периодичности итогов. Возникает вопрос: а не изменится ли целостность и непротиворечивость агрегированных данных при отключении стандартного механизма блокировок? Для ответа на этот вопрос нужно во-первых понимать, что SQL Server сам расставляет блокировки объектов при изменении данных. Убедиться в этом можно, изменив в транзакции запись таблицы(как правило на индексированные) и запустив sp _ lock . По результатам sp _ lock мы увидим что данная запись заблокирована, как будто если бы мы в явном виде поставили подсказку rowlock . Вкратце концепция изменения агрегированных данных (например регистра остатков) 1С 7.7 следующая – При движении по регистру вызывается ряд хранимых процедур но показательные из них две это _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С 7.7. В конце статьи хочется отметить, что для того чтобы реализовать механизм «гибких» блокировок, необходимы какие-либо навыки в MSSQL Server, а также понимание внутренней структуры 1С 7.7. Например снимая хинты с процедуры _1sp__1SSYSTEM_TLock вы тем самим можете попасть в ситуацию когда два проведенных документа перемещающие ТА проведутся одновременно и один из них окажется позже точки актуальности или наоборот сняв хинты с _1sp__1SJOURN_TLock и не сняв их с _1sp_DT???_TLockX вы можете создать deadlock -и. При использовании материалов данной статьи обязательна ссылка на информационный ресурс. По интересующим Вас вопросам просьба обращаться на softpoint@softpoint.ru
Статьи по этой теме: Что такое ошибка при транзакции, выполняемой другим пользователем? Технология "Гибкие блокировки" Экономическая эффективность внедрения технологии "Гибкие блокировки"
Перепечатка, воспроизведение в любой форме, распространение, в том числе в переводе, любых материалов с сайта www.softpoint.ru возможны только с письменного разрешения компании "СофтПоинт". Это правило действует для всех без исключения случаев, кроме тех, когда в материале прямо указано разрешение на копирование (основание: Закон Российской Федерации "Об авторском праве и смежных правах").
|