SQL Server-Deadlock bei zwei Updates aufgrund der Reihenfolge der Indexsperren

11

Ich habe zwei UPDATEs - eines sperrt zuerst das CI und dann das NCI (bei Status), da auch die Statusspalte aktualisiert wird. Der andere besitzt bereits eine U-Sperre für das NCI, weil er weiß, dass es sich ändert, und versucht dann, eine U-Sperre für das CI zu erhalten.

Was ist der einfachste Weg, um diese zur Serialisierung zu zwingen? Es erscheint seltsam, einen Hinweis auf TABELLEN-Ebene zu verwenden, da es sich um ein internes Indizierungsproblem handelt - es handelt sich nur um eine Tabelle. Wird UPDLOCK, HOLDLOCK automatisch nur auf alle für diese Tabelle benötigten Indizes angewendet und erzwingt dadurch die Serialisierung?

Hier sind die Fragen:

UPDATE htt_action_log
SET status = 'ABORTED', CLOSED = GETUTCDATE()
WHERE transition_uuid = '{F53ADDDA-E46B-4726-66D8-D7B640B66597}'
AND status = 'OPEN';

Dieses eine X sperrt die Zeile im CI (in der Spalte CREATED) und versucht dann, das NCI zu sperren, das die Statusspalte enthält.

UPDATE htt_action_log
SET status = 'RUNNING {36082BCD-EB52-4358-E3D3-4D96FD5B9F0F} 1360094342'
WHERE action_uuid = (SELECT TOP 1 action_uuid
                     FROM htt_action_log
                     WHERE transition_uuid = '{F53ADDDA-E46B-4726-66D8-D7B640B66597}'
                         AND status = 'OPEN'
                     ORDER BY action_seq)

Dieses eine U sperrt dieselbe NCI - für die verschachtelte Abfrage, denke ich, und sperrt dann das CI für das Update.

Somit erzeugt die Bestellung den Deadlock.

Die einfachste Lösung besteht darin, die beiden Abfragen zu zwingen, vollständig zu blockieren - dh zu serialisieren. Was ist der einfachste Weg, dies zu erzwingen? Fügen Sie einfach WITH (UPDLOCK, HOLDLOCK)die Verweise auf die Tabelle hinzu (eine in der ersten und zwei in der zweiten).

DDL:

Hinweis: Der Client verfügt über mehr Indizes für diese Tabelle, die von diesem Update betroffen sein sollten, jedoch im Deadlock-Diagramm nicht erwähnt werden.

CREATE TABLE [dbo].[HTT_ACTION_LOG](
    [ACTION_UUID] [varchar](128) NOT NULL,
    [TRANSITION_UUID] [varchar](128) NOT NULL,
    [STATUS] [varchar](128) NOT NULL,
    [CREATED] [datetime] NOT NULL,
    [CLOSED] [datetime] NULL,
    [ACTION_SEQ] [int] NOT NULL,
    [ACTION_TYPE] [varchar](15) NOT NULL,
    [ACTION_NAME] [varchar](50) NOT NULL,
    [ACTION_RESULT] [varchar](8000) NULL,
    [PENDING_SINCE] [datetime] NULL,
    [ACTION_SQL] [varchar](8000) NULL,
    [ERROR_OK] [int] NULL,
    [ERROR_COND] [varchar](2048) NULL,
    [RETRY] [varchar](128) NULL,
 CONSTRAINT [PK_HTT_ACTION_LOG_1] UNIQUE NONCLUSTERED 
(
    [ACTION_UUID] ASC
)
)

CREATE CLUSTERED INDEX [IK_HTT_ACTION_LOG_2] ON [dbo].[HTT_ACTION_LOG] 
(
    [CREATED] ASC
)

CREATE NONCLUSTERED INDEX [IK_HTT_ACTION_LOG_1] ON [dbo].[HTT_ACTION_LOG] 
(
    [TRANSITION_UUID] ASC,
    [STATUS] ASC
)
INCLUDE ( [ACTION_UUID],
[ACTION_SEQ])

CREATE NONCLUSTERED INDEX [IK_HTT_ACTION_LOG_4] ON [dbo].[HTT_ACTION_LOG] 
(
    [ACTION_UUID] ASC,
    [STATUS] ASC
)

CREATE NONCLUSTERED INDEX [missing_index_11438530_11438529_HTT_ACTION_LOG] ON [dbo].[HTT_ACTION_LOG] 
(
    [TRANSITION_UUID] ASC,
    [ACTION_TYPE] ASC
)
INCLUDE ( [ACTION_NAME])

CREATE NONCLUSTERED INDEX [missing_index_7207590_7207589_HTT_ACTION_LOG] ON [dbo].[HTT_ACTION_LOG] 
(
    [STATUS] ASC
)
INCLUDE ( [CREATED],
[PENDING_SINCE],
[ACTION_NAME])

CREATE NONCLUSTERED INDEX [missing_index_8535421_8535420_HTT_ACTION_LOG] ON [dbo].[HTT_ACTION_LOG] 
(
    [TRANSITION_UUID] ASC
)
INCLUDE ( [ACTION_UUID],
[STATUS])

ALTER TABLE [dbo].[HTT_ACTION_LOG] SET (LOCK_ESCALATION = AUTO)

ALTER TABLE [dbo].[HTT_ACTION_LOG]  WITH CHECK ADD  CONSTRAINT [FK_HTT_ACTION_LOG_1] FOREIGN KEY([TRANSITION_UUID])
REFERENCES [dbo].[HTT_TRANSITION_LOG] ([TRANSITION_UUID])

ALTER TABLE [dbo].[HTT_ACTION_LOG] CHECK CONSTRAINT [FK_HTT_ACTION_LOG_1]

ALTER TABLE [dbo].[HTT_ACTION_LOG] ADD  DEFAULT ('OPEN') FOR [STATUS]

ALTER TABLE [dbo].[HTT_ACTION_LOG] ADD  DEFAULT (getutcdate()) FOR [CREATED]

ALTER TABLE [dbo].[HTT_ACTION_LOG] ADD  DEFAULT ((0)) FOR [ERROR_OK]
Cade Roux
quelle

Antworten:

13

Der optimale Index für diese beiden Abfragen ist nicht weit von der bestehenden Definition des IK_HTT_ACTION_LOG_1Index (fügt ACTION_UUIDals INCLUDEden verbesserten Index unten):

CREATE INDEX nc1
ON dbo.HTT_ACTION_LOG
(
    TRANSITION_UUID,
    STATUS,
    ACTION_SEQ
);

Die erste Abfrage lautet:

UPDATE dbo.HTT_ACTION_LOG
SET [STATUS] = 'ABORTED', 
    CLOSED = GETUTCDATE()
WHERE
    TRANSITION_UUID = '{F53ADDDA-E46B-4726-66D8-D7B640B66597}'
    AND [STATUS] = 'OPEN';

Geben Sie den folgenden Ausführungsplan:

Update 1

Die zweite Abfrage kann folgendermaßen ausgedrückt werden:

UPDATE ToUpdate 
SET [STATUS] = 'RUNNING {36082BCD-EB52-4358-E3D3-4D96FD5B9F0F} 1360094342'
FROM
(
    SELECT TOP (1)
        hal.[STATUS]
    FROM dbo.HTT_ACTION_LOG AS hal
    WHERE
        hal.transition_uuid = '{F53ADDDA-E46B-4726-66D8-D7B640B66597}'
        AND hal.[STATUS] = 'OPEN'
    ORDER BY
        hal.ACTION_SEQ ASC
) AS ToUpdate;

Geben Sie diesen Ausführungsplan:

Update 2

Beide Abfragen greifen jetzt in derselben Reihenfolge auf dieselben Ressourcen zu, während auf der Leseseite des Plans viel weniger Zeilen gesperrt werden. Die Ausführungs-Engine benötigt UPDLOCKbeim Lesen des neuen Index automatisch s und stellt die gewünschte Serialisierung bereit.

Paul White 9
quelle