SQL Server 2005: блокировка диапазона ключей на уровне изоляции зафиксированных транзакций чтения? - PullRequest
1 голос
/ 15 января 2009

Я помогаю устранить некоторые взаимоблокировки в приложении .NET, использующем SQL Server 2005. У меня есть данные XML из трассировки ниже.

Что меня действительно озадачивает, так это блокировка RangeX-X на PK_Exp_Experience_PriorFirm, когда уровень изоляции транзакции считывается зафиксированным.

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

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

<deadlock-list>
  <deadlock victim="processc2f438">
    <process-list>
      <process id="processc2f438" taskpriority="0" logused="13488" waitresource="KEY: 120:72057594583646208 (8201498b6efe)" waittime="484" ownerId="693258089" transactionname="user_transaction" lasttranstarted="2009-01-06T16:33:27.817" XDES="0xa71ce370" lockMode="U" schedulerid="1" kpid="9112" status="suspended" spid="53" sbid="0" ecid="0" priority="0" transcount="2" lastbatchstarted="2009-01-06T16:33:27.863" lastbatchcompleted="2009-01-06T16:33:27.863" clientapp=".Net SqlClient Data Provider" hostname="CHQAPT3" hostpid="6464" loginname="AppUser" isolationlevel="read committed (2)" xactid="693258089" currentdb="120" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
        <executionStack>
          <frame procname="adhoc" line="1" stmtstart="108" sqlhandle="0x0200000015d9962978fc6206b09e4c872150511b455e8923">
            UPDATE Exp_Experience_PriorFirm SET RelatedGuid = @newGuid WHERE RelatedGuid = @oldGuid
          </frame>
          <frame procname="mssqlsystemresource.sys.sp_executesql" line="1" sqlhandle="0x0400ff7fbe80662601000000000000000000000000000000">
            sp_executesql
          </frame>
          <frame procname="MyDb.dbo.Contact_MergeRelationships" line="74" stmtstart="4754" stmtend="4976" sqlhandle="0x0300780036a608461ed8af00669b00000100000000000000">
            EXEC sp_executesql @sql,
            N'@oldGuid uniqueidentifier, @newGuid uniqueidentifier',
            @oldGuid, @newGuid
          </frame>
          <frame procname="MyDb.dbo.Contact_Company_MergeRelationships" line="8" stmtstart="312" sqlhandle="0x03007800b271a129c8ccaf00669b00000100000000000000">
            EXEC Contact_MergeRelationships @oldGuid, @newGuid, 'Contact_Company', @excludedTableNames
          </frame>
        </executionStack>
        <inputbuf>
          Proc [Database Id = 120 Object Id = 698446258]
        </inputbuf>
      </process>
      <process id="processeb5d68" taskpriority="0" logused="14212" waitresource="KEY: 120:72057594594066432 (7c02a3a5890e)" waittime="2312" ownerId="693243114" transactionname="user_transaction" lasttranstarted="2009-01-06T16:33:20.957" XDES="0x8cdb9450" lockMode="S" schedulerid="2" kpid="9000" status="suspended" spid="73" sbid="0" ecid="0" priority="0" transcount="2" lastbatchstarted="2009-01-06T16:33:29.770" lastbatchcompleted="2009-01-06T16:33:29.770" clientapp=".Net SqlClient Data Provider" hostname="CHQAPT3" hostpid="6464" loginname="AppUser" isolationlevel="read committed (2)" xactid="693243114" currentdb="120" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
        <executionStack>
          <frame procname="MyDb.dbo.Contact_Company_Delete" line="27" stmtstart="1128" sqlhandle="0x03007800b0e5761877cbaf00669b00000100000000000000">
            DELETE FROM Contact WHERE GUID = @Guid;
          </frame>
        </executionStack>
        <inputbuf>
          Proc [Database Id = 120 Object Id = 410445232]
        </inputbuf>
      </process>
    </process-list>
    <resource-list>
      <keylock hobtid="72057594583646208" dbid="120" objectname="MyDb.dbo.Exp_Experience_PriorFirm" indexname="PK_Exp_Experience_PriorFirm" id="lockd1d43f80" mode="RangeX-X" associatedObjectId="72057594583646208">
        <owner-list>
          <owner id="processeb5d68" mode="RangeX-X"/>
        </owner-list>
        <waiter-list>
          <waiter id="processc2f438" mode="U" requestType="wait"/>
        </waiter-list>
      </keylock>
      <keylock hobtid="72057594594066432" dbid="120" objectname="MyDb.dbo.Contact_PersonCompanyLocation" indexname="PK_Contact_PersonCompanyLocation" id="lockd20c4380" mode="X" associatedObjectId="72057594594066432">
        <owner-list>
          <owner id="processc2f438" mode="X"/>
        </owner-list>
        <waiter-list>
          <waiter id="processeb5d68" mode="S" requestType="wait"/>
        </waiter-list>
      </keylock>
    </resource-list>
  </deadlock>
</deadlock-list>

Ответы [ 4 ]

6 голосов
/ 20 марта 2009

Блокировка RangeX-X для PK_Exp_Experience_PriorFirm была принята как часть каскадного удаления.

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

Более подробно это описано здесь: Обновление Conor vs. Isolation Level на UPDATE / DELETE Cascading RI .

4 голосов
/ 15 января 2009

Вы используете READ COMMITED, как вы ожидаете.

Подобные взаимные блокировки могут возникать, если UPDATE получает блокировку ключа eXclusive для кластеризованного индекса и изменяет строку, и эта блокировка блокирует поиск закладок SELECT в кластеризованном индексе.

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

Добавление подсказки NOLOCK к ошибочному оператору SELECT - это одно из решений при условии, что вы можете допустить непринятое чтение.

Другой доступный вариант - установить READ_COMMITED_SNAPSHOT ON для базы данных. Это меняет способ, которым операторы SELECT читают зафиксированные данные; вместо общих блокировок они читают предыдущие версии (снимок) любых данных, измененных транзакциями, начавшимися в начале инструкции SELECT. Это не совсем бесплатно, хотя; стоимость увеличенной активности в tempDB. [Существует также возможность возникновения проблем с триггерами в режиме READ COMMITED SNAPSHOT.]

2 голосов
/ 15 ноября 2012

Другое распространенное объяснение этого может быть связано с индексами UNIQUE с включенной опцией IGNORE_DUP_KEY.

From BOL - этот параметр указывает реакцию на ошибку, когда операция вставки пытается вставить повторяющиеся значения ключа в уникальный индекс. Параметр IGNORE_DUP_KEY применяется только для операций вставки после создания или перестроения индекса. Опция не действует при выполнении CREATE INDEX, ALTER INDEX или UPDATE. По умолчанию установлено значение OFF.

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

OFF Сообщение об ошибке будет появляться при вставке дублированных значений ключа в уникальный индекс. Вся операция INSERT будет отменена.

Что они здесь не упоминают, так это то, что SERIALIZABLE изоляция применяется во время INSERTS, когда эта опция включена. Лично я не понял внутреннее требование сделать это, поскольку некоторые вставленные строки могут быть отброшены и ничего больше, но это то, что есть. Поставьте команду разработчиков SQL в очередь ...

Это поведение легко демонстрируется;

Сначала создайте новую таблицу с типичным PK:

CREATE TABLE [dbo].[Test_RC_TIL_RangeLocks](
    [RID] [int] IDENTITY(1,1) NOT NULL,
    [Col1] [int] NOT NULL,
    [Col2] [int] NOT NULL,
    [Col3] [int] NOT NULL
) ON [PRIMARY]

Далее мы хотим добавить индекс UNIQUE для Col1 и Col2 с IGNORE_DUP_KEY ON:

CREATE UNIQUE NONCLUSTERED INDEX [UIX_Test_RC_TIL_RangeLocks] ON [dbo].[Test_RC_TIL_RangeLocks](
    [Col1] ASC,
    [Col2] ASC
)WITH (
    PAD_INDEX  = OFF, 
    STATISTICS_NORECOMPUTE  = OFF, 
    SORT_IN_TEMPDB = OFF, 
    IGNORE_DUP_KEY = ON, --<<**THE OFFENDER**>>
    DROP_EXISTING = OFF, 
    ONLINE = OFF, 
    ALLOW_ROW_LOCKS  = ON, 
    ALLOW_PAGE_LOCKS  = ON) 
ON [PRIMARY]

Далее мы добавим 5 строк и посмотрим, что произойдет ...

SET TRANSACTION ISOLATION LEVEL READ COMMITTED
DECLARE @C int
SELECT @C=5
BEGIN TRANSACTION
WHILE @C>0
BEGIN
    INSERT Test_RC_TIL_RangeLocks(Col1,Col2,Col3)
    VALUES (@C,@C+1,2*@C + 100)
    SET @C=@C-1
END
SELECT * FROM Test_RC_TIL_RangeLocks
EXEC sp_lock @@SPID
COMMIT

Как и ожидалось, мы добавили пять строк:

RID Col1    Col2    Col3
1   5       6       110
2   4       5       108
3   3       4       106
4   2       3       104
5   1       2       102

И замки, которые нас интересуют:

sid ObjId     IndId Type Resource                  Mode
53  0             0 DB                             S
53  0             0 DB                             S
53  402100473     1 KEY (8194443284a0)             X
53  402100473     2 KEY (550e0a2a4b96)             RangeX-X
53  402100473     2 KEY (ffffffffffff)             RangeS-U
53  1131151075    0 TAB                            IS
53  402100473     1 PAG 1:744                      IX
53  402100473     2 PAG 1:748                      IX
53  402100473     1 KEY (98ec012aa510)             X
53  402100473     1 KEY (a0c936a3c965)             X
53  402100473     2 KEY (ec04ac4bee1f)             RangeX-X
53  402100473     0 TAB                            IX
53  402100473     2 KEY (0207a0a08e23)             RangeX-X
53  402100473     2 KEY (7112ec63c430)             RangeX-X
53  402100473     1 KEY (59855d342c69)             X
53  402100473     1 KEY (61a06abd401c)             X
53  338100245     0 TAB                            IX

Ах, страшные СЕРИАЛИЗИРУЕМЫЕ блокировки диапазона ключей, вызываемые с уровнем изоляции READ COMMITTED на простой вставке!

Так что, когда BOL говорит; Перед блокировкой диапазона клавиш должны быть выполнены следующие условия:

• Уровень изоляции транзакции должен быть установлен в SERIALIZABLE.

Помните, что это не всегда так ...

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

Приветствия ...

0 голосов
/ 15 января 2009

Согласно SQL Docs, ваша транзакция (или какая-либо другая) выполнялась на уровне сериализации.

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

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

...