web-dev-qa-db-fra.com

Deadlock sur Insert pour une table avec un déclencheur d'insertion qui met à jour la colonne de la même table déclenchée

Par spécification, chaque tableau utilise un guid comme clé primaire. Une autre exigence est que chaque ressource nécessite un identifiant de numéro 6 (100001, 100002, ...) en tant qu'iche publique unique au sein du client qui possède la ressource (liée via Resource -> Location -> Customer).

J'ai essayé de définir ce numéro 6 à l'aide d'un autre déclencheur. La logique fonctionne bien mais provoque des impasses lorsque je simuler de nombreux inserts entrants. Je peux éliminer cette impasse en ajoutant un tablockx indice à l'insert. Cependant, puisque LINQ2SQL sera utilisé pour insérer des données, je voudrais éviter d'ajouter une indication de verrouillage à l'insert. J'espérais qu'une combinaison de conseils de verrouillage ou d'indexations d'index dans la gâchette pourrait fonctionner à la place.

Le schéma:

CREATE TABLE [Resource] (
    [pkGUID]                UNIQUEIDENTIFIER NOT NULL,
    [nIdentifier]           INT NOT NULL DEFAULT 0,
    [fkLocation]            UNIQUEIDENTIFIER NOT NULL,
    CONSTRAINT [PK_Resource] PRIMARY KEY CLUSTERED ([pkGUID] ASC)
)
CREATE TABLE [Location] (
    [pkGUID]                UNIQUEIDENTIFIER NOT NULL,
    [fkCustomer]            UNIQUEIDENTIFIER NOT NULL,
    CONSTRAINT [PK_Location] PRIMARY KEY CLUSTERED ([pkGUID] 
)
CREATE TABLE [Customer] (
    [pkGUID]                UNIQUEIDENTIFIER NOT NULL,
    CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED ([pkGUID] 
)

Actuellement, il n'y a pas d'index sur la table Resource sauf la clé primaire en cluster.

La gâchette:

CREATE TRIGGER [dbo].[Trigger_Resource_Insert]
ON [dbo].[Resource]
FOR INSERT
AS
BEGIN
    SET NoCount ON

    ;with relevantCustomers as (
        select c.pkGUID
        from inserted i
        join Location l on i.fkLocation = l.pkGUID
        join Customer c on l.fkCustomer = c.pkGUID
        group by c.pkGUID
    ), maxNumbers as (
        select rc.pkGUID, max = case when max(x.nIdentifier) < 100000 then 100000 else max(x.nIdentifier) end
        from relevantCustomers rc
        join Location l on l.fkCustomer = rc.pkGUID
        join Resource x on x.fkLocation = l.pkGUID
        group by rc.pkGUID
    ), numbers as (
        select i.pkGUID, number = row_number() over (partition by n.pkGUID order by i.pkGUID) + n.max
        from inserted i
        join Location l on i.fkLocation = l.pkGUID
        join maxNumbers n on n.pkGUID = l.fkCustomer
    )
    update x
    set nIdentifier = n.number
    from Resource x
    join numbers n on x.pkGUID = n.pkGUID
END

Un insert générant un tas de données:

;with x as (
    select num = 1
    union all
    select x.num + 1
    from x
    where x.num < {0}
), l as (
    select top {1} fkLocation = pkGUID, sLocationName from Location order by sLocationName
)
insert into Resource with (tablockx) (pkGUID, fkLocation, ...)
select newid(), l.fkLocation, ...
from x
cross join l

Graphique de blocage du profileur SQL lors de l'exécution d'inserts sans tablockx indice:

Deadlock graph

4
sh54

Je dois dire que je ne suis pas tout à fait d'accord avec certains des commentaires qui indiquent que ce que vous voyez est nécessairement un problème de cadre. Le livre LINQ vers SQL le plus certainement vous permet de spécifier une procédure stockée comme moyen d'entrée pour les lignes.

Cela étant dit, je vous recommande vivement d'utiliser ce mécanisme au lieu d'une gâchette si possible. Vous pourrez ajouter vos indices de verrouillage à l'intérieur de la procédure stockée et vos inserts fonctionneront bien.

Votre spec, cependant semble très méfiant pour moi et les problèmes que vous semblez courir en ce moment sont de bons contre-extexamples quant à la raison pour laquelle on pourrait ne pas vouloir poursuivre une conception comme celle-ci. Donc, ma question (purement rhétorique) est pour le concepteur. Quel est le numéro séquentiel problématique censé représenter? Si c'est une séquence dans le temps, alors pourquoi ne pas simplement utiliser un temps et utiliser les fonctions de la fenêtre pour commander les lignes dans quelque chose comme une vue à la place (vous pouvez pointer Linq sur SQL sur des vues)? L'intention d'avoir une sorte de numéro séquentielle sans interruption? Êtes-vous connecté à toute la complexité et de verrouillage que vous allez avoir à gérer simplement pour maintenir ce nombre?

Malheureusement, je pense qu'il n'y a pas de réponse réelle - aucune combinaison magique de notes qui fonctionnera sur vos cadres et vos conceptions - qui résoudra votre problème. Imo ma réponse est de retour et regardez de manière critique à votre conception.

2
Dave Markle

on dirait que trop de code pour une gâchette.

reculons et regardons ce que vous essayez d'atteindre.

vous souhaitez définir Nentier pour aucun enregistrement que vous avez inséré.

ma solution serait de ne pas utiliser de déclencheur du tout, c'est-à-dire.

  1. insérez les enregistrements, utilisez la sortie pour capturer ce que vous avez inséré dans une table TEMP.
  2. utilisez la table Temp pour vous connecter à la table de ressources et faites votre mise à jour
1
Jimbo