2015-11-30 2 views
0

Некоторая справочная информация:
Данные регулярно обновляются, удаляя старые данные и вставляя новые. Данные группируются в профили, которые я использую как часть первичного ключа и для удаления старых данных.
Только один процесс записывает данные, поэтому вам не нужно беспокоиться о конфликтах обновления кем-то другим.
Другой процесс только считывает данные, которые я планировал решить с помощью Snapshot-Isolation.
Я получаю доступ к данным через сущность framework 6, первое моделирование кода.SQL Concurrent delete locking

Проблема: Я начинаю сбор данных для нескольких профилей параллельно. Работает отлично для всех таблиц с простым отношением внешних ключей, но для одного с самооценкой.

public class Web 
{ 
    public Web() 
    { 
     CacheDate = DateTime.Now; 
    } 

    [Key, Column(Order = 0)] 
    public Guid ProfileGuid { get; set; } 
    [Key, Column(Order = 2)] 
    public Guid WebId { get; set; } 

    public string Data { get; set; } 

    public virtual List<List> Lists { get; set; } 
    public virtual List<Web> SubWebs { get; set; } 

    public Guid? ParentProfileGuid { get; set; } 
    public Guid? ParentWebId { get; set; } 

    public virtual Web ParentWeb { get; set;} 
} 

modelBuilder.Entity<CacheWeb>() 
    .HasMany(e => e.SubWebs) 
    .WithOptional(e => e.ParentWeb) 
    .HasForeignKey(e => new { e.ParentProfileGuid, e.ParentWebId }) 
    .WillCascadeOnDelete(false); 

Из-за проблемы с производительностью я не использовать EF, чтобы удалить данные, но пользовательская SQL команды: InsertContext.Database.ExecuteSqlCommand(String.Format("DELETE FROM Webs WHERE ProfileGuid = '{0}'", profile.Guid));

Если я называю два из этих удалений в параллельных транзакциях, то первый вызов создает замок, который предотвращает продолжение второй транзакции.

Насколько я могу судить, замок каким-то образом связан с индексом самонаведения.

Я только что нашел другое странное поведение, это зависит от того, какая транзакция удаляется первыми. Единственная разница в данных, которые я могу найти, - это глубина отношения родитель-потомок.
Если я запускаю транзакцию Сначала (имеет один родительский элемент и несколько прямых дочерних элементов), я не могу запустить транзакцию B (с некоторыми родительскими - дочерними - дочерними дочерними отношениями), но если я запускаю B, то сначала я могу запустить A без попадая в неприятности с существующими замками.

Есть ли способ решить эту проблему? Или любая идея, что может быть проблемой с замком? Если вам нужна дополнительная информация, прокомментируйте, что именно вам нужно, так как я не совсем уверен, что важно.

EDIT: для разъяснения

BEGIN Transaction t1 
DELETE FROM Webs WHERE ProfileGuid = 'b35dbba4-54fc-4df7-b1c8-e559d81dfee3' 
BEGIN Transaction t2 
DELETE FROM Webs WHERE ProfileGuid = 'b35dbba4-54fc-4df7-b1c8-e559d81dfee4' 

работ. Поменяйте порядок, и t1 должен ждать завершения t2.

EDIT2: Планы исполнения Seek Scan

EDIT3: Модель и полные запросы model_s План выполнения от 3 запроса (удаление CacheList)

BEGIN Transaction t1 
DELETE FROM CacheItems WHERE ProfileGuid = 'B35DBBA4-54FC-4DF7-B1C8-E559D81DFEE3' 
DELETE FROM CacheFolders WHERE ProfileGuid = 'B35DBBA4-54FC-4DF7-B1C8-E559D81DFEE3' 
DELETE FROM CacheLists WHERE ProfileGuid = 'B35DBBA4-54FC-4DF7-B1C8-E559D81DFEE3' 
DELETE FROM CacheWebs WHERE ProfileGuid = 'B35DBBA4-54FC-4DF7-B1C8-E559D81DFEE3' 
+0

Можно ли запустить трассировку SQL Server с использованием шаблона TSQL_Locks и опубликовать график взаимоблокировки? Это покажет точные данные о тупике. – Vanlightly

+0

Я попробовал, и он ничего не писал в файл событий тупика. Наверное, потому что нет тупика? (никогда не использовал график раньше). Как только я совершаю одну транзакцию, другая работает плавно. Но поскольку одна транзакция может занять до часа ожидания, это не опция. – lolsharp

+0

Вы увидите серию событий, таких как Lock: Deadlock Chain, Deadlock Graph. – Vanlightly

ответ

0

SQL Server может заблокировать разные уровней в зависимости от каждого конкретного запроса. Это может объяснить, почему изменение порядка может сделать транзакцию блокировкой другой или нет.

способов избежать замков являются

  • установив новый transaction isolation level, для всей транзакции/соединения, которое также может быть сделано с помощью TransactionScopes инстансов с любым constuctor, который поддерживает TransactionOption указать IsolationLevel.
  • использованием table hints, чтобы контролировать, как таблица заблокирована, или читать

Оба эти решения являются опасными, потому что более низкие уровни изоляции может привести ко всем видам ошибок.

0

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

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

Как и эта логическая изоляция в приложении, вам необходимо убедиться, что разные запросы изолированы друг от друга на уровне узла-указателя. Убедитесь, что все запросы используют поиск индекса и никогда не проверяются. Это позволит избежать двух запросов, поместив блокировку S (разделяемую блокировку) на одну и ту же запись во время поиска записей, а затем не сможет преобразовать ее в блокировку X при попытке удалить. Запустите каждый из ваших запросов с помощью Show Actual Execution Plan и убедитесь, что вы не видите никаких сканирований индексов. Если вы видите сканирование и не понимаете, зачем тогда оставлять комментарии.

+0

Спасибо, сканирование индексов, кажется, проблема. Один из запросов использует Index Scan или Index Seek в зависимости от профиля, который я удаляю. Я добавил планы исполнения, возможно, вы можете взглянуть на нее и дать мне совет. – lolsharp