SQL Server Deadlock pada dua pembaruan karena urutan kunci indeks

11

Saya memiliki dua UPDATE - yang pertama mengunci CI dan kemudian NCI (status) karena kolom status juga sedang diperbarui. Yang lain sudah memiliki kunci U pada NCI karena tahu itu berubah dan kemudian mencoba untuk mendapatkan kunci U pada CI.

Apa cara termudah untuk memaksa ini untuk bersambung? Tampaknya aneh menggunakan petunjuk level TABEL karena ini adalah masalah pengindeksan internal - hanya ada satu tabel yang terlibat - akankah UPDLOCK, HOLDLOCK secara otomatis hanya berlaku untuk semua indeks yang diperlukan pada tabel itu dan dengan demikian memaksanya untuk diserialisasi?

Inilah pertanyaannya:

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

Yang satu itu mengunci baris dalam CI (pada kolom BUAT) dan kemudian mencoba untuk mengunci X pada NCI yang mencakup kolom status.

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)

U yang ini mengunci NCI yang sama - untuk kueri bersarang saya kira, lalu pergi untuk mengunci CI untuk pembaruan.

Dengan demikian pesanan menghasilkan kebuntuan.

Solusi termudah adalah memaksa kedua query untuk sepenuhnya memblokir - yaitu serialisasi. Apa cara termudah untuk memaksanya, cukup masukkan WITH (UPDLOCK, HOLDLOCK)referensi ke tabel (satu di yang pertama dan dua di yang kedua)?

DDL:

Catatan klien memiliki lebih banyak indeks pada tabel ini yang harus dipengaruhi oleh pembaruan ini, tetapi tidak disebutkan dalam grafik deadlock.

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
sumber

Jawaban:

13

Indeks optimal untuk kedua kueri tersebut tidak jauh dari definisi IK_HTT_ACTION_LOG_1indeks yang ada (tambahkan ACTION_UUIDsebagai INCLUDEke indeks yang ditingkatkan di bawah):

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

Kueri pertama adalah:

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

Memberikan rencana eksekusi berikut:

Perbarui 1

Kueri kedua dapat diekspresikan dengan cara ini:

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;

Memberikan rencana eksekusi ini:

Perbarui 2

Kedua pertanyaan sekarang mengakses sumber daya yang sama dalam urutan yang sama, sambil mengunci lebih sedikit baris di sisi baca paket. Mesin eksekusi akan secara otomatis mengambil UPDLOCKsaat membaca indeks baru, memberikan serialisasi yang Anda cari.

Paul White 9
sumber