Ошибка конфликт блокировок при выполнении транзакции native client

Ошибка загрузки информационной базы. Конфликт блокировок при выполнении транзакции.

Я
   Tornadius

14.12.22 — 09:41

Доброго дня, форумчане.

Столкнулся с проблемой:

После перехода на платформу 8.3.19.1723:

1) выгружаю dt-шник из файловой базы.

2) гружу его в серверную базу (MS SQL)

получаю ошибку:

Ошибка загрузки информационной базы. В информационную базу загружены не все данные

по причине:

Конфликт блокировок при выполнении транзакции:

Microsoft SQL Native Client: Lock request time out period exceeded.

HRESULT=80004005, SQLSrvr: SQLSTATE=HYT00, state=38, Severity=10, native=1222, line=1

3) гружу dt-шник в файловую базу на другой машине — все замечательно, все грузится.

Вопрос: 1) Это особенность платформы?

        2) Это особенность SQL — он у нас старый — 2005.

        3) Не правильно настроены параметры базы — тайминги и прочая…

   Tornadius

1 — 14.12.22 — 10:04

При попытке запустить базу после загрузки выдает сообщение:

>> Авторизация не выполнена. Работа системы будет завершена.

Не удалось выполнить автоматическую регистрацию администратора в списке по причине:

«Значение не является значением объектного типа (Получить)».

Для ведения списка и настройки прав пользователей предназначен список Пользователи,

режим конфигурирования 1С:Предприятия для этого использовать не следует.

   fbear

2 — 14.12.22 — 10:18

это баг из-за загрузки в несколько потоков, попробуй так

1cv8.exe DESIGNER /IBName ibname /RestoreIB «E:baseload dt1000000.dt»  -JobsCount 1

   Kassern

3 — 14.12.22 — 10:23

(0) Могу предложить 2 варианта, если ничего не поможет:

1) Выгрузить конфигурацию и накатить на пустышку базу sql. Далее через ВыгрузкаЗагрузкаДанных перенести данные, перепровести документы и закрыть месяца

2) Выполнить переход в сервис (создастся архив с данными базы) на уровне предприятия. Так же развернуть пустышку с конфой на sql и там выполнить загрузку из сервиса.

   Tornadius

4 — 14.12.22 — 10:31

(3) Накатывал базу из архива (выгружен с сервера) средствами SQL — все работает.

    гружу туда нужный dt — не грузится.

Дело в том, что бухи подготовили на файловой копии данные, и поступило распоряжение распространить эти данные на всех бухгалтеров.

завел на сервере новую базу и попытался загрузить туда dt-шник из файловой копии — и тут началось …

   Kassern

5 — 14.12.22 — 10:38

(4) Я вам написал как обойти выгрузку dt и решить вопрос, если остальные способы не помогли

   Tornadius

6 — 14.12.22 — 10:43

(5) Пытаюсь разобраться с параметрами командной строки: ibname

   Tornadius

7 — 14.12.22 — 10:49

(5) Где и как указать имя сервера?

   Kassern

8 — 14.12.22 — 10:52

(7) Что сделать хотите?

   fbear

9 — 14.12.22 — 10:57

2(6)

4.3.7.1. Параметр /IBName

Параметр /IBName позволяет указать наименование запускаемой информационной базы. В этом случае программа запуска (или исполняемый файл конкретного клиента) выполнит поиск указанной информационной базы в списке информационных баз.

Если баз с указанным именем более одной, запуск будет прекращен с выдачей сообщения об ошибке.

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

Примечание. Если в наименовании информационной базы есть кавычки, они должны быть удвоены при указании имени в параметре: База «»СтройТоргВсе»».

   Tornadius

10 — 14.12.22 — 11:03

У меня получилась строка:

C:Program Files1cv88.3.19.1723bin1cv8.exe /ibname «1s-temp» /RestoreIB «C:Program Files1cv88.3.19.1723binbp_GMyC.dt»  -JobsCount 1

   ansh15

11 — 14.12.22 — 13:28

В качестве предположения: не может ли загрузка из dt в несколько потоков приводить к коллизиям при установленном maxdop=1 у MS SQL, скажем у очень старых версий?

Ее же не для того многопоточной сделали, чтобы выключать…

   Kassern

12 — 14.12.22 — 13:41

(10) Получилось загрузить ДТшку?

   Tornadius

13 — 15.12.22 — 07:09

(12) Нет.

батник не стартует:

1cv8.exe DESIGNER /N «*****» /P «******» /IBName «1s-temp» /RestoreIB «C:Program Files1cv88.3.19.1723binbp_GMyC.dt»  -JobsCount 1

Даже если его положить прямо в папку bin.

   Tornadius

14 — 15.12.22 — 07:10

(13) Может вместо имени базы указать ее регистрацию на сервере?

   Tornadius

15 — 15.12.22 — 07:11

(11) Про настройки SQL спрошу у админа.

   JeHer

16 — 15.12.22 — 07:18

(4) >>>Дело в том, что бухи подготовили на файловой копии данные, и поступило распоряжение распространить эти данные на всех бухгалтеров.

завел на сервере новую базу и попытался загрузить туда dt-шник из файловой копии — и тут началось …

Это ж бухи, пусть перенесут эти данные на сервер сами )))

Попробуйте грохнуть список юзеров в файловой. Выгрузить и загрузить в sql. Если запустится завести юзеров штатными средствами.

   Tornadius

17 — 15.12.22 — 07:33

(16) Это не надо в рабочую базу, задача стоит сделать общий доступ для всех в копии.

     вот хотели развернуть еще одну базу на сервере и загрузить туда ДТ-шник.

     Пока вышли из положения рассшарив папку с базой на двоих. Но это временно.

   Tornadius

18 — 15.12.22 — 07:34

(16) Бухи в мирное время пешком не ходят — они берут либо оутсорт либо программиста… :)

   fbear

19 — 15.12.22 — 09:02

(13) а какая ошибка то? Зачем логин и пароль?

1. Создайте новую базу на sql

2. Добавьте новую ИБ на сервере 1С с выше созданной базой

3. Добавьте в список ИБ в стартере эту базу

4. Запустите командную строку

c:Program Files1cv88.3.20.1674bin>1cv8.exe DESIGNER /IBName TestRestore /RestoreIB «d:tempdump.dt»  -JobsCount 1

В моем случае во всех названиях (sql, ИБ на сервере и в стартере было одно и то же — TestRestore), но Имя надо в команде, то которое прописано в стартере. И путь к дампу поменяйте на свой.

Только что проверил — все работает.

   lodger

20 — 15.12.22 — 09:36

(10) а сервак указать не надо?

   Demiurg

21 — 15.12.22 — 13:52

EXEC sp_configure ‘show advanced options’, 1;  

GO  

RECONFIGURE WITH OVERRIDE;  

GO  

EXEC sp_configure ‘max degree of parallelism’, 1;  

GO  

RECONFIGURE WITH OVERRIDE;  

GO

  

Kassern

22 — 15.12.22 — 14:00

(21) Это же можно в настройках скуля поменять, без всяких хранимых процедур

Проблема ожидания на блокировках при параллельной работе пользователей в 1С

В процессе эксплуатации высоконагруженных систем можно столкнуться с  проблематикой ожидания на блокировках при параллельной работе пользователей. При этом встречаться такие блокировки могут даже в тех случаях, когда есть кажущаяся видимость того, что происходить их не должно. Именно такая ситуация случилась при внедрении на крупном предприятии решения для автоматизации предприятий питания «1С:Общепит. Модуль для 1С:ERP и 1С:КА2».

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

«Выпуск блюд» является центральным документом решения «1С:Общепит. Модуль для 1С:ERP и 1С:КА2» и объединяет в себе большое количество функционала, такого как отражение производства продукции, его дальнейшую реализацию, перемещение и так далее. Сложность разработки на базе 1С:ERP заключается в том, что крайне нежелательно вносить изменения в типовые объекты. Поэтому при разработке было решено сделать «Выпуск блюд» документом-интегратором. Это значит, что сам по себе объект не выполняет движений по типовым регистрам 1С:ERP. Выпуск блюд в момент своего проведения создает ряд типовых документов, которые и производят все движения. Соответственно, блокировка возникла в момент проведения типового документа, который формировался и проводился  внутри транзакции проведения документа «Выпуск блюд». На рисунке ниже показан пример дерева типовых документов. Видно, что по одному отраслевому документу «Выпуск блюд» может быть создано несколько типовых документов, например «Производство без заказа».

Выпуск блюд

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

Расследование

Параметры стенда:

  • Копия клиентской базы «1С:Общепит. Модуль для 1С:ERP и 1С:КА2» в клиент-серверном режиме, на которой воспроизводится проблемная ситуация.
  • Операционная система: Windows Server 2012 R2.
  • База данных: Microsoft SQL Server 2017 64х.
  • Платформа 1С: 8.3.15.1656.
  • Конфигурация оборудования:
    • Процессор: Intel Xeon X5680 12M Cache 3.33, 6 ядер, 2 шт.
    • Оперативная память: 192GB/DDR3.
    • Дисковый накопитель: Intel SSDSC2BA800G4.

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

Выпуск блюд. По норме

Текст ошибки «Конфликт блокировок при выполнении транзакции: Microsoft SQL Native Client 11.0: Lock request timeout period exceeded» позволяет понять, что произошла ошибка ожидания на блокировках системы управления базами данных (сокр. СУБД) Microsoft SQL Server (сокр. MS SQL). Значит более подробную информацию следует искать при помощи Extended Events в Microsoft SQL Management Studio. События, которые позволят произвести анализ возникающих блокировок:

  • bloced_process_report
  • lock_deadlock
  • lock_timeout_greater_than_0
  • locks_lock_waits
  • xml_deadlock_report

Внешний вид настройки лога расширенных событий:

Внешний вид настройки лога расширенных событий

Запускаем сессию и смотрим за результатом при помощи команды «Watch Live Data». Воспроизводим ситуацию с блокировкой повторно, после чего в окне лога расширенных событий видим следующую ситуацию:

Зафиксировано событие lock_timeout_grater_than_0

Зафиксировано событие lock_timeout_grater_than_0. Это событие наступает при ожидании на блокировках и содержит такую полезную информацию, как id ресурса, который вызвал конфликт (поля resourse), тип ресурса (resourse_type) и текст запроса (sql_text).

По типу ресурса PAGE становится понятно, что заблокированы были не записи таблицы, а страница, на которой содержатся записи. Из документации Microsoft можно понять, что в MS SQL Server размер страницы составляет 8 КБ.

Microsoft SQL Server

Теперь посмотрим на текст запроса (sql_text) и запомним имя таблицы, на которой происходит блокировка:

sql_text

Определим, что это за объект в терминах 1С. Для этого воспользуемся обработкой «Структура хранения метаданных». Видим, что таблица, из-за которой возникает блокировка это таблица итогов регистра накопления «Товары организации»:

Просмотр метаданных

Выдвинем гипотезу, что на заблокированной странице помимо необходимых записей могли также находиться и другие данные, которые никак не соответствуют ключам аналитики документа «Выпуск блюд». То есть, могла произойти избыточная блокировка. Значит следующим шагом будет просмотр содержимого заблокированной страницы. Идентификатор страницы можно увидеть по полям resourse события lock_timeout_grater_than_0:

Просмотр содержимого заблокированной страницы

Чтобы увидеть содержимое ресурса на заблокированной странице включим специальный флаг трассировки командой DBCC TRACEON(3604):

Включим специальный флаг трассировки командой DBCC TRACEON(3604)

Не забываем после всех манипуляций выключить установленные флаги трассировки при помощи команды DBCC TRACEOFF:

DBCC TRACEOFF ([n — номер флага], [, -1 — глобально])

Непосредственно увидеть содержимое страницы можно при помощи следующей команды:

DBCC PAGE(db_name|db_id, file_id, page_id, print_option) WITH TABLE RESULTS

  • где db_name — имя исследуемой базы;
  • file_id и page_id — номер файла и страницы в файле, которые получаем из полей resourse;
  • а опция WITH TABLERESULTS позволяет вывести содержимое страницы в виде таблицы.

После выполнения команды анализируем результирующую таблицу и обнаруживаем, что во всех записях поле _Period имеет значение 5999-11-01:

Во всех записях поле _Period имеет значение 5999-11-01

Очень похоже на текущие итоги регистра накопления, для того чтобы в этом убедиться сделаем запрос к таблице итогов регистра накопления «Товары организации»:

Товары организации

Делаем вывод, что на заблокированной странице находятся текущие итоги регистра накопления «Товары организации», которые вовсе не обязательно совпадают по аналитикам учета с проводимым документом «Выпуск блюд».

Как средство оперативного воздействия отключаем текущие итоги, и снимаем остроту проблематики. Такое решение позволяет проводить ранее проблемные документы без ошибок.

Управление итогами — полные возможности

Как уже говорилось ранее, документ «Выпуск блюд» является документом-интегратором и создает в процессе своего проведения ряд связанных типовых документов. Более тонкое исследование позволяет выяснить, что в процессе повторного проведения «Выпуск блюд» прежде чем обновить данные в связанных документах производит отмену проведения. В этот момент и обновляются текущие итоги, что приводит к блокировке:

Если убрать принудительную отмену проведения, то проблемные документы перепроводятся без ошибок

Если убрать принудительную отмену проведения, то проблемные документы перепроводятся без ошибок.

Результат

В данной статье были рассмотрены подходы к расследованиям ожиданий на блокировках на примере блокировки, возникающей при одновременном проведении документов с разными аналитиками, которая была обнаружена в процессе внедрения решения «1С:Общепит. Модуль для 1С:ERP и 1С:КА2». Проведение расследования позволило выявить, что непересекающиеся данные были заблокированы на уровне страницы данных и это была таблица итогов регистра накопления.

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

Однако, данное решение не застрахует от возможных страничных блокировок в будущем. Все потому, что все связанные документы проводятся вместе с документом «Выпуск блюд» в рамках одной и той же транзакции. Блокировки имеют время жизни до окончания транзакции, а значит они накапливаются и укрупняются, MS SQL Server начинает блокировать данные не по диапазону записей, а страницами, что и приводит к замедлению параллельной работы пользователей. Поэтому по результатам расследования можно рекомендовать переработку блока формирования связанных документов таким образом, чтобы раздробить транзакцию проведения на части. Эта оптимизация также была выполнена в обновлении типового отраслевого решения «1С:Общепит. Модуль для 1С:ERP и 1С:КА2», что полностью решило данную ситуацию на проекте.

Авторы статьи

Черанев Андрей

Черанев Андрей

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

Первоначальная задача и связанная с ней проблема

Начальные условия

Имеется нетиповая конфигурация «Управление производственным предприятием 1.3», работает в режиме совместимости с 8.2.13 на платформе 8.3. Используемая СУБД: MS SQL Server 2014. В конфигурации существуют некий работающий механизм переразнесения входящих платежных поручений по определенному графику платежей (в разрезе статей ДДС). Данный график уточняется (изменяется) в течении месяца, а в конце месяца все входящие платежные поручения должны быть переразнесены согласно этому графику.

Задача

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

Методика решения

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

Проблема

При выполнении многопоточного перепроведения начали «падать» ошибки взаимоблокировок на уровне СУБД:

Конфликт блокировок при выполнении транзакции:
Microsoft SQL Server Native Client 11.0: Transaction (Process ID 93) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
HRESULT=80004005, SQLSrvr: SQLSTATE=40001, state=34, Severity=D, native=1205, line=1

Анализ проблемы

Рассмотрим какие действия необходимо выполнить для анализа возникшей проблемы.

Технологический журнал

Первым делом необходимо настроить технологический журнал на сбор событий EXCP и DBMSSQL (последнее используется для получения исполняемых запросов на СУБД MS SQL Server). Для события EXCP достаточно установить только фильтр по имени базы, по этому событию мало пишется данных в технологический журнал. А вот событие DBMSSQL, наоборот, пишет очень много данных, поэтому здесь необходимо либо установить фильтры, либо разбирать огромный файл в поисках необходимой информации. Не буду останавливаться на этом вопросе подробно, скажу лишь то что обязательно должны писаться все события DBMSSQL с отбором по свойству lkp=1 (необходимо для получения информации о жертве конфликта блокировок на уровне СУБД). Если же захочется найти источника блокировки, тогда, необходимо будет писать хотя бы все запросы с отбором по таблице на которой происходит конфликт.

Поскольку для анализа проблемы я буду использовать отдельную базу, я включу для сбора все события EXCP и DBMSSQL с отбором по имени базы данных.

<log location=«Z:Excp» history=«12»>

<event>

<like property=«p:processName» value=«MyDatabase%»/>

<eq property=«Name» value=«EXCP»/>

</event>

<property name=«all»/>

</log>

<log location=«Z:Query» history=«12»>

<event>

<eq property=«Name» value=«DBMSSQL»/>

<like property=«p:processName» value=«MyDatabase%»/>

</event>

<property name=«all»/>

</log>

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

Event Date Duration ConnectID Regions WaitConnections Status Context SQL Text
DBMSSQL 2016.07.27 11:31:32.199082 4133991 16196 dbo._AccumRg17539 16240 Victim ОбщийМодуль.УправлениеЗакрытиемРегУчетаКлиентСервер.Модуль : 919 : ДокументОбъектППВ.Записать(РежимЗаписиДокумента.Проведение);
Документ.ПлатежноеПоручениеВходящее.МодульОбъекта : 836 : ОбщегоНазначения.УдалитьДвиженияРегистратора(ЭтотОбъект, Отказ, Истина, Режим);
ОбщийМодуль.ОбщегоНазначения.Модуль : 5490 : УдалитьЗаписанныеДвиженияДокумента(ДокументОбъект, Отказ, ВыборочноОчищатьРегистры, РежимПроведенияДокумента);
ОбщийМодуль.ОбщегоНазначения.Модуль : 5607 : ПолныеПрава.УдалитьДвиженияПеречняРегистров(СоответствиеУдалятьДвижения, ДокументОбъект.Ссылка);
ОбщийМодуль.ПолныеПрава.Модуль : 1353 : ЗаписатьНаборЗаписейНаСервере(ЭлементСоответствиеПереченьРегистров.Ключ, СсылкаНаДокумент,, ЭлементСоответствиеПереченьРегистров.Значение);
ОбщийМодуль.ПолныеПрава.Модуль : 1343 : Набор.Записать();
РегистрНакопления.ОборотыБюджетов.МодульНабораЗаписей : 248 : ПроверитьГраницыПериодовБюджетированияИЗадачиУтвержденияБюджетов(Регистратор, Отказ);
РегистрНакопления.ОборотыБюджетов.МодульНабораЗаписей : 338 : Результат = Запрос.Выполнить();
INSERT INTO #tt4 WITH(TABLOCK) (_Q_001_F_000RRef, _Q_001_F_001, _Q_001_F_002RRef, _Q_001_F_003RRef, _Q_001_F_004RRef, _Q_001_F_005RRef, _Q_001_F_006RRef, _Q_001_F_007RRef, _Q_001_F_008RRef, _Q_001_F_009RRef, _Q_001_F_010, _Q_001_F_011RRef, _Q_001_F_012RRef, _Q_001_F_013RRef, _Q_001_F_014_TYPE, _Q_001_F_014_RTRef, _Q_001_F_014_RRRef, _Q_001_F_015, _Q_001_F_016RRef, _Q_001_F_017RRef) SELECT DISTINCT
T1._Fld17540RRef,
T1._Period,
T1._Fld23565RRef,
T1._Fld17541RRef,
T1._Fld17542RRef,
T1._Fld17543RRef,
T1._Fld17544RRef,
T1._Fld17545RRef,
T1._Fld17546RRef,
T1._Fld24225RRef,
T1._Fld24226,
T1._Fld23564RRef,
T1._Fld23566RRef,
T1._Fld26026RRef,
T1._Fld26027_TYPE,
T1._Fld26027_RTRef,
T1._Fld26027_RRRef,
T1._Fld26565,
T1._Fld38490RRef,
T1._Fld38489RRef
FROM dbo._AccumRg17539 T1
LEFT OUTER JOIN #tt3 T2 WITH(NOLOCK)
ON (T1._Fld17540RRef = T2._Q_001_F_000RRef)
WHERE (T1._RecorderTRef = 0x00000185 AND T1._RecorderRRef = p_0) AND T2._Q_001_F_000RRef IS NULL
p_0: 0x8E769C8E9919A5F011E6523D15F913A7

Жертвой оказался запрос типа SELECT по таблице _AccumRg17539 с отбором по регистратору (найден по свойству lkp=1). Данный запрос должен был установить совмещаемые (Shared, S) блокировки. С помощью обработки из статьи «Получение информации о структуре хранения базы данных в терминах 1С:Предприятие и СУБД» получим информацию о том что это основная таблица оборотного регистра «Обороты бюджетов»

Структура базы данных. Таблица AccumRg17539

Структура базы данных. Таблица AccumRg17539

Поскольку совмещаемая блокировка не может конфликтовать с другой совмещаемой блокировкой, пытаемся в технологическом журнале найти конфликтующие запросы типа INSERT, DELETE или UPDATE с отбором по номеру соединения источника (для этого-то я и включил запись всех событий DBMSSQL). При этом учитываем что запрос жертвы должен был использовать индекс по регистратору и, значит, должен был установить совмещаемые блокировки только по своему регистратору. Поскольку в регистр накопления невозможно записать движения без отбора по регистратору, ищем запрос типа INSERT/DELETE/UPDATE по таблице _AccumRg17539 с отбором по номеру соединения источника и отбором по регистратору из текста SQL запроса жертвы (0x8E769C8E9919A5F011E6523D15F913A7). Но, к сожалению, подобного запроса у источника найти не удается, поэтому следующим шагом приходит мысль проверить точно ли запрос жертвы использует предполагаемый мной индекс.

План выполнения запроса

Для ускорения выполнения, планы исполняемых запросов кэшируются, этим-то мы и воспользуемся чтобы понять какой план запроса был использован. Для того чтобы найти план, выполним запрос к динамическим функциям: dm_exec_query_stats, dm_exec_sql_text, dm_exec_query_plan (более подробно эта тема рассмотрена в статье «Методы получения плана запроса в СУБД MS SQL Server») с отборами по времени выполнения и фрагментам запроса.

План запроса XML

План запроса XML

Уже в плане запроса начинает смущать толстая стрелка исходящая из Index Seek, тем не менее, как видно, использован индекс по регистратору (_ByRecorder)

Параметры IndexSeek

Параметры IndexSeek

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

Так же можно выполнить сам SQL запрос жертвы (из технологического журнала) в Management Studio и увидеть предполагаемый и актуальный планы запросов. В моем случае в актуальном плане запроса количество выбранных строк равно двум, в то время как в предполагаемом плане запроса количество строк ожидается 17230. Данный факт может говорить о неактуальной статистике, поэтому необходимо проверить эту версию. В моем случае статистика актуальна и, ожидаемо, обновление статистики и очистка процедурного кэша не дают никакого результата. При этом я запомню то что оптимизатор не смог правильно вычислить предполагаемое количество строк, хоть пока мне и не понятно что с этим делать.

Граф взаимоблокировки

Поскольку предыдущие шаги не дали результатов, получим граф взаимоблокировки. Для этого необходимо настроить Profiler на получение события Deadlock graph с отбором по исследуемой базе данных и выполнить действие еще раз. Во время выполнения операции, количество взаимоблокировок было не единично. Полученный список событий обширен, а графы разнообразны, но все они связаны с таблицей _AccumRg17539. При разборе взаимоблокировок необходимо начинать с самой простой, поэтому выберем самый простой граф.

Граф взаимоблокировки

Граф взаимоблокировки

Из графа уже можно понять проблему: процесс 92 установил IX блокировку на страницу индекса и процесс 84 установил IX блокировку на ту же страницу индекса. Блокировка IX совместима с другой блокировкой IX. Затем каждый из процессов запрашивает S блокировку (хочет конвертировать блокировку до уровня SIX) на ту же страницу индекса, но SIX блокировка не совместима с IX блокировкой, поэтому возникает взаимоблокировка и процесс 92 выбирается жертвой. Если посмотреть на запросы, то окажется что у обоих процессов S блокировку хотел установить запрос аналогичный вышеприведенному запросу жертвы из технологического журнала.

Метод расследования при помощи таймаута

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

НачатьТранзакцию();

ДокументОбъект = Документ.ПолучитьОбъект();

ДокументОбъект.Записать(РежимЗаписиДокумента.ОтменаПроведения);

ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);

Предупреждение(«Ожидание»);

ЗафиксироватьТранзакцию();

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

Тут же хочу упомянуть о двух возникших у меня проблемах в данном методе:

  1. При проведении появилось ожидание на управляемой блокировке, которое ранее не проявлялось (т.к. в реальном механизме переразнесения платежей использовалось перепроведение документа без отмены проведения, а конфликтующий регистр не изменялся и, соответственно, не записывался). Разбор этой проблемы в данной статье приводится не будет
  2. Не любой документ из набора конфликтовал, поэтому пришлось по технологическому журналу подобрать «правильный» документ (перепроведение которого выполняло конфликтующее соединение в тот же период времени)

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

Текущие установленные блокировки

Вновь выполним перепроведение одного из документов и встанем на ожидании.

В Management Studio выполним запрос из статьи «Получение информации о текущих блокировках в СУБД MS SQL Server».

Текущие транзакционные блокировки

Текущие транзакционные блокировки

Как видно на рисунке, установлены исключительные (X) блокировки на ключи индексов, а также IX блокировки на страницы индексов. При этом одна из блокировок (на уровне страниц) установлена в режиме SIX (была конвертирована из IX блокировки в результате выполнения запроса, приводящего к конфликту.

Текущие запросы

Теперь, не снимая с ожидания выполняющуюся транзакцию, выполним тот самый (конфликтующий) запрос напрямую из Management Studio с подстановкой соответствующего параметра (ссылки на второй документ). Запрос должен повиснуть на выполнении.

Теперь выполним запрос для получения информации о текущих исполняемых запросах, он приведен в статье «Получение информации о текущих исполняемых запросах MS SQL Server»

Текущие запросы

Текущие запросы

Как видно, запрос ожидает получения S блокировки на той же странице индекса. А сессия, которая не дает установить необходимую блокировку, соответствует сессии транзакции остановленной на ожидании.

Эксперименты

Дальнейшие эксперименты включающие в себя:

  1. Перестроение индекса
  2. Обновление статистики
  3. Очистку процедурного кэша
  4. Проведение различных регистраторам (того же типа)

Дают следующие результаты:

  1. Перестроение индекса и обновление его статистики никак не влияют на план запроса, а так же на установку блокировки на уровне страниц индекса
  2. По части регистраторов происходит ошибка определения количества ожидаемых строк и происходит установка блокировки на уровне страницы индекса
  3. По другой части регистраторов оптимизатор запросов определяет корректное количество строк, а блокировка происходит на уровне ключей

Таким образом, подозрения падают на плохое распределение в статистике, которое приводит к ошибке вычисления ожидаемого количества строк и к последующей установке блокировки на уровне индекса.

Получив распределение количества регистраторов от количества записей по регистратору, выявляется сильно неоднородное распределение количества записей по регистраторам. Так 93% уникальных регистраторов содержат лишь 9% строк от общего количества строк в таблице и, наоборот, 7% регистраторов содержат 91% строк записей от всей таблицы.

Также при анализе количества записей по регистраторам был выявлен документ содержащий 3% записей (91115 из 3079082). После отмены проведения данного документа (как оказалось этот документ никому уже не нужен) проблема исчезла.

Выводы

Причиной возникновения взаимоблокировки является попытка установки проблемным запросом S блокировки на уровне страницы индекса. При этом сам запрос как на уровне платформы 1С, так и на уровне СУБД является правильным. Выбор на каком уровне (строк/страниц/таблиц) будут установлены блокировки происходит на уровне СУБД. Причиной такой ошибки является «плохое» распределение статистики по используемому запросом индексу (индексу по регистратору), что было подтверждено дальнейшим расследованием. Изменение запроса таким образом, чтобы использовался другой индекс, без нарушения логики невозможно.

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

Отмена проведения проблемного документа является временным решением (пока не появится очередной документ приводящий к подобной проблеме), поэтому среди возможных решений можно предложить следующие:

  1. Ограничение количества проводимых документом записей (а большие документы разбивать на несколько)
  2. Для больших документов создать отдельный регистр
  3. Отключить блокировку индекса на уровне страниц (вероятно, нарушает лицензионное соглашение 1С) и внести соответствующие изменения в регламентные заданий SQL Server

Отключение блокировки индекса на уровне страниц

Одним из решений конфликта блокировок является установка свойства ALLOW_PAGE_LOCKS для данного индекса (по регистратору в таблице _AccumRg17539) в значение «OFF». Поскольку с выключенным значением данного свойства индекс не может быть реорганизован, необходимо внести соответствующие изменения в планы обслуживания. С выключенным значением данного свойства блокировка устанавливается на уровне ключей и не конфликтует.

Запрос для изменения данного свойства приведен ниже:

ALTER INDEX _Accum17539_ByRecorder_RN ON

dbo._AccumRg17539

SET (ALLOW_PAGE_LOCKS = OFF);

Данное решение приведено для расширения круга знаний и не рекомендуется для использования на продукционной базе (тем более если есть возможность решить проблему иным способом).

Добрый день, коллеги.

1С:Предприятие 8.3 (8.3.17.1851)

Получил ошибку блокировку СУБД при выполнении запроса(!).

Ломаю голову как такое может быть, если:
1. В обработке нет транзакции
2. В принципе какая блокировка может быть на версионном режиме чтения

08:12.360004-0,EXCP,6,process=rphost,p:processName=db_prod,OSThread=25352,t:clientID=682,t:applicationName=BackgroundJob,t:computerName=dcerp,t:connectID=10608,SessionID=540,Usr=ExchangeETL,DBMS=DBMSSQL,DataBase=dcsql4db_prod,dbpid=84,Exception=DataBaseException,Descr=’Конфликт блокировок при выполнении транзакции:
Microsoft SQL Server Native Client 11.0: Превышено время ожидания запроса на блокировку.
HRESULT=80040E31, SQLSrvr: SQLSTATE=HYT00, state=38, Severity=10, native=1222, line=1
‘,Context=’
ОбщийМодуль.ДополнительныеОтчетыИОбработки.Модуль : 1744 : ВыполнитьКоманду(Новый Структура(«ДополнительнаяОбработкаСсылка, ИдентификаторКоманды», ВнешняяОбработка, ИдентификаторКоманды), Неопределено);
ОбщийМодуль.ДополнительныеОтчетыИОбработки.Модуль : 338 : РезультатВыполнения = ВыполнитьКомандуВнешнегоОбъекта(ВнешнийОбъект, ИдентификаторКоманды, ПараметрыКоманды, АдресРезультата);
ОбщийМодуль.ДополнительныеОтчетыИОбработки.Модуль : 2072 : ВыполнитьКомандуДополнительногоОтчетаИлиОбработки(
ОбщийМодуль.ДополнительныеОтчетыИОбработки.Модуль : 2002 : ВнешнийОбъект.ВыполнитьКоманду(ИдентификаторКоманды);

ВнешняяОбработка.МояВненшняя.МодульОбъекта : 2733 : Если Источник <> Неопределено И НЕ ОбщегоНазначения.СсылкаСуществует(Источник)
ОбщийМодуль.ОбщегоНазначения.Модуль : 3493 : Возврат НЕ Запрос.Выполнить().Пустой();’

Запрос на котором встрял:
Функция СсылкаСуществует(ПроверяемаяСсылка) Экспорт

ТекстЗапроса =
«ВЫБРАТЬ ПЕРВЫЕ 1
| 1 КАК Поле1
|ИЗ
| &ИмяТаблицы КАК Таблица
|ГДЕ
| Таблица.Ссылка = &Ссылка»;

ТекстЗапроса = СтрЗаменить(ТекстЗапроса, «&ИмяТаблицы», ИмяТаблицыПоСсылке(ПроверяемаяСсылка));

Запрос = Новый Запрос;
Запрос.Текст = ТекстЗапроса;
Запрос.УстановитьПараметр(«Ссылка», ПроверяемаяСсылка);

УстановитьПривилегированныйРежим(Истина);
Возврат НЕ Запрос.Выполнить().Пустой();

КонецФункции

Содержание
1. Ошибка 1С: “Конфликт блокировок при выполнении транзакции”. В чем причина?
2. Ошибки в 1С из-за блокировок
2.1 Пример необходимой блокировки в 1С
2.2 Пример избыточной блокировки в 1С 
2.3 Как избавиться от избыточных блокировок в 1С

Ошибка 1С: “Конфликт блокировок при выполнении транзакции”. В чем причина?

Этот вопрос возник у нас на проекте по внедрению ЗУП2.5 с численностью 20000 и средним количеством одновременных пользовательских сессий 200.

На этапе опытной эксплуатации при расчете зарплаты пользователи начали интенсивно работать с документами «Начисление зарплаты сотрудникам организаций». Объем документов был порядка 2500 строк.  У пользователей начали появляться сообщения «Конфликт блокировок при выполнении транзакции», и расчет приходилось запускать заново.

1.png

Стали разбираться. Оказалось, мы столкнулись  с эффектом «Избыточной блокировки». Обычно этот эффект появляется при параллельном проведении документов, во время него самым первым документом блокируется большой объем записей регистров на все время проведения документа. Эта блокировка задерживает проведение остальных документов, мешает параллельной работе пользователей и замедляет рабочий процесс. Вообще блокировка данных при проведении документов вещь полезная, она сохраняет целостность данных и гарантирует правильность выполнения расчетных алгоритмов. Но бывает так, что либо объем заблокированных данных чрезмерен, либо время блокировки слишком велико. В результате мы имеем многопользовательскую систему, которая по сути является однопользовательской: вместо параллельного проведения документов — последовательное.

Ошибки в 1С из-за блокировок


Пример необходимой блокировки в 1С

Представим такую ситуацию – есть два документа «Начисление зарплаты сотрудникам организаций», в которых указан одинаковый налоговый период, а на закладке НДФЛ указаны одинаковые сотрудники. Рассмотрим случай, когда блокировка вообще отсутствует. Если последовательно запускать расчет этих документов, то в первом сумма НДФЛ посчитается правильно, а во втором будет равна нулю, т.к. рассчитанный и фактически начисленный НДФЛ на момент проведения второго документа будут совпадать.

 Но если запустить эти документы параллельно, то они одновременно начислят НДФЛ, не подозревая о существовании друг друга, и в результате налог удвоится. Если блокировка настроена верно, то первый документ, запущенный на долю секунды раньше второго, успеет первым прочитать и заблокировать данные о фактически исчисленном налоге в регистре «НДФЛ расчеты с бюджетом» по сотруднику Пушкину А.С. Из этого запроса будет видно, что фактический налог за январь пока не начислялся и значит надо выполнить движение по регистру. Блокировка будет отпущена только после завершения записи в регистр. Второй документ, дойдя до запроса чтения фактически начисленного налога будет поставлен системой на ожидание до тех пор, пока первый документ не закончит транзакцию проведения, после чего он прочитает в запросе, что налог уже начислен и движение по регистру выполнять не надо. Это необходимая блокировка.

Конечно, этот пример притянут за уши для простоты объяснения. На самом деле логика ЗУП 2.5 такова, что для задвоения НДФЛ пользователям не нужно прикладывать особых усилий. НДФЛ рассчитывается до проведения документа, а при проведении содержимое табличной части просто заносится в регистры без всякой проверки. Пользователям между расчетом и проведением предоставляется возможность посмотреть будущий результат и при необходимости поправить руками. Конечно это большой плюс в пользу гибкости ЗУПа, но предъявляет высокие требования к профессиональному уровню расчетчиков. Поэтому вопрос предотвращения задвоения НДФЛ решается организационным путем или с помощью дополнительных проверочных отчетов. Конечно, в ЗУП2.5 есть регистры, которые рассчитываются и записываются одновременно при проведении документа, например «НДФЛ к зачету», но этот пример пришлось бы дольше объяснять ;).

Пример избыточной блокировки в 1С

А теперь представим другую ситуацию. При проведении документа выполняется запрос, который должен отобрать документы, в которых присутствует сотрудник из этого документа. Но запрос написан так, что сервер SQL вынужден находить нужные документы методом перебора. Для технических специалистов это означает, что вместо CLUSTERED INDEX SCAN выполняется TABLE SCAN, т.е. вместо сканирования таблицы индексов происходит сканирование самой таблицы. Причем в процессе перебора блокируются все записи, к которым прикоснулся запрос, даже те, в которых не присутствуют искомые сотрудники. И эта блокировка будет действовать до конца завершения проведения документа, что будет препятствовать параллельному проведению документов с другими сотрудниками. Это избыточная блокировка.

Как избавиться от избыточных блокировок в 1С

Лечение избыточных блокировок может идти двумя путями. Первый — это оптимизация запросов, выполняемых внутри транзакций и добавление необходимых табличных индексов в конфигураторе. Второй — это перевод выполнения SQL-запросов на более низкий уровень изоляции, когда при выполнении запросов записи в таблицах блокируются только на момент выполнения самого запроса, либо не блокируются вовсе. А необходимые блокировки устанавливаются средствами объекта «БлокировкаДанных» и выполняются на стороне сервера 1С.

Теперь немного теории про уровни изоляции на SQL сервере:

1.      В автоматическом режиме в транзакциях используется уровень изоляции SERIALIZABLE. Этот уровень накладывает блокировки типа X (запрещает чтение и запись) до конца транзакции на все данные, которых коснулись запросы или произошла запись данных.

2.      В управляемом режиме в транзакциях используется уровень изоляции ReadCommitted. Этот уровень на записанные данные также устанавливает блокировки типа X до конца транзакции. Но при выполнении запросов на данные накладывает блокировки типа S (запрещает запись и проверяет нет ли в этот момент параллельных записей), при завершении запроса блокировки снимаются не дожидаясь завершения транзакции.

3.      Если база данных переведена в режим  ReadCommitted SNAPSHOT, то в управляемом режиме при чтении данных не накладывается блокировка типа S, есть только блокировка типа X при записи.

Тоже самое чуть более подробно в таблице:

Обычно лечение начинают с понижения уровня изоляции, т.к. это не особо трудозатратно и дает быстрый результат. Достаточно перевести конфигурацию из «Автоматического» режима управления блокировкой данных в «Управляемый», и транзакции начнут выполняться на уровне изоляции типа ReadCommitted, вместо SERIALIZABLE или Repeatable Read.

Чтобы переключить базу данных в режим READ COMMITTED SNAPSHOT (RCSI) необходимо в «SQL Server Management Studio» в свойствах базы данных установить параметр «Is Read Committed Snapshot On» в значение «True»:

2.png 

В некоторых источниках предлагают установить параметр «Allow Snapshot Isolation» в значение «True», но в этом нет необходимости, т.к. это приведет к включению другого режима изоляции SNAPSHOT, который не поддерживается платформой 1С (На момент написания статьи релиз платформы 8.3.9).

Режим управления блокировкой данных задается для неявных транзакций, которые выполняются при записи или при проведении документов, т.е. внутри  предопределенных процедур типа ПриЗаписи() или ОбработкаПроведения(). Но большинство «тяжелых» вычислений в типовой конфигурации ЗУП2.5 происходит при выполнении команды «Рассчитать». При этом в модуле объекта запускается процедура РассчитатьВсе(), внутри которой неоднократно повторяется конструкция НачатьТранзакцию() …ЗафиксироватьТранзакцию(). Это явно указанные транзакции, внутри которых происходит запись и очистка регистров и выполняются запросы. Нам необходимо убедиться, что при переключении конфигурации в управляемый режим в процедуре «РассчитатьВсе()» транзакции также начинают выполняться на уровне ReadCommitted.

Для этого проведем небольшой эксперимент: 

• Запустим SQL Server Profiler.

• Запустим «NEW TRACE».

• Выполним подключение к серверу SQL.

• В окне «Trace Properties» на закладке «General» выберем «Use the template» = «Blank», а на закладке «Events Selections» раскроем группу «Stored Procedures» и выберем «RPC:Complited». По кнопке «Column Filters» укажем имя базы и длительность выполнения запросов более 1.

3.png 4.png 
• Кнопку RUN пока нажимать не будем, т.к. нам надо сначала запустить базу данных в режиме отладки и остановить выполнение расчета документа «Начисление зарплаты сотрудникам организаций» перед выполнением самого массивного запроса. Например, это будет команда
«Результат = Запрос.ВыполнитьПакет();» в функции «ПолучитьДанныеНДФЛПоРегистратору» в общем модуле «ПроведениеРасчетов». Здесь происходит выполнение основного запроса для расчета НДФЛ. Поставим на ней точку останова отладчика и запустим расчет в документе.
5.png
·         После того как отладчик остановится, нажмем кнопку RUN в Профайлере.

·         Теперь сделаем один шаг в отладчике кнопкой F11. Когда запрос будет выполнен и отладчик перейдет на следующий шаг, остановим чтение Профайлера кнопкой «Pause Selected Trace».

·         Теперь найдем самый длительный запрос по колонке Duration и внимательно изучим текст запроса. Если при обращении к реальной (а не временной) таблице после слова WITH стоит SERIALIZABLE, то мы имеем дело с автоматическим режимом блокировки. Если ничего не стоит – то с управляемым.

6.png 7.png 

Если в хинте запроса (Hint – это параметр после слова WITH, позволяющий влиять на план выполнения запроса) не указан уровень изоляции, то выполняется уровень изоляции, установленный по умолчанию для текущей SQL-сессии. Определить уровень изоляции, действующий по умолчанию для текущих сессий можно в «SQL Server Management Studio» с помощью команды

SEL ECT CASE transaction_isolation_level 

WHEN 0 THEN ‘Unspecified’ 

WHEN 1 THEN ‘ReadUncommitted’ 

WHEN 2 THEN ‘ReadCommitted’ 

WHEN 3 THEN ‘Repeatable’ 

WHEN 4 THEN ‘SERIALIZABLE’ 

WHEN 5 THEN ‘SNAPSHOT’ END AS TRANSACTION_ISOLATION_LEVEL 

FR OM sys.dm_exec_sessions

В управляемом режиме для всех сессий будет указан режим ReadCommitted.

Таким образом мы получили экспериментальное подтверждение того, что после перевода в управляемый режим всей конфигурации, в этом режиме начинают работать не только процедуры записи и проведения, но и процедуры расчета документов ЗУП. А это значит, что количество сообщений о конфликте блокировок будет существенно снижено, а параллельность работы с базой данных увеличится.

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

Настройка  управляемых блокировок – это тема для отдельной статьи. Вкратце скажу, что программно управляемые блокировки устанавливаются с помощью объекта «БлокировкаДанных». Сами управляемые блокировки работают уже не на уровне SQL сервера, как в случае с автоматическими блокировками, а на уровне сервера 1С. Для определения необходимых и достаточных управляемых блокировок надо понимать логику программы одновременно на уровне бизнес-процессов и на уровне архитектуры таблиц СУБД.

Но на мой взгляд, для таких конфигураций, как ЗУП2.5 вообще нет смысла использовать какие-либо блокировки, лучше использовать проверочные отчеты для выявления нарушения целостности данных — на практике это самый быстрый способ расчета зарплаты. Особенно на крупных предприятиях, где точно есть сотрудники с внутренним совмещением в обособленных подразделениях, а за каждым ОП закреплен отдельный расчетчик, что и является причиной задвоения НДФЛ. Какой бы не был вышколенный персонал, сама идеология конфигурации допускает возможность задвоения НДФЛ. Поэтому лучше не мешать пользователям работать параллельно во время массированных месячных расчетов, а по завершении точечно и быстро исправить небольшой процент ошибок, чем заставлять их сидеть и нервничать в очереди из-за страха допустить хотя бы одну ошибку. В этом проекте мы использовали самописный отчет «Проверка НДФЛ», который отображал сотрудников с некорректным НДФЛ.

Так же на этом проекте мы столкнулись с эффектом «Эскалация блокировок», когда SQL сервер сам принимает решение, что надо укрупнить область наложения блокировок вплоть до блокировки целиком всей таблицы. В результате работа пользователей останавливается, и все ждут завершения проведения одного документа – виновника эскалации, либо когда по таймауту снимутся взаимные блокировки, либо произойдет перезагрузка сервера. В каких случаях возникает эскалация и как с этим бороться тоже тема для отдельной статьи.

Валерий Федоров

Руководитель проектов ООО “Кодерлайн”

Понравилась статья? Поделить с друзьями:
  • Ошибка конфигурации почтовой системы при
  • Ошибка конфиденциальности в гугл на планшете
  • Ошибка конфигурации обратитесь к оператору
  • Ошибка конфигурация узла распределенной иб не соответствует ожидаемой
  • Ошибка конфигурации мультиплексной сети sae j1939 нарушение калибровки