Ресурс заблокирован, ждите...

Сразу оговорюсь, что нижеизложенное действительно для Microsoft Dynamics Ax работающего с Microsoft SQL Server, Oracle не рассматриваем.

Проблема блокирования ресурсов на чтение и запись далеко не нова и описана во многих источниках. Первое исследование, касающееся того, как Microsoft Dynamics Ax блокирует ресурсы, которое мне довелось видеть -документ “Решение проблемы взаимных блокировок” созданный много лет назад моим коллегой Андреем Васько. Очень рекомендую к чтению (был опубликован на https://club.msbs.ru, ресурс доступен для партнеров), актуально и по сей день...

Данные из этого документа, с любезного согласия автора, были использованы при создании книги “Разработка бизнес-приложений в Microsoft® Business Solutions — Axapta® версии 3.0” https://www.alpina.ru/book/189/.

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

Microsoft Dynamics Ax использует READ COMMITED внутри транзакций. На изменяемые данные налагается блокировка, запрещающая не только модификацию, но и чтение данных другой транзакцией.

Итак, что можно предложить для улучшения ситуации с блокировками в версии 3.0? Не секрет, что код типа ‘select for update <таблица>’ в Microsoft Dynamics Ax преобразуется в 'select from <таблица> (UPDLOCK)' в Microsoft SQL Server.

Например, возьмем следующий кусок кода (/Classes/InventUpd_Reservation/deleteReserveRefTransId) :

ttsbegin;

while select forupdate inventTrans

index hint TransIdIdx

where inventTrans.inventTransId == _movementissue.transId() &&

inventTrans.transChildType == _movementIssue.transChildType() &&

inventTrans.transChildRefId == _movementissue.transChildRefId() &&

inventTrans.statusReceipt == StatusReceipt::None &&

inventTrans.StatusIssue >= statusIssue::ReservPhysical &&

inventTrans.InventRefTransId != ''

{

inventTrans::DeleteMarking(inventTrans.InventRefTransId,inventTrans.InventTransId,inventTrans.Qty,true);

inventTrans.InventRefTransId = '';

inventTrans.StatusIssue = statusIssue::OnOrder;

inventTrans.update();

}

ttscommit;

Принимаем к рассмотрению:

· “while select forupdate”, который можеn привести к блокировке таблицы

· “index hint”, заставляющий использовать индекс ‘TransIdIdx’, хотя лучший индекс мог бы быть выбран оптимизатором базы данных

Для уменьшения риска возможного нарушения целостности данных, повторяем выборку внутри цикла (с добавлением связки по RecId и проверкой выборки – возврата RecId = 0). ‘P_inventTrans’ – еще одна табличная переменная для той же таблицы. ‘RecID’ используется для выборки только необходимой для обновления:

ttsbegin;

inventTrans.selectLocked(false);

while select RecId from inventTrans

where inventTrans.inventTransId == _movementissue.transId() &&

inventTrans.transChildType == _movementIssue.transChildType() &&

inventTrans.transChildRefId == _movementissue.transChildRefId() &&

inventTrans.statusReceipt == StatusReceipt::None &&

inventTrans.StatusIssue >= statusIssue::ReservPhysical &&

inventTrans.InventRefTransId != ''

{

select forupdate P_inventTrans

where P_inventTrans.RecId == inventTrans.RecId &&

P_inventTrans.transChildType == _movementIssue.transChildType() &&

P_inventTrans.transChildRefId == _movementissue.transChildRefId() &&

P_inventTrans.statusReceipt == StatusReceipt::None &&

P_inventTrans.StatusIssue >= statusIssue::ReservPhysical &&

P_inventTrans.InventRefTransId != ''

if (P_inventTrans.RecID != 0)

{

inventTrans::DeleteMarking(P_inventTrans.InventRefTransId,

                              P_inventTrans.InventTransId,P_inventTrans.Qty,true);

P_inventTrans.InventRefTransId = '';

P_inventTrans.StatusIssue = statusIssue::OnOrder;

P_inventTrans.update();

...

}

}

        ttscommit;

Что сделано в коде указанном выше:

· Использование “while select RecId” во внешнем цикле уменьшает сетевой трафик

· Без подсказки 'index hint' в коде X++ оптимизатор Microsoft SQL Server будет использовать более оптимальный индекс

Естественно, при чтении записей вне транзации (для отчетов, например), такой механизм не требуется. Достаточно использовать <таблица>.selectLocked(false) для предотвращения блокировки на чтение.

О блокировках еще не все, в следующий раз – OCC и Microsoft SQL Server 2005…