Тупик SQL-сервера между оператором INSERT и SELECT - PullRequest
7 голосов
/ 29 апреля 2010

У меня проблема с несколькими тупиками на SQL Server 2005. Этот находится между INSERT и оператором SELECT.

Есть две таблицы. Таблица 1 и Таблица2. Table2 имеет PK Table1 (table1_id) в качестве внешнего ключа.
Индекс для table1_id кластеризован.

INSERT вставляет одну строку в table2 одновременно.
SELCET объединяет 2 таблицы. (это длинный запрос, выполнение которого может занять до 12 секунд)

Согласно моему пониманию (и экспериментам), INSERT должен получить блокировку IS для table1 для проверки ссылочной целостности (которая не должна вызывать взаимоблокировку). Но в этом случае он получил блокировку страницы IX

Отчет о взаимоблокировке:

<deadlock-list>
 <deadlock victim="process968898">
  <process-list>
   <process id="process8db1f8" taskpriority="0" logused="2424" waitresource="OBJECT: 5:789577851:0 " waittime="12390" ownerId="61831512" transactionname="user_transaction" lasttranstarted="2010-04-16T07:10:13.347" XDES="0x222a8250" lockMode="IX" schedulerid="1" kpid="3764" status="suspended" spid="52" sbid="0" ecid="0" priority="0" transcount="2" lastbatchstarted="2010-04-16T07:10:13.350" lastbatchcompleted="2010-04-16T07:10:13.347" clientapp=".Net SqlClient Data Provider" hostname="VIDEV01-B-ME" hostpid="3040" loginname="DatabaseName" isolationlevel="read uncommitted (1)" xactid="61831512" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
    <executionStack>
     <frame procname="DatabaseName.dbo.prcTable2_Insert" line="18" stmtstart="576" stmtend="1148" sqlhandle="0x0300050079e62d06e9307f000b9d00000100000000000000">
INSERT INTO dbo.Table2
    (
        f1,
        table1_id,
        f2
    )
    VALUES
    (
        @p1,
        @p_DocumentVersionID,
        @p1

    )     </frame>
    </executionStack>
    <inputbuf>
Proc [Database Id = 5 Object Id = 103671417]    </inputbuf>
   </process>
   <process id="process968898" taskpriority="0" logused="0" waitresource="PAGE: 5:1:46510" waittime="7625" ownerId="61831406" transactionname="INSERT" lasttranstarted="2010-04-16T07:10:12.717" XDES="0x418ec00" lockMode="S" schedulerid="2" kpid="1724" status="suspended" spid="53" sbid="0" ecid="0" priority="0" transcount="2" lastbatchstarted="2010-04-16T07:10:12.713" lastbatchcompleted="2010-04-16T07:10:12.713" clientapp=".Net SqlClient Data Provider" hostname="VIDEV01-B-ME" hostpid="3040" loginname="DatabaseName" isolationlevel="read committed (2)" xactid="61831406" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
    <executionStack>
     <frame procname="DatabaseName.dbo.prcGetList" line="64" stmtstart="3548" stmtend="11570" sqlhandle="0x03000500dbcec17e8d267f000b9d00000100000000000000">
         <!-- XXXXXXXXXXXXXX...SELECT STATEMENT WITH Multiple joins including   both Table2  table 1 and .... XXXXXXXXXXXXXXX -->
    </frame>
    </executionStack>
    <inputbuf>
Proc [Database Id = 5 Object Id = 2126630619]    </inputbuf>
   </process>
  </process-list>
  <resource-list>
   <pagelock fileid="1" pageid="46510" dbid="5" objectname="DatabaseName.dbo.table1" id="lock6236bc0" mode="IX" associatedObjectId="72057594042908672">
    <owner-list>
     <owner id="process8db1f8" mode="IX"/>
    </owner-list>
    <waiter-list>
     <waiter id="process968898" mode="S" requestType="wait"/>
    </waiter-list>
   </pagelock>
   <objectlock lockPartition="0" objid="789577851" subresource="FULL" dbid="5" objectname="DatabaseName.dbo.Table2" id="lock970a240" mode="S" associatedObjectId="789577851">
    <owner-list>
     <owner id="process968898" mode="S"/>
    </owner-list>
    <waiter-list>
     <waiter id="process8db1f8" mode="IX" requestType="wait"/>
    </waiter-list>
   </objectlock>
  </resource-list>
 </deadlock>
</deadlock-list>

Может кто-нибудь объяснить, почему INSERT получает блокировку страницы IX?
Я правильно не читаю отчет о взаимоблокировке?
Кстати, мне не удалось воспроизвести эту проблему.

Спасибо!

EDIT: СОЗДАНИЕ СТОЛОВ:

CREATE TABLE [dbo].[Table2] (
    [Table2_id] [int] IDENTITY (1, 1) NOT NULL ,
    [f1] [int] NULL ,
    [Table1_id] [int] NOT NULL ,
    [f2] [int] NOT NULL ,
)

ALTER TABLE [dbo].[Table2] ADD 
    CONSTRAINT [FK_Table2_Table1] FOREIGN KEY 
    (
        [Table1_id]
    ) REFERENCES [dbo].[Table1] (
        [Table1_id]
    )


CREATE TABLE [dbo].[Table1] (
    [Table1_id] [int] IDENTITY (1, 1) NOT NULL ,
)

ALTER TABLE [dbo].[Table1] WITH NOCHECK ADD 
    CONSTRAINT [PK_Table1] PRIMARY KEY  CLUSTERED 
    (
        [Table1_id]
    ) 

Ответы [ 2 ]

6 голосов
/ 29 апреля 2010

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

В вашем случае INSERT имеет намеренную блокировку на странице. Это означает, что он также получил блокировку X для строки на странице, что является нормальным поведением. Теперь он пытается получить новую блокировку IX, поэтому ему, вероятно, нужно вставить строку на другой странице. Это было бы нормальным поведением вставки в таблицу с несколькими индексами: первый IX находится на одном из индексов (возможно, кластеризованных), а второй IX - на некластеризованном индексе.

SELECT, как вы говорите, возвращается через 12 секунд, поэтому это длинный запрос для большого набора данных, и план, вероятно, выбрал высокую степень детализации блокировки, блокировки страницы. У SELECT есть S-блокировка на странице, для которой INSERT требуется блокировка IX, и требуется еще одна S-блокировка на странице, для которой INSERT имеет блокировку IX.

Это тривиальный тупик, и его очень легко исправить: убедитесь, что ваш SELECT не нуждается в этих S-блокировках страницы. Это не ошибка вставки здесь. Не зная, что делает SELECt, я не могу точно сказать, является ли это оптимальным или нет. По моему опыту, почти всегда у SELECT, подобного этому, есть много, много и гораздо больше возможностей для совершенствования (например, сам SELECT или схема под ним).

Но при условии, что SELECT является оптимальным, ваша самая простая карта выхода из тюрьмы - это включить управление версиями строк :

ALTER DATABASE <dbname> SET ALLOW_SNAPSHOT_ISOLATION ON;
ALTER DATABASE <dbname> SET READ_COMMITTED_SNAPSHOT ON;

Обновление:

На самом деле при втором чтении очевидно, что INSERT имеет блокировки для разных таблиц (если только вы не изменили XML, который выглядит отредактированным здесь и там), поэтому ваше объяснение того, как действует вставка , должно быть ошибочным , INSERT является частью транзакции, которая, по крайней мере, сделала две записи: одну в Table1 и одну в Table2. Но это не сильно меняет проблему или решение. Это правда, что у вас есть возможность разделить две записи в транзакции на отдельные транзакции, но это, очевидно, худший путь.

2 голосов
/ 29 апреля 2010

Я бы сказал, что DatabaseName.dbo.prcTable2_Insert выполняется внутри транзакции или явно открывает ее, и она (или соединение с открытой транзакцией) уже выполнила вставку в Table1 заранее.

...