Blocage SQL Server sur deux mises à jour en raison de l'ordre de verrouillage d'index

11

J'ai deux mises à jour - une verrouille d'abord le CI puis le NCI (sur le statut) car la colonne de statut est également en cours de mise à jour. L'autre possède déjà un verrou U sur le NCI car il sait qu'il change et essaie ensuite d'obtenir un verrou U sur le CI.

Quelle est la manière la plus simple de forcer ces derniers à sérialiser? Il semble étrange d'utiliser un indice de niveau TABLE car il s'agit d'un problème d'indexation interne - il n'y a qu'une seule table impliquée - UPDLOCK, HOLDLOCK s'appliquera-t-il automatiquement à tous les index nécessaires sur cette table et forcera-t-il ainsi à être sérialisé?

Voici les requêtes:

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

Ce X verrouille la ligne dans le CI (sur la colonne CREATED), puis tente de verrouiller X sur le NCI qui inclut la colonne d'état.

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)

Celui-ci verrouille le même NCI - pour la requête imbriquée, je suppose, puis verrouille le CI pour la mise à jour.

Ainsi, l'ordre produit l'impasse.

La solution la plus simple consiste à forcer les deux requêtes à bloquer complètement - c'est-à-dire à sérialiser. Quelle est la façon la plus simple de forcer cela, il suffit de mettre WITH (UPDLOCK, HOLDLOCK)les références au tableau (un dans le premier et deux dans le second)?

DDL:

Remarque client a plus d'index sur cette table qui devraient être affectés par cette mise à jour, mais ne sont pas mentionnés dans le graphique de blocage.

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
la source

Réponses:

13

L'index optimal pour ces deux requêtes n'est pas loin de la définition existante de l' IK_HTT_ACTION_LOG_1index (ajouter en ACTION_UUIDtant que INCLUDEà l'index amélioré ci-dessous):

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

La première requête est:

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

Donner le plan d'exécution suivant:

Mise à jour 1

La deuxième requête peut être exprimée de cette façon:

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;

Donner ce plan d'exécution:

Update 2

Les deux requêtes accèdent désormais aux mêmes ressources dans le même ordre, tout en verrouillant beaucoup moins de lignes du côté lecture du plan. Le moteur d'exécution prendra automatiquement UPDLOCKs lors de la lecture du nouvel index, fournissant la sérialisation que vous recherchez.

Paul White 9
la source