Question

J'ai une table [File] qui a le schéma suivant

CREATE TABLE [dbo].[File]
(
    [FileID] [int] IDENTITY(1,1) NOT NULL,
    [Name] [varchar](256) NOT NULL,
 CONSTRAINT [PK_File] PRIMARY KEY CLUSTERED 
(
    [FileID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

L'idée est que le FileID est utilisé comme clé pour la table et le nom est le chemin complet qui représente un fichier.

Ce que j'ai essayé de faire est de créer une procédure stockée qui vérifiera si le nom est déjà utilisé, si c'est le cas, utilisez cet enregistrement, sinon créez un nouvel enregistrement.

Mais lorsque je teste le code avec plusieurs threads exécutant simultanément la procédure stockée, des erreurs différentes se produisent.

Cette version du code créera un blocage et lancera une exception d'interblocage sur le client.

CREATE PROCEDURE [dbo].[File_Create]
    @Name varchar(256)
AS
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    BEGIN TRANSACTION xact_File_Create
    SET XACT_ABORT ON

    SET NOCOUNT ON 
    DECLARE @FileID int
    SELECT @FileID = [FileID] FROM [dbo].[File] WHERE [Name] = @Name
    IF @@ROWCOUNT=0
    BEGIN
        INSERT INTO [dbo].[File]([Name])
        VALUES (@Name)
        SELECT @FileID = [FileID] FROM [dbo].[File] WHERE [Name] = @Name
    END

    SELECT * FROM [dbo].[File]
    WHERE [FileID] = @FileID

    COMMIT TRANSACTION xact_File_Create
GO

Cette version du code aboutit à des lignes contenant les mêmes données dans la colonne Nom.

CREATE PROCEDURE [dbo].[File_Create]
    @Name varchar(256)
AS
    BEGIN TRANSACTION xact_File_Create

    SET NOCOUNT ON 
    DECLARE @FileID int
    SELECT @FileID = [FileID] FROM [dbo].[File] WHERE [Name] = @Name
    IF @@ROWCOUNT=0
    BEGIN
        INSERT INTO [dbo].[File]([Name])
        VALUES (@Name)
        SELECT @FileID = [FileID] FROM [dbo].[File] WHERE [Name] = @Name
    END

    SELECT * FROM [dbo].[File]
    WHERE [FileID] = @FileID

    COMMIT TRANSACTION xact_File_Create
GO

Je me demande quelle est la bonne façon de faire ce type d’action? En général, il s'agit d'un modèle que j'aimerais utiliser lorsque les données de colonne sont uniques dans une seule colonne ou dans plusieurs colonnes et qu'une autre colonne est utilisée comme clé.

Merci

Était-ce utile?

La solution

Si vous effectuez une recherche intensive dans le champ Nom, vous souhaiterez probablement l'indexer (unique et peut-être même en cluster s'il s'agit du champ de recherche primaire ). Comme vous n'utilisez pas l'ID de fichier de la première sélection, il suffit de sélectionner count (*) dans le fichier où Name = @Name et de voir s'il est supérieur à zéro (cela empêchera SQL de conserver les verrous de la table). la phase de recherche, car aucune colonne n’est sélectionnée).

Vous êtes sur la bonne voie avec le niveau SERIALIZABLE, car votre action aura une incidence sur le succès ou l'échec des requêtes suivantes avec la présence du nom. La raison pour laquelle la version sans cet ensemble entraîne des doublons est que deux sélections ont été exécutées simultanément et qu’il n’y avait pas d’enregistrement. Elles ont donc toutes les deux inséré (ce qui crée le doublon).

Le blocage avec la version précédente est probablement dû à l’absence d’index rendant le processus de recherche long. Lorsque vous chargez le serveur dans une transaction SERIALIZABLE, tout le reste devra attendre la fin de l'opération. L’index devrait rendre l’opération rapide, mais seuls les tests indiqueront si elle est suffisamment rapide. Notez que vous pouvez répondre à la transaction ayant échoué en soumettant à nouveau: dans des situations réelles, nous espérons que le chargement sera transitoire.

EDIT: En rendant votre table indexée, sans utiliser SERIALIZABLE, vous vous retrouvez avec trois cas:

  • Le nom est trouvé, l'ID est capturé et utilisé. Commun
  • Nom introuvable, insère comme prévu. Commun
  • Le nom est introuvable, l'insertion échoue car une autre correspondance exacte a été publiée dans les millisecondes qui suivent la première. Très rare

Je m'attendrais à ce que ce dernier cas soit vraiment exceptionnel. Il serait donc préférable d'utiliser une exception pour capturer ce cas très rare plutôt que de saisir SERIALIZABLE, qui a de graves conséquences sur les performances.

Si vous vous attendez vraiment à ce qu'il soit courant d'avoir des publications en quelques millisecondes du même nom nouveau , utilisez une transaction SERIALIZABLE en conjonction avec l'index. Ce sera plus lent dans le cas général, mais plus rapide lorsque ces messages seront trouvés.

Autres conseils

Créez d’abord un index unique sur la colonne Nom. Ensuite, à partir de votre code client, vérifiez d'abord si le nom existe en sélectionnant le FileID et en plaçant le nom dans la clause where. Si c'est le cas, utilisez le FileID. Sinon, insérez-en un nouveau.

L'utilisation de la fonction Exists peut nettoyer un peu les choses.

if (Exists(select * from table_name where column_name = @param)
begin
  //use existing file name
end
else
  //use new file name
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top