#c_sharp #база_данных #sql_server #entity_framework #deadlock
Разбираюсь в не своем коде, там много обращений в БД(MS SQL SERVER) с использованием generic репозитория и много очень запутанного кода(поэтому его демонстрации не будет). Бывает такое что при работе вылетает exception deadlock на методе выборки из базы. Если упростить описание этого процесса, то для выборки из DbSetприменяются данные методы : IQueryable.OrderByDescending Queryable.Where Queryable.Skip QueryableExtensions.Include До и после этой выборки, но до применения общего context.SaveChanges могут происходить любые Update Insert Delete Select в любых количествах. Искал как может происходить deadlock и находил только описание случая с 2 транзакциями на Update которые блокируют ресурсы друг друга. Но в случае с Select не понимаю как такое может произойти. Буду рад примеру такой ситуации. Не очень разбираюсь, но возможно изолированность транзакций как то может влиять на это. Или может это проблемы на стороне c#, а не базы. System.Data.Entity.Core.EntityCommandExecutionExce ption: An error occurred while reading from the store provider's data reader. Se e the inner exception for details. ---> System.Data.SqlClient.SqlException: Tran saction (Process ID 106) was deadlocked on lock resources with another process a nd has been chosen as the deadlock victim. Rerun the transaction. at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolea n breakConnection, Action`1 wrapCloseInAction) at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception , Boolean breakConnection, Action`1 wrapCloseInAction) at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObj ect stateObj, Boolean callerHasConnectionLock, Boolean asyncClose) at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady) at System.Data.SqlClient.SqlDataReader.TryHasMoreRows(Boolean& moreRows) at System.Data.SqlClient.SqlDataReader.TryReadInternal(Boolean setTimeout, Bo olean& more) at System.Data.SqlClient.SqlDataReader.Read() at System.Data.Entity.Core.Common.Internal.Materialization.Shaper`1.StoreRead () --- End of inner exception stack trace --- at System.Data.Entity.Core.Common.Internal.Materialization.Shaper`1.HandleRea derException(Exception e) at System.Data.Entity.Core.Common.Internal.Materialization.Shaper`1.StoreRead () at System.Data.Entity.Core.Common.Internal.Materialization.Shaper`1.SimpleEnu merator.MoveNext() at System.Data.Entity.Internal.LazyEnumerator`1.MoveNext() at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection) at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
Ответы
Ответ 1
Deadlock вполне может возникнуть при SELECT. На самом деле для возникновения дедлока неважна конкретная операция. Важен сам факт установки блокировки, который вызывает Deadlock. Сильно упрощенный пример:: По умолчанию SELECT ставит S-локи. UPDATE - ставит X-локи. S и X-локи несовместимы - их нельзя одновременно поставить на один и тот же объект. Тот, кто попытается поставить лок вторым, будет ждать, пока первый его снимет. У вас одновременно выполняются две транзакции: Транзакция 1 ставит X-лок на объект A (UPDATE A...) Транзакция 2 ставит X-лок на объект B (UPDATE B...) Транзакция 1 пытается поставить S-лок на объект B (SELECT FROM B...). Ждет, пока будет снят существующий лок от транзакции 2. Транзакция 2 пытается поставить S-лок на объект A (SELECT FROM A...). Ждет, пока будет снят существующий лок от транзакции 1. В (4) SQL Server понимает, что транзакции ждут друг друга, и убивает одну из них как deadlock victim. Пример очень сильно упрощен, гранулярность локов может быть разная (строка, таблица, страница и т.д.), SQL Server умеет повышать гранулярность вверх. Но общая картина все равно та же. Стандартный способ избежать дедлоков на чтении - перейти на уровень изоляции транзакций, в котором SELECT не ставит локи. Это или SNAPSHOT, или READ COMMITTED SNAPSHOT. Эти уровни изоляции транзакций используют row versioning вместо блокировок, и SELECT-ы при них читают копию данных, если к моменту чтения есть незакомитанные изменения другой транзакции. Разница между ними - в обработке записи. READ COMMITTED SNAPSHOT ведет себя как обычный READ COMMITTED, и просто ставит U/X локи. SNAPSHOT пишет в копию, и падает на коммите транзакции, если кто-то уже успел записать до него. Если у вас уже используется READ COMMITTED (он почти всегда и везде используется по умолчанию), то достаточно просто включить READ COMMITTED SNAPSHOT на уровне базы, и дедлоки пропадут: ALTER DATABASE MyDatabase SET ALLOW_SNAPSHOT_ISOLATION ON ALTER DATABASE MyDatabase SET READ_COMMITTED_SNAPSHOT ON Отдельно стоит упомянуть работу с транзакциями через TransactionScope. Этот замечательный класс по умолчанию переписывает стандартный для EF и SQL Server уровень READ COMMITTED на SERIALIZABLE, что почти гарантирует дедлоки. Если пользуетесь им - не забывайте вручную выставлять нужный вам уровень изоляции в конструкторе.
Комментариев нет:
Отправить комментарий