J'avais toujours utilisé quelque chose de similaire à ce qui suit pour y parvenir:
INSERT INTO TheTable
SELECT
@primaryKey,
@value1,
@value2
WHERE
NOT EXISTS
(SELECT
NULL
FROM
TheTable
WHERE
PrimaryKey = @primaryKey)
... mais une fois sous charge, une violation de clé primaire s'est produite. Il s'agit de la seule instruction qui s'insère dans ce tableau. Cela signifie-t-il donc que la déclaration ci-dessus n'est pas atomique?
Le problème est qu'il est presque impossible de recréer à volonté.
Je pourrais peut-être le changer en quelque chose comme ceci:
INSERT INTO TheTable
WITH
(HOLDLOCK,
UPDLOCK,
ROWLOCK)
SELECT
@primaryKey,
@value1,
@value2
WHERE
NOT EXISTS
(SELECT
NULL
FROM
TheTable
WITH
(HOLDLOCK,
UPDLOCK,
ROWLOCK)
WHERE
PrimaryKey = @primaryKey)
Bien que j'utilise peut-être les mauvais verrous ou que j'utilise trop de verrouillage ou quelque chose.
J'ai vu d'autres questions sur stackoverflow.com où les réponses suggèrent un "IF (SELECT COUNT (*) ... INSERT" etc.), mais j'étais toujours sous l'hypothèse (peut-être incorrecte) qu'une seule instruction SQL serait atomique.
Quelqu'un a-t-il une idée?
Qu'en est-il du modèle "JFDI" ?
BEGIN TRY
INSERT etc
END TRY
BEGIN CATCH
IF ERROR_NUMBER() <> 2627
RAISERROR etc
END CATCH
Sérieusement, c'est le plus rapide et le plus simultané sans verrous, en particulier à des volumes élevés. Que faire si l'UPDLOCK est escaladé et que toute la table est verrouillée?
Leçon 4: Lors du développement du processus upsert avant de régler les index, je me suis d'abord assuré que la ligne
If Exists(Select…)
se déclencherait pour n'importe quel élément et interdirait les doublons. Nada. En peu de temps, il y a eu des milliers de doublons car le même élément atteindrait l'upert à la même milliseconde et les deux transactions verraient un inexistant et effectueraient l'insertion. Après de nombreux tests, la solution consistait à utiliser l'index unique, à détecter l'erreur et à réessayer, permettant à la transaction de voir la ligne et d'effectuer une mise à jour à la place d'une insertion.
J'ai ajouté HOLDLOCK qui n'était pas présent à l'origine. Veuillez ignorer la version sans cette indication.
En ce qui me concerne, cela devrait suffire:
INSERT INTO TheTable
SELECT
@primaryKey,
@value1,
@value2
WHERE
NOT EXISTS
(SELECT 0
FROM TheTable WITH (UPDLOCK, HOLDLOCK)
WHERE PrimaryKey = @primaryKey)
De plus, si vous voulez réellement mettre à jour une ligne si elle existe et l'insérer si elle ne l'est pas, vous pourriez trouver cette question utile.
Vous pouvez utiliser MERGE:
MERGE INTO Target
USING (VALUES (@primaryKey, @value1, @value2)) Source (key, value1, value2)
ON Target.key = Source.key
WHEN MATCHED THEN
UPDATE SET value1 = Source.value1, value2 = Source.value2
WHEN NOT MATCHED BY TARGET THEN
INSERT (Name, ReasonType) VALUES (@primaryKey, @value1, @value2)
Je ne sais pas si c'est la voie "officielle", mais vous pouvez essayer le INSERT
, et retomber sur UPDATE
s'il échoue.
Tout d'abord, un grand merci à notre homme @gbn pour ses contributions à la communauté. Je ne peux même pas commencer à expliquer à quelle fréquence je me retrouve à suivre ses conseils.
Quoi qu'il en soit, assez de fanboy.
Pour ajouter un peu à sa réponse, peut-être "l'améliorer". Pour ceux qui, comme moi, se sont sentis instables de ce qu'il fallait faire dans le <> 2627
scénario (et pas de CATCH
vide n'est pas une option). J'ai trouvé cette petite pépite de technet .
BEGIN TRY
INSERT etc
END TRY
BEGIN CATCH
IF ERROR_NUMBER() <> 2627
BEGIN
DECLARE @ErrorMessage NVARCHAR(4000);
DECLARE @ErrorSeverity INT;
DECLARE @ErrorState INT;
SELECT @ErrorMessage = ERROR_MESSAGE(),
@ErrorSeverity = ERROR_SEVERITY(),
@ErrorState = ERROR_STATE();
RAISERROR (
@ErrorMessage,
@ErrorSeverity,
@ErrorState
);
END
END CATCH