Dans une tentative de découpler une application de notre base de données monolithique, nous avons essayé de changer les colonnes INT IDENTITY de diverses tables pour qu'elles soient une colonne calculée PERSISTED qui utilise COALESCE. Fondamentalement, nous avons besoin de l'application découplée pour pouvoir mettre à jour la base de données pour les données communes partagées entre de nombreuses applications tout en permettant aux applications existantes de créer des données dans ces tables sans avoir besoin de modifier le code ou la procédure.
Donc, essentiellement, nous sommes passés d'une définition de colonne de;
PkId INT IDENTITY(1,1) PRIMARY KEY
à;
PkId AS AS COALESCE(old_id, external_id, new_id) PERSISTED NOT NULL,
old_id INT NULL, -- Values here are from existing records of PkId before table change
external_id INT NULL,
new_id INT IDENTITY(2000000,1) NOT NULL
Dans tous les cas, le PkId est également une CLÉ PRIMAIRE et dans tous les cas sauf un, il est CLUSTERED. Toutes les tables ont les mêmes clés et index étrangers que précédemment. En substance, le nouveau format permet au PkId d'être fourni par l'application découplée (comme external_id), mais permet également au PkId d'être la valeur de la colonne IDENTITY, permettant ainsi au code existant qui repose sur la colonne IDENTITY via l'utilisation de SCOPE_IDENTITY et @@ IDENTITY de travailler comme avant.
Le problème que nous avons rencontré est que nous avons rencontré quelques requêtes qui s'exécutaient dans un délai acceptable pour s'éteindre complètement. Les plans de requête générés utilisés par ces requêtes ne ressemblent en rien à ce qu'ils étaient auparavant.
Étant donné que la nouvelle colonne est une CLÉ PRIMAIRE, le même type de données qu'avant, et PERSISTE, je m'attendais à ce que les requêtes et les plans de requête se comportent de la même manière qu'auparavant. Le PkId INT COMPUTED PERSISTED INT devrait-il essentiellement se comporter de la même manière qu'une définition INT explicite en termes de la façon dont SQL Server produira le plan d'exécution? Y a-t-il d'autres problèmes probables avec cette approche que vous pouvez voir?
Le but de ce changement était censé nous permettre de changer la définition de la table sans avoir besoin de modifier les procédures et le code existants. Compte tenu de ces problèmes, je ne pense pas que nous puissions adopter cette approche.
la source
Réponses:
PREMIER
Vous n'avez probablement pas besoin de trois colonnes:
old_id
,external_id
,new_id
. Lanew_id
colonne, étant unIDENTITY
, aura une nouvelle valeur générée pour chaque ligne, même lorsque vous l'insérez dansexternal_id
. Mais, entreold_id
etexternal_id
, ceux-ci sont à peu près mutuellement exclusifs: soit il y a déjà uneold_id
valeur, soit cette colonne, dans la conception actuelle, sera justeNULL
si vous utilisezexternal_id
ounew_id
. Étant donné que vous n'ajouterez pas un nouvel identifiant "externe" à une ligne qui existe déjà (c'est-à-dire qui a uneold_id
valeur), et qu'il n'y aura pas de nouvelles valeurs à entrerold_id
, alors il peut y avoir une colonne utilisée aux deux fins.Donc, débarrassez-vous de la
external_id
colonne et renommez-laold_id
en quelque chose commeold_or_external_id
ou autre. Cela ne devrait nécessiter aucune modification réelle de quoi que ce soit, tout en réduisant une partie de la complication. Au plus, vous devrez peut-être appeler la colonneexternal_id
, même si elle contient des "anciennes" valeurs, si le code d'application est déjà écrit pour être inséré dansexternal_id
.Cela réduit la nouvelle structure à être juste:
Maintenant, vous n'avez ajouté que 8 octets par ligne au lieu de 12 octets (en supposant que vous n'utilisez pas l'
SPARSE
option ou la compression de données). Et vous n'avez pas eu besoin de changer de code, de code T-SQL ou d'application.SECONDE
Poursuivant dans cette voie de simplification, regardons ce qu'il nous reste:
old_or_external_id
colonne a déjà des valeurs, ou recevra une nouvelle valeur de l'application, ou restera commeNULL
.new_id
aura toujours une nouvelle valeur générée, mais cette valeur ne sera utilisée que si laold_or_external_id
colonne l'estNULL
.Il n'y a jamais un moment où vous auriez besoin de valeurs à la fois dans
old_or_external_id
etnew_id
. Oui, il y aura des moments où les deux colonnes ont des valeurs car ellesnew_id
sont anIDENTITY
, mais cesnew_id
valeurs sont ignorées. Encore une fois, ces deux champs s'excluent mutuellement. Et maintenant?Maintenant, nous pouvons voir pourquoi nous en avions besoin
external_id
en premier lieu. Étant donné qu'il est possible d'insérer dans uneIDENTITY
colonne à l'aide deSET IDENTITY_INSERT {table_name} ON;
, vous pouvez vous passer de tout changement de schéma et ne modifier votre code d'application que pour encapsuler lesINSERT
instructions / opérations dansSET IDENTITY_INSERT {table_name} ON;
et lesSET IDENTITY_INSERT {table_name} OFF;
instructions. Vous devez ensuite déterminer la plage de départ pour réinitialiser laIDENTITY
colonne (pour les valeurs nouvellement générées) car elle devra être bien au-dessus des valeurs que le code d'application va insérer car l'insertion d'une valeur plus élevée entraînera la prochaine valeur générée automatiquement. être supérieur à la valeur MAX actuelle. Mais vous pouvez toujours insérer une valeur inférieure à la valeur IDENT_CURRENT .La combinaison des colonnes
old_or_external_id
etnew_id
n'augmente pas non plus les chances de se retrouver dans une situation de valeurs qui se chevauchent entre les valeurs générées automatiquement et les valeurs générées par l'application, car l'intention d'avoir les colonnes 2, voire 3, est de les combiner en une valeur de clé primaire, et ce sont toujours des valeurs uniques.Dans cette approche, il vous suffit de:
Laissez les tableaux comme:
Cela ajoute 0 octet à chaque ligne, au lieu de 8, voire 12.
SET IDENTITY_INSERT {table_name} ON;
etSET IDENTITY_INSERT {table_name} OFF;
déclarations.DEUXIÈME, partie B
Une variation de l'approche notée directement ci-dessus consisterait à insérer les valeurs du code d'application commençant par -1 et descendant à partir de là. Cela laisse les
IDENTITY
valeurs comme étant les seules à augmenter . L'avantage ici est que non seulement vous ne compliquez pas le schéma, vous n'avez pas non plus à vous soucier de rencontrer des ID qui se chevauchent (si les valeurs générées par l'application se retrouvent dans la nouvelle plage générée automatiquement). Ceci n'est qu'une option si vous n'utilisez pas déjà des valeurs d'ID négatives (et il semble assez rare que les gens utilisent des valeurs négatives sur les colonnes générées automatiquement, cela devrait donc être une possibilité probable dans la plupart des situations).Dans cette approche, il vous suffit de:
Laissez les tableaux comme:
Cela ajoute 0 octet à chaque ligne, au lieu de 8, voire 12.
-1
.SET IDENTITY_INSERT {table_name} ON;
etSET IDENTITY_INSERT {table_name} OFF;
déclarations.Ici, vous devez toujours le faire
IDENTITY_INSERT
, mais: vous n'ajoutez pas de nouvelles colonnes, vous n'avez pas besoin de "réamorcer" lesIDENTITY
colonnes et vous n'avez aucun risque futur de chevauchements.DEUXIÈME, 3e partie
Une dernière variante de cette approche serait de permuter éventuellement les
IDENTITY
colonnes et d'utiliser à la place des séquences . La raison de cette approche est de pouvoir avoir les valeurs d'insertion de code d'application qui sont: positives, au-dessus de la plage générée automatiquement (pas en dessous), et pas nécessaireSET IDENTITY_INSERT ON / OFF
.Dans cette approche, il vous suffit de:
Copiez la
IDENTITY
colonne dans une nouvelle colonne qui n'a pas laIDENTITY
propriété, mais qui a uneDEFAULT
contrainte à l'aide de la fonction NEXT VALUE FOR :Cela ajoute 0 octet à chaque ligne, au lieu de 8, voire 12.
SET IDENTITY_INSERT {table_name} ON;
etSET IDENTITY_INSERT {table_name} OFF;
déclarations.CEPENDANT , en raison de l'exigence que le code avec
SCOPE_IDENTITY()
ou@@IDENTITY
fonctionne toujours correctement, le passage aux séquences n'est pas actuellement une option car il semble qu'il n'y ait pas d'équivalent de ces fonctions pour les séquences :-(. Triste!la source
IDENTITY_INSERT
, mais je ne l'ai pas testé. Je ne suis pas sûr que l'option # 1 va résoudre votre problème global, c'était juste une observation pour réduire la complexité inutile. Pourtant, si vous avez plusieurs threads insérant de nouveaux ID "externes", comment garantissez-vous qu'ils sont uniques?IDENTITY_INSERT ON
pour le même tableau en deux sessions et a été insérer dans les deux sans aucun problème.