Что такое дедлок 1с
Помогите понять текущую картину.
Управляемый режим:
В ИТС статье написано, что дедлок такого типа возможен при чтении без установки блокировки и последующая запись - но как такое возможно. Ведь после чтения ресурса блокировка с него будет снята!
Нет в платформе метода снять управляемую блокировку!
Блокировки снимаются только завершением транзакции.
Дедлок по сценарию повышения уровня блокировки в упрявляемом режиме возможен только при установке разделяемой блокировки с последующей записью.
(5)вот и я так думаю.
Для дедлока надо сначала заблокировать разделяемой, а потом повысить до исключительной
Если читаешь данные без установки блокировок - то по умолчанию режим чтения - read committed. При записи разработчик обязан в управляемом режиме сам управлять блокировками и уровнями изоляции. Соответственно, если запись набора записей регистра - это свойство БлокироватьДляИзменения (блокируется весь набор, блокировка снимается после записи набора), в остальных случаях - открывать транзакцию, блокировать нужные данные с помощью объекта БлокировкаДанных, закрывать транзакцию.
(8) посмотри, что происходит в коде при одновременном чтении и записи. Кто-то из них пытается установить более пессимистичную блокировку
(0) "повышение уровня блокировки" это с разделяемой до исключительной.
Лочит твоя транзакция сначала разделяемой потом исключительной один и тот же объект.
как читаешь? а)запросом? или б)объект получаешь или набор записей читаешь?
Просто где-то в последних релизах 8.2 умники в 1С решили, что если объект читается в транзакции то непременно надо воткнуть разделяемую блокировку (внутренее).
(15)только если в коде написано Новый БлокировкаДанных - то уже да, до конца транзакции. А если не написано, то не лочится
(12) Перевести с разделяемой на исключительную блокировку - это повысить уровень блокировки. Т.е. нам нужно усилить имеющуюся блокировку, увеличить количество конфликтов в таблице совместимостей. На мой взгляд, это достаточно понятный термин.
(23) Для управляемого режима написано следующее:
"Обратите внимание на то, что взаимоблокировки в данном случае не произошло. Вместо этого была нарушена бизнес-логика системы"
(24) Менеджер блокировок работает над СУБД, у него нет информации о данных, он не может знать, что запрос нам вернёт и что он пройдёт по пути, автоматическая блокировка при чтении средствами менеджера 1С невозможна. При записи используется объектная техника и всегда есть конкретные записываемые данные - вот тут менеджер блокировок может автоматически выставить исключительную блокировку.
(27)Я не говорил ничего про автоматические блокировки. Я имел в виду что будет вставать разделяемая управляемая.
Полностью согласен с (27).
Хотя вот я уже специально проверил на 8.2.19.83 - нет ее.
Хм.. тогда о чем же мы говорили с Рупасовым.. склероз у меня чтоли, старость не радость..
сори кого обидел..
(29) Чисто теоретически, могли повысить уровень изоляции до REPEATABLE READ. Тогда прочитанные данные будут блокироваться до конца транзакции. Но это нонсенс.
Думаю, просто не поняли друг-друга.
(19), (22) друзья, я знаю, что по факту это включение/выключение разделения итогов
(31) Да ты в общем неверно пишешь, при записи разработчик ничего не должен делать как раз - управляемая блокировка при записи встаёт самостоятельно. То, что блокировка снимается после записи, тоже неверно - она снимается при окончании транзакции.
(31) Блокировка снимается после окончания транзакции - абсолютно верно, а когда заканчивается транзакция в случае записи набора записей регистра?
Как раз таки именно разработчик должен позаботиться о том, чтобы грамотно наложить нужные блокировки на нужные данные при их записи. Возможно ли чтение этих данных в процессе записи или невозможно. Какой уровень изоляции выставить и т.д., какие конкретно данные блокировать - это все на совести того, кто непосредственно внедряет многопользовательскую систему.
В автоматическом режиме работы - блокируется вся таблица, куда пишутся данные, многопользовательская работа невозможна. В управляемом - разработчик сам обязан все проконтролировать, вот что имел ввиду. Надо смотреть в коде чтения и записи, что там происходит, может чтение пытается выставить исключительную блокировку
(33) Запись набора записей не заканчивает транзакцию. Например, там может быть запись следующего набора записей потом. Если речь про операцию .Записать(), находясь вне транзакции, то тогда безусловно - она закончится после записи, но и смысла в каких-либо управляемых блокировках в таком случае нет.
Разработчик не должен заботиться о наложении блокировок при записи на записываемые данные, они и сами накладываются прекрасно. Разработчику следует чесаться только тогда, когда ему по какой-либо причине нужно наложить более широкую блокировку, чем просто на записываемые данные. Например, в упомянутом тобой случае с разделителем.
Разработчик не выставляет уровни изоляции транзакций. Всё его манипулирование уровнями изоляций сводится к выставлению автоматическиго/управляемого режима (serializable-repeatableread/readcommitted), а также использованию или не использованию НачатьТранзакцию() при чтении данных (readuncommitted или repeatableread-readcommitted).
В автоматическом режиме вся таблица блокируется только при использовании СУБД-версионников и файлового варианта (который тоже версионник).
(36) как раз сейчас внедряем. Одновременная запись и чтение из разных БД в одну и ту же таблицу и не стандартными средствами платформы 1С, между прочим, хотя и из нее.
Вместо того, чтобы переходить на личности, если не согласны опровергните аргументированно.
Необходимо, чтобы в одну и ту же таблицу писались одновременно данные. Одинаковый набор всех полей-колонок возможен. Необходимо реализовать, чтобы записываемые данные не читались до тех пор, пока не запишется весь пакет строк. Необходимо, чтобы чтение данных не мешало записи и не замедляло ее работу. Запись не должна ждать чтение.
Как будете реализовывать?
(38) 1) версионная схема с read committed.
Пункт с замедлением работы неясен - ресурсы СУБД не бесконечны, любая операция влияет.
(33) запись набора производится в СВОЕЙ, отдельной транзакции, во вложенной. Легко проверить, если в документе, проводимом по нескольким регистрам в одном из регистров выставить автоматический режим - вывалится ошибка.
(43) Я даже не знаю как это комментировать, чтобы не обидеть. Ну посмотрите профайлером начало и конец транзакции. Или попробуйте откатить только запись регистра, продолжая проведение документа. Или посмотрите блокировки в sp_locks после того, как все регистры записаны. Можно еще почитать ИТС.
(43) профайлером не проверял.
Режим проведения документа - управляемый. Режим одного из регистров, непосредственно по которому документ не проводится - автоматический. В итоге:
Ошибка при выполнении обработчика - 'ПриЗаписи'. Ошибка использования Менеджера блокировок
Автоматический режим блокировки недопустим в этой транзакции.
Столкнувшись раз с такой ошибкой, решил, что запись наборов делается во вложенных транзакциях. Ошибаюсь?
На минуточку, мы тут выяснили выше, что блокировки выставляет и снимает открываемая транзакция. 10 регистров, все в управляемом, один в автоматическом. И?
Если документ проводится в упр режиме, транзакция открыта в управляемом режиме, явно или нет. Так почему же он тогда не игнорирует свойство регистра? И зачем тогда вообще это свойство, если можно было бы обойтись только свойством документа? Эй, "знатоки" (45), (46), поделитесь соображениями
(49)Об этом написано в документации. Читать пробовали? Для этого не надо быть "знатоком".
Читать, только читать, писать еще рано.
(49) Какими соображениями? Это тупо проверка платформы на совместимость режимов. Остальное - твои домыслы.
(51) ну так дай ссылку, ткни так сказать носом нерадивого, а уж потом только взывай к всевышнему. Я если не прав - признаю и скажу спасибо, что наставил на путь истинный, а пока так - типа ты, холоп, недостоин еще с барином спор вести. Несолидно
Если туда нет доступа, то это: Руководство программиста 9.3.5
(55) я предполагаю, что записи наборов регистров производятся во вложенных транзакциях. На этот вывод меня натолкнула ситуация из (47), а также статья:
Господа (44), (45) и (46) изволили посмеяться, однако контраргументов не привели.
А еще небольшой раздел 9.2.2 вам будет очень полезен. :)
Чтобы вы не пугали своих заказчиков "вложенными" транзакциями, они ведь тоже могут оказаться людьми грамотными.
(58) О, ну да, "вложенный" - конечно не то слово, которое верно отражает суть, но для простого понимания, "на пальцах" - почему бы и нет, спасибо, месье
(57) Какие тут могут быть "аргументы"? Берешь и проверяешь. Есть ТЖ, есть профайлер, раздолье для любых проверок.
Лениво тебе что-то объяснять, ты явно не стремишься к пониманию.
Зато не стесняешься писать откровенный бред про снятие блокировок после записей регистров, ручные блокировки при записи объектов, управление уровнями изоляций и так далее.
(60) последняя попытка вступить в конструктивный спор.
1. "снятие блокировок после записей регистров" - я уверен, что блокировки устанавливаются и снимаются транзакцией, в рамках которой производится запись набора. Также считаю, что набор записывается в рамках отдельной транзакции (раздел 9.3.5, ситуация из (47)),слово "вложенный" применять не буду, хотя на ИТС спокойно оперируют понятием "транзакция верхнего уровня". Но, думаю, понятно, о чем идет речь. Запись набора заканчивается фиксацией транзакции, поэтому "снятие блокировок после записей регистров".
2. "ручные блокировки при записи объектов, управление уровнями изоляций" - я попытался озвучить общий принцип, чтобы обратить внимание автора, где искать решение проблемы, на возможную коллизию чтения и записи или параллельной записи. Верность этих утверждений могу подтвердить практическим примером: при работе напрямую с СУБД, необходимо явно указывать уровень изоляции транзакции и блокировки. У объекта ADO, для наглядности, для этого есть свойства IsolationLevel и LockType.
Кажется, аргументировано? Опровергните или укажите на ошибки.
(60) вы сами, в (44) проверяли где и как открываются и фиксируются транзакции при проведении документа по нескольким регистрам? или голословно?
Нет чтобы сказать - простите дурака, у меня в голове каша из транзакций СУБД и транзакций 1С. Нужно права качать.
(61)
1. Друг, фразы вроде "я уверен" не имеют никакого отношения к "конструктивному спору". Тебе предложено проверить - ты отказываешься.
Выдержка из документации, надеюсь, достаточно конструктивна: "Это означает, что всегда действует только транзакция самого верхнего уровня. Все транзакции, вызванные внутри уже открытой транзакции, фактически относятся к той же транзакции, а не образуют вложенную транзакцию"
2. А управляемый и автоматический режимы, про который ты при этом пишешь, они в каком свойстве объекта ADO находятся?
(62) Я смотрел километры логов ТЖ.
(63) никто ничего не качает, лично я пытаюсь разобраться. Проверять тоже не отказывался, разве где-то написал об этом? с удовольствием проверю, благо и задачи как раз по теме.
Самое смешное, что мне неясно, каким бы образом повлияло использование вложенных транзакций СУБД (если бы они имели место) на профильное обсуждение.
Хорошо, тогда кто-нибудь, объясните, почему возникает ситуация в (47)? Еще раз, режим работы конфигурации - автоматический и управляемый. Документ проводится, записывается и удаляется в управляемом режиме. Один из регистров - в автоматическом. Транзакция по документу открыта в управляемом режиме, одна из транзакций по регистру пытается открыться в автоматическом до момент фиксации "самой первой" (давайте так говорить) транзакции - ошибка. Выходит, все-таки набор записывается в своей транзакции, разве нет? Да, она в рамках "самой первой", но тем не менее?
(67) Нет. Признак режима блокировок для регистра - это всего лишь контролька. Признак. Галка. Чтобы исключить запись в одну и ту же таблицу с использованием разных идеологий блокировок.
(67) Вся эта ситуация - конкретный способ реализации смешанного режима блокировок в 1с. В автоматическом режиме у нас просто идут запросы в СУБД, это более простой для платформы режим. В управляемом используется новый элементв уравнении - менеджер блокировок 1с. В смешанном режиме, чтобы менеджер блокировок мог установить управляемую блокировку, объект блокировки должен поддерживать работу в управляемом режиме - то есть в метаданных это явно должно быть прописано, так вот решили сделать. Почему управляемый в автоматическом может работать,а наоборот - нет? Так это как раз следствие того, что всё происходит в одной транзакции! Если у транзакции автоматический режим, значит она менеджер блокировок использовать не будет в любом случае - ей не важно, что там будет у остальных объектов стоять в качестве режима блокировки. Если у транзакции управляемый режим, то запись любого объекта будет вызывать помимо всего прочего еще и попытку наложения управляемой блокировки. А для регистров в автоматическом режиме это сделать будет нельзя - это запрещено разработчиком в конфигураторе!
(69) Не совсем так. Намутили они со смешанным режимом чего-то.
Мне крышу сносит этот абзац из документации:
"Первая особенность заключается в том, что даже если для транзакции используется автоматический режим управления блокировками, система установит дополнительно и соответствующие управляемые блокировки при записи данных в этой транзакции. Из этого следует, что транзакции, исполняющиеся в режиме управляемых блокировок, могут конфликтовать с транзакциями, исполняющимися в режиме автоматического управления блокировками."
Вот что они имели в виду? Что такое "соответствующие управляемые блокировки"?
(70) Имеют ввиду, что при записи регистра на всякий случае будет установлена управляемая исключительная блокировка, как если бы он записывался в управляемой транзакции. Уровень изоляции транзакций на СУБД при этом всё равно остаётся высоким, а нужно это для того, чтобы управляемые блокировки на регистр не игнорировались при его записи из автоматической транзакции.
(71) На какой "всякий" случай? Для параллельной управляемой транзакции? А нафига? Что-то я туплю под конец дня.
(72) Пример, регистр в управляемом режиме, его двигают два документа один в управляемой транзакции, второй в автоматической. Как его предложите блокировать на СУБД или в менеджере блокировок 1С?
Совместимость штука мутная.
(73) Автоматическая транзакция в любом случае на уровне СУБД заблокирует те же ресурсы, что и "дублированной" управляемой блокировкой. При этом "дубляж" ничего не решает, т.к. автоматическая блокировка всегда будет "попутно" блокировать и другие ресурсы, о которых менеджер управляемых блокировок заведомо ничего знать не будет.
(74) Раз не знает значит они и не важны.
Продолжая (73) оба документа прочли регистр, первый поставил управляемую блокировку (на СУБД поставил и снял); второй накладывает S (или U) на остатки регистра.
Потом первый документ хочет писать и ждёт снятия на уровне СУБД; второй повышает S (или U) до X, пишет и успешно заканчивает транзакцию.
Первый документ списывает в минус на разлоченном регистре.
Автоматическая блокировка ничего не будет знать о ручной управляемой блокировке в соседней, управляемой, транзакции, и позволит записать документ тогда, когда разработчик явно это запретил.
(75), (76) если я все верно понимаю, по одним и тем же регистрам не должны делать движения документы в разных режимах. зачем тогда режим совместимости. Вернее тогда всю конфигурацию перевести в управляемый режим.
(77) неверно. В смешанном режиме автоматический режим транзакции ставит некоторые управляемые блокировки сам.
+ процесс перехода подразумевает установку управляемых блокировок из кода даже в автоматическом режиме транзакции, с заделом на будущее, когда они понадобятся.
Т.е. если хочешь сделать регистр управляемым, везде где надо, ставь управляемые блокировки, а затем меняй его режим. Когда закончишь эту операции со всеми регистрами по которым проводится документ, можно и документ сделать управляемым. И так далее.
Мне тоже кажется, что всю конфигурацию проще перевести в управляемый режим сразу.
(37)
> В автоматическом режиме вся таблица блокируется только при использовании СУБД-версионников
можно этот момент поподробнее
интересно исходя из того, что блокировок в версионнике наоборот становится меньше, насколько я могу судить по sql server
Чувствую, в этой ветке порвется еще много валькин-дед-bоянов.
В любом случае, MrStomak прав, впрочем как и всегда ;)
+(80) Вижу в документации, что при автоматическом режиме у Постгреса и Оракла блокируются таблицы.
Я правильно понимаю, что это 1С их так ограниченно использует, что Оракл аналогичен файловой БД по способу блокирования?
(83) Я думаю, это связано с особенностями блокировок у версионников на высоких уровнях изоляции. Вот выдержка из документации на PostgreSQL пункт 13.3.2: Row-level locks do not affect data querying; they block only writers to the same row.
(0) Речь идет о транзакционных блокировках, действующих до окончания транзакции, в которой они вызваны.
(91) Причина в том, что автоматический режим не должен применяться с версионными СУБД, включая оракл. Он существует для совместимости со старыми конфигурациями.
(91) Я не очень понимаю выражения "юзать как файловую базу". Да, процедуры, триггеры, функции, пакеты не используются. Равно как и не используются возможности любой другой СУБД, работающей с 1С.
Всё многообразие типов данных, объектов - всё мимо.
Но связано всё это с тем, что прикладная логика зашивается в конфигурации и платформе и нет смысла переносить её в СУБД, которая решает только задачи чтения/записи. Иначе невозможно было бы поддерживать работу платформы на разных СУБД.
(75),(76) Я просил пример коллизий для случая, когда автоматический режим НЕ УСТАНАВЛИВАЕТ управляемые блокировки, а не когда он их НЕ ПРОВЕРЯЕТ. Ясен пень, что раз допускается совместное использование управляемых и автоматических блокировок, то транзакции в автоматическом режиме должны анализировать управляемые блокировки. Непонятно, нафига в автоматическом режиме УСТАНАВЛИВАТЬ управляемые блокировки.
Такой операции как "анализировать" не существует в прикладном решении. "Анализом" по таблице совместимости занимается менеджер блокировок СУБД и менеджер блокировок 1С, когда в них пытаются засунуть новую блокировку.
В случае, если в менеджер блокировок 1С устанавливать блокировку не будут, то и никого "анализа" не последует. СУБД скажет "на блокировку, всё ок" и будет лажа.
(93) можно было хотя бы часть реализовать через хранимые процедуры, в 7.7 же они использовались иногда
(95) прямое следствие из факта (91)
Взаимоблокировка — это неразрешимый конфликт блокировок, возникающий при работе двух или более сессий клиентских приложений к СУБД.
Взаимоблокировки и таймауты — это достаточно неприятные ошибки, которые часто возникают только в продуктивных системах, но не воссоздаются в процессе нагрузочного тестирования.
Я попытаюсь кратко описать те инструменты, которые можно использовать для облегчения поиска и устранения подобных ошибок применительно к связке 1С+DB2. Как известно, «Центр управления производительностью» (инструмент из «Корпоративного инструментального пакета» фирмы 1С) на данный момент не умеет извлекать информацию о взаимоблокировках и таймаутах из DB2. Скорее всего, в ближайшем будущем эта возможность появится.
Первым делом нам необходимо подготовить базу DB2 для сохранения событий взаимоблокировок и таймаутов в отдельной таблице. Лучше всего войти на сервер под учетной записью db2admin, тогда не будет проблем с правами доступа при выполнении каких-либо операций.
Открываем консоль администратора DB2 и выполняем последовательно следующие операции:
подключение к базе данных
db2 «CONNECT TO »
создание отдельного табличного пространства для монитора событий
db2 «CREATE TABLESPACE TBSPACE_LOCKING»
создание самого монитора событий
db2 «CREATE EVENT MONITOR EVMON_LOCKING FOR LOCKING WRITE TO UNFORMATTED EVENT TABLE (TABLE EMDATA.TAB_LOCKING IN TBSPACE_LOCKING)»
запуск монитора событий
db2 «UPDATE DB CFG USING MON_DEADLOCK HISTORY IMMEDIATE»
db2 «SET EVENT MONITOR EVMON_LOCKING STATE 1»
отключение от базы
db2 CONNECT RESET
Далее нам необходимо дождаться появления некоторого количества событий, которые в последствии мы будем анализировать. Если это продуктивная база, то просто ждем, если тестовая, то запускаем нагрузочный тест. Для проведения нагрузочного теста очень удобно использовать Тест-центр (еще одна составная часть «Корпоративного инструментального пакета»).
Следует учесть, что при большой интенсивности взаимоблокировок и таймаутов, монитор событий может записать значительный объем информации, поэтому желательно отслеживать динамику роста базы и своевременно отключать монитор, либо чистить таблицу с логами.
Чтобы проверить наличие зафиксированных событий можно воспользоваться следующим запросом:
SELECT Count(*) FROM TABLE ( EVMON_FORMAT_UE_TO_XML( 'LOG_TO_FILE',FOR EACH ROW OF ( SELECT * FROM emdata.tab_locking)))
Для очистки истории событий необходимо выполнить вот этот запрос:
DELETE FROM EMDATA.«TAB_LOCKING»
Для отключения монитора событий:
db2 «CONNECT TO »
db2 «SET EVENT MONITOR EVMON_LOCKING STATE 0»
db2 CONNECT RESET
После сбора некоторого объема событий приступаем к извлечению этой информации.
Перед этим необходимо выполнить подготовку инструмента, который позволит преобразовать имеющиеся события в читабельный формат. Для этого:
1) просто для удобства создайте отдельный каталог, например, c:\deadlock
2) скопируйте в него 2 файла из основного каталога IBM DB2. Если DB2 устанавливали по-умолчанию, то эти файлы здесь:
«C:\Program Files\IBM\SQLLIB\samples\java\jdbc\db2evmonfmt.java»
«C:\Program Files\IBM\SQLLIB\samples\java\jdbc\DB2EvmonLocking.xsl»
3) открываем командную строку, заходим в каталог c:\deadlock\ и указываем системе путь к java:
set PATH=C:\Program Files\IBM\SQLLIB\java\jdk\bin;%PATH%
4) компилируем утилиту
javac db2evmonfmt.java
Инструмент готов, теперь непосредственно выполняем извлечение событий в XML-формат:
java db2evmonfmt -d -ue emdata.tab_locking -type deadlock -fxml -u db2admin -p Password >deadlock.txt
где,
-type deadlock — фильтр на тип выгружаемых событий, можно не указывать, тогда система выгрузит и таймауты, и взаимоблокировки
-u db2admin -p Password — параметры авторизации, если работаете уже под db2admin, то можно не указывать.
deadlock.txt — путь к результирующему файлу
Об этих и остальных параметрах можно почитать в справке, просто вызвав java db2evmonfmt
После вызова указанной команды система сформирует файл deadlock.txt, в который будут помещены события в формате XML. Браузером сразу открывать не пытайтесь, т.к. в этом файле скомпонованы тексты XML cо служебными вставками, поэтому для полноценного просмотра этот файл нужно еще нарезать. А можно и так просматривать с помощью вот этой классной бесплатной программы — Notepad++.
Структура полученных XML-файлов достаточно простая.
Для таймаутов указывается по 1 запросу из участвующих сессий — 1 запрос из сессии, которая захватила и длительное время удерживает ресурс, и 1 запрос из сессии(-й), которая стала жертвой и завершила свое выполнение аварийно.
Для взаимоблокировок указывается похожая информация, но для сессий отображаются ВСЕ выполненные запросы с начала транзакции.
Для удобства можно выполнить замену имен таблиц СУБД на имена метаданных 1С, тогда картина станет более прозрачной для восприятия (программистом 1С эта задача решается легко).
Пример типичной взаимоблокировки:
А вот дальше уже надо включать голову и искать участки кода, которые привели к этим ошибкам. В принципе, параллельно с фиксацией событий средствами DB2, можно включить формирование Технологического журнала 1С, а затем сопоставить каким-то образом эту информацию, но, по утверждению разработчиков ЦУПа, для выполнения этой операции пока недостаточно данных.
Если нужно о чем-то написать подробнее — укажите в комментариях, и я попробую это сделать.
Заранее признателен за замечания по существу.
Взаимоблокировка — это неразрешимый конфликт блокировок, возникающий при работе двух или более сессий клиентских приложений к СУБД.
Взаимоблокировки и таймауты — это достаточно неприятные ошибки, которые часто возникают только в продуктивных системах, но не воссоздаются в процессе нагрузочного тестирования.
Я попытаюсь кратко описать те инструменты, которые можно использовать для облегчения поиска и устранения подобных ошибок применительно к связке 1С+DB2. Как известно, «Центр управления производительностью» (инструмент из «Корпоративного инструментального пакета» фирмы 1С) на данный момент не умеет извлекать информацию о взаимоблокировках и таймаутах из DB2. Скорее всего, в ближайшем будущем эта возможность появится.
Первым делом нам необходимо подготовить базу DB2 для сохранения событий взаимоблокировок и таймаутов в отдельной таблице. Лучше всего войти на сервер под учетной записью db2admin, тогда не будет проблем с правами доступа при выполнении каких-либо операций.
Открываем консоль администратора DB2 и выполняем последовательно следующие операции:
подключение к базе данных
db2 «CONNECT TO »
создание отдельного табличного пространства для монитора событий
db2 «CREATE TABLESPACE TBSPACE_LOCKING»
создание самого монитора событий
db2 «CREATE EVENT MONITOR EVMON_LOCKING FOR LOCKING WRITE TO UNFORMATTED EVENT TABLE (TABLE EMDATA.TAB_LOCKING IN TBSPACE_LOCKING)»
запуск монитора событий
db2 «UPDATE DB CFG USING MON_DEADLOCK HISTORY IMMEDIATE»
db2 «SET EVENT MONITOR EVMON_LOCKING STATE 1»
отключение от базы
db2 CONNECT RESET
Далее нам необходимо дождаться появления некоторого количества событий, которые в последствии мы будем анализировать. Если это продуктивная база, то просто ждем, если тестовая, то запускаем нагрузочный тест. Для проведения нагрузочного теста очень удобно использовать Тест-центр (еще одна составная часть «Корпоративного инструментального пакета»).
Следует учесть, что при большой интенсивности взаимоблокировок и таймаутов, монитор событий может записать значительный объем информации, поэтому желательно отслеживать динамику роста базы и своевременно отключать монитор, либо чистить таблицу с логами.
Чтобы проверить наличие зафиксированных событий можно воспользоваться следующим запросом:
SELECT Count(*) FROM TABLE ( EVMON_FORMAT_UE_TO_XML( 'LOG_TO_FILE',FOR EACH ROW OF ( SELECT * FROM emdata.tab_locking)))
Для очистки истории событий необходимо выполнить вот этот запрос:
DELETE FROM EMDATA.«TAB_LOCKING»
Для отключения монитора событий:
db2 «CONNECT TO »
db2 «SET EVENT MONITOR EVMON_LOCKING STATE 0»
db2 CONNECT RESET
После сбора некоторого объема событий приступаем к извлечению этой информации.
Перед этим необходимо выполнить подготовку инструмента, который позволит преобразовать имеющиеся события в читабельный формат. Для этого:
1) просто для удобства создайте отдельный каталог, например, c:\deadlock
2) скопируйте в него 2 файла из основного каталога IBM DB2. Если DB2 устанавливали по-умолчанию, то эти файлы здесь:
«C:\Program Files\IBM\SQLLIB\samples\java\jdbc\db2evmonfmt.java»
«C:\Program Files\IBM\SQLLIB\samples\java\jdbc\DB2EvmonLocking.xsl»
3) открываем командную строку, заходим в каталог c:\deadlock\ и указываем системе путь к java:
set PATH=C:\Program Files\IBM\SQLLIB\java\jdk\bin;%PATH%
4) компилируем утилиту
javac db2evmonfmt.java
Инструмент готов, теперь непосредственно выполняем извлечение событий в XML-формат:
java db2evmonfmt -d -ue emdata.tab_locking -type deadlock -fxml -u db2admin -p Password >deadlock.txt
где,
-type deadlock — фильтр на тип выгружаемых событий, можно не указывать, тогда система выгрузит и таймауты, и взаимоблокировки
-u db2admin -p Password — параметры авторизации, если работаете уже под db2admin, то можно не указывать.
deadlock.txt — путь к результирующему файлу
Об этих и остальных параметрах можно почитать в справке, просто вызвав java db2evmonfmt
После вызова указанной команды система сформирует файл deadlock.txt, в который будут помещены события в формате XML. Браузером сразу открывать не пытайтесь, т.к. в этом файле скомпонованы тексты XML cо служебными вставками, поэтому для полноценного просмотра этот файл нужно еще нарезать. А можно и так просматривать с помощью вот этой классной бесплатной программы — Notepad++.
Структура полученных XML-файлов достаточно простая.
Для таймаутов указывается по 1 запросу из участвующих сессий — 1 запрос из сессии, которая захватила и длительное время удерживает ресурс, и 1 запрос из сессии(-й), которая стала жертвой и завершила свое выполнение аварийно.
Для взаимоблокировок указывается похожая информация, но для сессий отображаются ВСЕ выполненные запросы с начала транзакции.
Для удобства можно выполнить замену имен таблиц СУБД на имена метаданных 1С, тогда картина станет более прозрачной для восприятия (программистом 1С эта задача решается легко).
Пример типичной взаимоблокировки:
А вот дальше уже надо включать голову и искать участки кода, которые привели к этим ошибкам. В принципе, параллельно с фиксацией событий средствами DB2, можно включить формирование Технологического журнала 1С, а затем сопоставить каким-то образом эту информацию, но, по утверждению разработчиков ЦУПа, для выполнения этой операции пока недостаточно данных.
Если нужно о чем-то написать подробнее — укажите в комментариях, и я попробую это сделать.
Заранее признателен за замечания по существу.
Заголовок вышел броским, но накипело. Сразу скажу, что речь пойдет об 1С. Дорогие 1С-ники, вы не умеете работать с транзакциями и не понимаете что такое исключения. К такому выводу я пришел, просматривая большое количество кода на 1С, рождаемого в дебрях отечественного энтерпрайза. В типовых конфигурациях с этим все достаточно хорошо, но ужасающее количество заказного кода написано некомпетентно с точки зрения работы с базой данных. Вы когда-нибудь видели у себя ошибку "В данной транзакции уже происходили ошибки"? Если да — то заголовок статьи относится и к вам. Давайте под катом разберемся, наконец, что такое транзакции и как правильно с ними обращаться, работая с 1С.
Почему надо бить тревогу
Для начала, давайте разберемся, что же такое представляет собой ошибка "В данной транзакции уже происходили ошибки". Это, на самом деле, предельно простая штука: вы пытаетесь работать с базой данных внутри уже откаченной (отмененной) транзакции. Например, где-то был вызван метод ОтменитьТранзакцию, а вы пытаетесь ее зафиксировать.
Почему это плохо? Потому что данная ошибка ничего не говорит вам о том, где на самом деле случилась проблема. Когда в саппорт от пользователя приходит скриншот с таким текстом, а в особенности для серверного кода, с которым интерактивно не работает человек — это… Хотел написать "критичная ошибка", но подумал, что это buzzword, на который уже никто не обращает внимания…. Это задница. Это ошибка программирования. Это не случайный сбой. Это косяк, который надо немедленно переделывать. Потому что, когда у вас фоновые процессы сервера встанут ночью и компания начнет стремительно терять деньги, то "В данной транзакции уже происходили ошибки" это последнее, что вы захотите увидеть в диагностических логах.
Есть, конечно, вероятность, что технологический журнал сервера (он ведь у вас включен в продакшене, да?) как-то поможет диагностировать проблему, но я сейчас навскидку не могу придумать вариант — как именно в нем найти реальную причину указанной ошибки. А реальная причина одна — программист Вася получил исключение внутри транзакции и решил, что один раз — не карабас "подумаешь, ошибка, пойдем дальше".
Что такое транзакции в 1С
Неловко писать про азбучные истины, но, видимо, немножго придется. Транзакции в 1С — это то же самое, что транзакции в СУБД. Это не какие-то особенные "1С-ные" транзакции, это и есть транзакции в СУБД. Согласно общей идее транзакций, они могут либо выполниться целиком, либо не выполниться совсем. Все изменения в таблицах базы данных, выполненные внутри транзакции, могут быть разом отменены, как будто ничего не было.
Далее, нужно понимать, что в 1С не поддерживаются вложенные транзакции. Собственно говоря, они не поддерживаются не "в 1С", а вообще не поддерживаются. По-крайней мере, теми СУБД, с которыми умеет работать 1С. Вложенных транзакций, например, нет в MS SQL и Postgres. Каждый "вложенный" вызов НачатьТранзакцию просто увеличивает счетчик транзакций, а каждый вызов "ЗафиксироватьТранзакцию" — уменьшает этот счетчик. Данное поведение описано в множестве книжек и статей, но выводы из этого поведения, видимо, разобраны недостаточно. Строго говоря, в SQL есть т.н. SAVEPOINT, но 1С их не использует, да и вещь это достаточно специфичная.
Здесь и далее, специально для Воинов Истинной Веры, считающих, что код должен писаться только на английском, под спойлерами будет приведен аналог кода в англоязычном синтаксисе 1С.
На самом деле, нет. Мне совершенно не хочется дублировать примеры на английском только ради того, чтобы потешить любителей холиваров и священных войн.
Вы же наверняка пишете такой код, да? Приведенный пример кода содержит ошибки. Как минимум, три. Знаете какие? Про первую я скажу сразу, она связана с объектными блокировками и не имеет отношения непосредственно к транзакциям. Про вторую — чуть позже. Третья ошибка — это deadlock, который возникнет при параллельном исполнении этого кода, но это тема для отдельной статьи, ее рассматривать сейчас не будем, дабы не усложнять код. Ключевое слово для гугления: deadlock управляемые блокировки.
Обратите внимание, простой ведь код. Такого в ваших 1С-системах просто вагон. И он содержит сразу, как минимум, 3 ошибки. Задумайтесь на досуге, сколько ошибок есть в более сложных сценариях работы с транзакциями, написанных вашими программистами 1С :)
Объектные блокировки
Итак, первая ошибка. В 1С существуют объектные блокировки, так называемые "оптимистические" и "пессимистические". Кто придумал термин, не знаю, убил бы :). Совершенно невозможно запомнить, какая из них за что отвечает. Подробно про них написано здесь и здесь, а также в прочей IT-литературе общего назначения.
Суть проблемы в том, что в указанном примере кода изменяется объект базы данных, но в другом сеансе может сидеть интерактивный пользователь (или соседний фоновый поток), который тоже будет менять этот объект. Здесь один из вас может получить ошибку "запись была изменена или удалена". Если это произойдет в интерактивном сеансе, то пользователь почешет репу, ругнется и попробует переоткрыть форму. Если это произойдет в фоновом потоке, то вам придется искать это в логах. А журнал регистрации, как вы знаете, медленный, а ELK-стек для журналов 1С у нас в отрасли настраивают единицы… (мы, к слову, входим в число тех, кто настраивает и другим помогает настраивать :))
Короче говоря, это досадная ошибка и лучше, чтобы ее не было. Поэтому, в стандартах разработки четко написано, что перед изменением объектов необходимо ставить на них объектную блокировку методом "ОбъектСправочника.Заблокировать()". Тогда параллельный сеанс (который тоже должен так поступить) не сможет начать операцию изменения и получит ожидаемый, управляемый отказ.
А теперь про транзакции
С первой ошибкой разобрались, давайте перейдем ко второй.
Если не предусмотреть проверку исключения в этом методе, то исключение (например, весьма вероятное на методе "Записать()") выбросит вас из данного метода без завершения транзакции. Исключение из метода "Записать" может быть выброшено по самым разным причинам, например, сработают какие-то прикладные проверки в бизнес-логике, или возникнет упомянутая выше объектная блокировка. Так или иначе, вторая ошибка гласит: код, начавший транзакцию, не несет ответственность за ее завершение.
Именно так я бы назвал эту проблему. В нашем статическом анализаторе кода 1С на базе SonarQube мы даже отдельно встроили такую диагностику. Сейчас я работаю над ее развитием, и фантазия программистов 1С, чей код попадает ко мне на анализ, порой приводит меня в шок и трепет…
Почему? Потому что выброшенное наверх исключение внутри транзакции в 90% случаев не даст эту транзакцию зафиксировать и приведет к ошибке. Следует понимать, что 1С автоматически откатывает незавершенную транзакцию только после возвращения из скриптового кода на уровень кода платформы. До тех пор, пока вы находитесь на уровне кода 1С, транзакция остается активной.
Поднимемся на уровень выше по стеку вызовов:
Смотрите, что получается. Наш проблемный метод вызывается откуда-то извне, выше по стеку. На уровне этого метода разработчик понятия не имеет — будут ли какие-то транзакции внутри метода ОченьПолезныйИВажныйКод или их не будет. А если будут — то будут ли они все завершены… Мы же все тут за мир и инкапсуляцию, верно? Автор метода "ВажныйКод" не должен думать про то, что именно происходит внутри вызываемого им метода. Того самого, в котором некорректно обрабатывается транзакция. В итоге, попытка поработать с базой данных после выброса исключения изнутри транзакции, с высокой вероятностью приведет к тому, что "В данной транзакции бла-бла…"
Размазывание транзакций по методам
Второе правило "транзакционно-безопасного" кода: счетчик ссылок транзакций в начале метода и в его конце должен иметь одно и то же значение. Нельзя начинать транзакцию в одном методе и завершать ее в другом. Из этого правила, наверное, можно найти исключения, но это будет какой-то низкоуровневый код, который пишут более компетентные люди. В общем случае так писать нельзя.
Выше — неприемлемый говнокод. Нельзя писать методы так, чтобы вызывающая сторона помнила и следила за возможными (или вероятными — как знать) транзакциями внутри других методов, которые она вызывает. Это нарушение инкапсуляции и разрастание спагетти-кода, который невозможно трассировать, сохраняя рассудок.
Особенно весело вспомнить, что реальный код намного больше синтетических примеров из 3-х строчек. Выискивать начинающиеся и завершающиеся транзакции по шести уровням вложенности — это прям мотивирует на задушевные беседы с авторами.
Вернемся к исходному методу и попытаемся его починить. Сразу скажу, что объектную блокировку мы чинить пока не будем, просто, чтобы не усложнять код примера.
Первый подход типичного 1С-ника
Обычно программисты 1С знают, что при записи может быть выдано исключение. А еще они боятся исключений, поэтому стараются их все перехватывать. Например, вот так:
Однако, опытный 1С-ник здесь скажет, что нет, лучше не стало. По сути ничего не поменялось, а может даже стало и хуже. В методе "Записать()" платформа 1С сама начнет транзакцию записи, и эта транзакция будет уже вложенной по отношению к нашей. И если в момент работы с базой данных 1С свою транзакцию откатит (например, будет выдано исключение бизнес-логики), то наша транзакция верхнего уровня все равно будет помечена как "испорченная" и ее нельзя будет зафиксировать. В итоге этот код так и останется проблемным, и при попытке фиксации выдаст "уже происходили ошибки".
А теперь представьте, что речь идет не о маленьком методе, а о глубоком стеке вызовов, где в самом низу кто-то взял и "выпустил" начатую транзакцию из своего метода. Верхнеуровневые процедуры могут и понятия не иметь, что кто-то там внизу начинал транзакции. В итоге, весь код валится с невнятной ошибкой, которую расследовать невозможно в принципе.
Код, который начинает транзакцию, обязан завершить или откатить ее. Не взирая ни на какие исключения. Каждая ветка кода должна быть исследована на предмет выхода из метода без фиксации или отмены транзакции.
Методы работы с транзакциями в 1С
Не будет лишним напомнить, что вообще 1С предоставляет нам для работы с транзакциями. Это всем известные методы:
- НачатьТранзакцию()
- ЗафиксироватьТранзакцию()
- ОтменитьТранзакцию()
- ТранзакцияАктивна()
Первые 3 метода очевидны и делают то, что написано в их названии. Последний метод — возвращает Истину, если счетчик транзакций больше нуля.
И есть интересная особенность. Методы выхода из транзакции (Зафиксировать и Отменить) выбрасывают исключения, если счетчик транзакций равен нулю. То есть, если вызвать один из них вне транзакции, то возникнет ошибка.
Как правильно пользоваться этими методами? Очень просто: надо прочитать сформулированное выше правило: код, начавший транзакцию, должен нести ответственность за ее завершение.
Как же соблюсти это правило? Давайте попробуем:
Выше мы уже поняли, что метод ДелаемЧтоТо — потенциально опасен. Он может выдать какое-то исключение, и транзакция "вылезет" наружу из нашего метода. Окей, добавим обработчик возможного исключения:
Так, стоп… Если мы просто прокидываем исключение дальше, то зачем тут вообще нужна Попытка? А вот зачем: правило заставляет нас обеспечить завершение начатой нами транзакции.
Теперь, вроде бы, красиво. Однако, мы ведь помним, что не доверяем коду ДелаемЧтоТо(). Вдруг там внутри его автор не читал этой статьи, и не умеет работать с транзакциями? Вдруг он там взял, да и вызвал метод ОтменитьТранзакцию или наоборот, зафиксировал ее? Нам очень важно, чтобы обработчик исключения не породил нового исключения, иначе исходная ошибка будет потеряна и расследование проблем станет невозможным. А мы помним, что методы Зафиксировать и Отменить могут выдать исключение, если транзакция не существует. Здесь-то и пригождается метод ТранзакцияАктивна.
Финальный вариант
Наконец, мы можем написать правильный, "транзакционно-безопасный" вариант кода. Вот он:
**UPD: в комментариях предложен более безопасный вариант, когда ЗафиксироватьТранзакцию расположен внутри блока Попытка. Здесь приведен именно этот вариант, ранее Фиксация располагалась после блока Попытка-Исключение.
Постойте, но ведь не только "ОтменитьТранзакцию" может выдавать ошибки. Почему же тогда "ЗафиксироватьТранзакцию" не обернут в такое же условие с "ТранзакцияАктивна"? Опять же, по тому же самому правилу: код, начавший транзакцию, должен нести ответственность за ее завершение. Наша транзакция необязательно самая первая, она может быть вложенной. На нашем уровне абстракции мы обязаны заботиться только о нашей транзакции. Все прочие должны быть нам неинтересны. Они чужие, мы не должны нести за них ответственность. Именно НЕ ДОЛЖНЫ. Нельзя предпринимать попыток выяснения реального уровня счетчика транзакций. Это опять нарушит инкапсуляцию и приведет к "размазыванию" логики управления транзакциями. Мы проверили активность только в обработчике исключения и только для того, чтобы убедиться, что наш обработчик не породит нового исключения, "прячущего" старое.
Чек-лист рефакторинга
Давайте рассмотрим несколько наиболее распространенных ситуаций, требующих вмешательства в код.
Паттерн:
Обернуть в "безопасную" конструкцию с Попыткой, Проверкой активности и пробросом исключения.
Паттерн:
Анализ и Рефакторинг. Автор не понимал, что делает. Начинать вложенные транзакции можно безопасно. Не нужно проверять условие, нужно просто начать вложенную транзакцию. Ниже по модулю он наверняка еще там извращается с их фиксацией. Это гарантированный геморрой.
Примерно похожий вариант:
аналогично: фиксация транзакции по условию — это странно. Почему тут условие? Что, кто-то иной мог уже зафиксировать эту транзакцию? Повод для разбирательства.
Паттерн:
- ввести управляемую блокировку во избежание deadlock
- ввести вызов метода Заблокировать
- обернуть в "попытку", как показано выше
Паттерн:
В заключение
Я, как вы уже, наверное, догадались, отношусь к людям, любящим платформу 1С и разработку на ней. К платформе, разумеется, есть претензии, особенно в среде Highload, но в общем и целом, она позволяет недорого и быстро разрабатывать очень качественные корпоративные приложения. Давая из коробки и ORM, и GUI, и веб-интерфейс, и Reporting, и много чего еще. В комментариях на Хабре обычно пишут всякое высокомерное, так вот, ребята — основная проблема 1С, как экосистемы — это не платформа и не вендор. Это слишком низкий порог вхождения, который позволяет попадать в отрасль людям, не понимающим, что такое компьютер, база данных, клиент-сервер, сеть и всякое такое. 1С сделала разработку корпоративных приложений слишком легкой. Я за 20 минут могу написать на ней учетную систему для закупок/продаж с гибкими отчетами и веб-клиентом. После этого, мне несложно подумать о себе, что и на больших масштабах можно писать примерно так же. Как-то там 1С сама все внутри сделает, не знаю как, но наверное сделает. Напишу-ка я "НачатьТранзакцию()".
И знаете — самое главное, что это прекрасно. Простота разработки в 1С позволяет моментально реализовывать бизнес-идеи и встраивать их в процессы компании. Потом всегда можно отрефакторить, главное понимать как. И если вдруг вам нужна помощь в аудите вашей "медленной 1С" — обращайтесь к специалистам по оптимизации. Она совсем не медленная.
В данной статье будет рассмотрена методика расследования конфликтов (взаимоблокировка, ожидание на блокировке, превышение времени ожидания) на управляемых блокировках на уровне платформы 1С:Предприятие.
Настройка технологического журнала
Для расследования конфликтов на управляемых блокировках, во-первых, необходимо настроить технологический журнал на сбор событий TLOCK, TTIMEOUT, TDEADLOCK, при необходимости дополнительно установив отбор по имени базы в свойстве p:processName
Классификация конфликтов блокировок
Можно выделить 3 варианта проблем связанных с блокировками:
- Превышение времени ожидания на блокировках (таймаут)
- Взаимоблокировка (deadlock)
- Ожидания на блокировках без возникновения таймаута или взаимоблокировки
Следует отметить, что первые две ошибки являются критическими и требуют немедленного исправления. Третья же, говорит лишь о том что в системе существуют ожидания на блокировках и, в зависимости от их характера и предъявляемого качества к информационной системе, могут быть вызваны как ошибками (избыточными блокировками, длительными транзакциями), так и необходимым условием последовательного доступа к одним и тем же ресурсам.
Совместимость блокировок
Без простейшего понимания совместимости блокировок достаточно сложно описать дальнейшие действия, поэтому, советую ознакомиться с этой статьей на сайте ИТС.
Вкратце укажу что несовместимыми блокировками будут являться такие блокировки, у которых одновременно выполняются следующие условия:
- Несовместимы режимы блокировок (значения Shared/Exclusive в свойстве Locks события TLOCK)
- Совпадают пространства блокировок (свойство Region события TLOCK)
- Пересекаются блокируемые диапазоны пространства блокировок (свойство Locks).
Особенно хочу отметить что в пункте 3 пересекаться должны именно диапазоны, а не блокируемые записи базы данных, в этом заключается существенная разница. Допустим, в конфигурации есть непериодический, независимый регистр сведений «Цены поставщиков» с измерениями «Контрагент», «Номенклатура» и ресурсом «Цена». Записи регистра приведены ниже в таблице:
Контрагент | Номенклатура | Цена |
---|---|---|
ООО «Наше Печенье» | Печенье с малиновой начинкой | 100 |
ООО «Наши Конфеты» | Конфета «Коровка» | 50 |
Если в первой транзакции будет установлена блокировка по пространству этого регистра с условием «Контрагент = ООО «Наше Печенье»», а во второй — по этому же регистру, но с условием «Номенклатура = Конфета «Коровка»», то произойдет конфликт блокировок несмотря на то что в базе данных отсутствуют пересекающиеся записи. Причина заключается в том что блокируемые диапазоны пересекаются ( возможно наличие записи у которой будет «Контрагент = ООО «Наше Печенье»» и «Номенклатура = Конфета «Коровка»»). Рекомендую самостоятельно проверить это воспользовавшись обработкой из раздела «Практическая проверка материала»
Превышение времени ожидания на блокировках
Начинать расследование стоит со строки события TTIMEOUT. В свойстве t:connectID этого события указан номер соединения жертвы, а в поле WaitConnections указан номер соединения источника конфликта (то соединение, которое установило блокировку несовместимую с блокировкой жертвы). Следующим шагом необходимо найти какую управляемую блокировку хотела, но не смогла установить жертва. Для этого, зная значения двух вышеприведенных полей, надо найти строку события TLOCK, у которого будут такие же значения полей t:connectID и WaitConnections, она будет идти после события TTIMEOUT. В данной строке особый интерес представляют поля Regions (пространство блокировок) и свойство Locks, в котором указаны пространство и режим блокировки, а так же набор комбинаций полей и их значений, по которым жертва пыталась установить блокировку. Помимо этого, нам доступен контекст события на встроенном языке (какая строка кода вызвала установку этой блокировки). Далее нам необходимо получить информацию об источнике конфликта. Смысл заключается в том что необходимо найти ближайшую несовместимую блокировку (событие TLOCK), идущую до события TTIMEOUT, свойство t:connectID которого равен значению из поля WaitConnections события TTIMEOUT, а значение поля Regions совпадает со значением ранее найденного TLOCK жертвы. Найдя это событие, мы, аналогично событию жертвы, можем получить информацию об установленной блокировке: пространстве, режиме, полям и их значениям, а так же контекст на встроенном языке. Обладая знаниями о конфликтующих блокировках и контекстах их вызовов, необходимо проанализировать данный конфликт, установить причину возникновения конфликта и устранить ее. В случае если конфликт произошел, например, при проведении документа (или при другом действии, которое регистрируется в журнале регистрации), возможно получить дополнительную информацию из журнала регистрации сопоставив время, пользователя и номер сеанса (SessionID).
Пример разбора:
- Ищем событие TTIMEOUT, по свойствам t:connectID и WaitConnections определяем соединение жертвы — 31139, соединение источника — 31143
- Ищем TLOCK жертвы по значениям полей t:connectID=31139 и WaitConnections=31143. Определяем что жертва хотела установить исключительную блокировку про пространству DIMS регистра сведений InfoRg218 (информацию о регистре можно получить с помощью метода ПолучитьСтруктуруХраненияБазыДанных () , подробнее в статье «Получение информации о структуре хранения базы данных в терминах 1С:Предприятие и СУБД») по значениям полей Fld219=1 и Fld220=2
- По контексту события получаем информацию о месте вызова: Форма обработки «Обработка5», строка 61
- Ищем TLOCK источника, по значению поля t:connectID=31143 и пространству InfoRg218.DIMS, а также расположению в технологическом журнале (должно быть ближайшим предваряющим событием). При этом помним что блокировка должна быть несовместима (дополнительно к пространству блокировок должно выполняться условие на несовместимость по режиму блокировки и заблокированному диапазону). Такому условию удовлетворяет исключительная блокировка про пространству DIMS регистра сведений InfoRg218 и значению поля Fld219=1
- По контексту источника блокировки определяем где в конфигурации была установлена блокировка: Форма обработки «Обработка5», строка 15
- Анализируем и устраняем причину ошибки
Ожидания на блокировках без возникновения таймаута или взаимоблокировки
Схема разбора проблемы данного вида похожа на схему разбора таймаута (с небольшими отличиями). Во-первых, начинать разбор необходимо со строки события TLOCK, у которого значение свойства WaitConnections не пустое (но при этом не было связанных с этой блокировкой событий TTIMEOUT или TDEADLOCK, иначе это уже будет либо таймаутом, либо взаимоблокировкой соответственно). Данное событие является жертвой конфликта (несмотря на то что блокировка в итоге была установлена, жертве пришлось ждать освобождения ресурса, поэтому она «жертва»). Далее нам необходимо найти источник блокировки. Здесь следует поступить так же как и в случае с таймаутом: необходимо найти ближайшую несовместимую блокировку (событие TLOCK), идущую до события TLOCK жертвы, свойство t:connectID которого равен значению из поля WaitConnections, а свойство Regions соответствует событию жертвы. Далее, получив контексты событий, необходимо проанализировать и устранить причину возникновения проблемы.
Пример разбора:
- Ищем событие жертвы TLOCK. Как видно, установка управляемой блокировки жертвой заняла чуть более 5 секунд. По свойствам t:connectID и WaitConnections определяем соединение жертвы — 36680, соединение источника — 36619. По свойствам Regions и Locks получаем информацию о блокировке, по контексту события — информацию о месте вызова: Форма обработки «Обработка5», строка 68
- Ищем TLOCK источника, по значению поля t:connectID=36619 и пространству InfoRg218.DIMS, а также расположению в технологическом журнале (должно быть ближайшим предваряющим событием). При этом помним что блокировка должна быть несовместима (дополнительно к пространству блокировок должно выполняться условие на несовместимость по режиму блокировки и заблокированному диапазону). Опять же получаем информацию об установленной блокировке и месте ее установки: Форма обработки «Обработка5», строка 15
Взаимоблокировка
Разбор взаимоблокировки на управляемой блокировке
Послесловие
К счастью, с жертвой блокировки всегда все понятно: если заполнено значение свойства WaitConnections, значит, данное событие оказалось жертвой, а наличие событий TTIMEOUT, TDEADLOCK или их отсутствие классифицирует вид конфликта. С источником все несколько сложнее, необходимо найти именно несовместимую , установленную источником, блокировку. Дополнительно при поиске конфликтов можно учесть время начала и окончания транзакций, например по событию SDBL или журналу регистрации. Также можно учесть время ожидания жертвой (если жертва ждала 10 секунд, то блокировка была установлена не менее чем за 10 секунд до события жертвы).
Стоит отметить что заполненное свойство WaitConnections не исключает блокировку из числа возможных источников для других блокировок (если она все же была установлена после ожидания, а не была завершена по таймауту или выбрана жертвой дэдлока). Это возможно, например, для каскада блокировок:
- «Транзакция 1» установила блокировку
- «Транзакция 2» хочет установить блокировку, но ждет «Транзакцию 1»
- «Транзакция 1» завершилась, «Транзакция 2» установила блокировку.
- «Транзакция 3» хочет установить блокировку, но ждет «Транзакцию 2»
В таком случае, «Транзакция 2» будет являться жертвой по отношению к «Транзакции 1» и источником по отношению к «Транзакции 3», и у нее будет заполнено свойство WaitConnections.
Ко всему прочему, возможна ситуация когда источник по той или иной причине наложил не одну несовместимую блокировку в течении транзакции:
- «Транзакция 1» установила блокировка по «Измерению 1»
- «Транзакция 1» установила блокировка по «Измерению 2»
- «Транзакция 2» пытается установить блокировку по «Измерению 1» и «Измерению 2» с такими же значениями.
Таким образом, обе блокировки источника являются несовместимыми с блокировкой жертвы. В этом случае предпочтительным способом будет найти и устранить ближайшую блокировку источника, а при тестировании, после устранения ближайшей блокировки, будет найдена и первая.
Так же хочется отметить что записи событий источника и жертвы даже в простом случае могут находиться в разных файлах технологического журнала (например, в файле другого часа, другого процесса и т.д.).
Практическая проверка материала
Как и раньше, приведу пример для практической проверки материала. Создадим базу данных, в которой установим режим управления блокировкой «Управляемый», основной режим запуска «Обычное приложение», режим использования модальных окон в «Использовать». Добавим в базу регистр сведений «АнализУправляемыхБлокировок» (непериодический, независимый). В регистре добавим измерения: «Измерение1» (тип Число), «Измерение2» (тип Число). Не забудьте настроить технологический журнал как указано в начале статьи. Теперь откройте обработку из вложения к данной статье в двух сеансах, выполните нижеприведенные кейсы и проанализируйте технологический журнал.
Таймаут (TTIMEOUT)
Длительное ожидание на блокировке
Взаимоблокировка вида «захват ресурсов в разном порядке»
В первом сеансе нажать «Взаимоблокировка 1», во-втором «Взаимоблокировка 2», после чего сразу же нажать «ОК» модального окна в первом сеансе, а затем во-втором.
Взаимоблокировка вида «повышение уровня блокировки»
В первом, а затем во-втором сеансе нажать «Взаимоблокировка (повышение уровня блокировки)». После этого сразу же нажать «ОК» модального окна в первом сеансе, а затем во-втором.
Читайте также: