2010-11-12 14 views
1

Je veux générer un nombre unique à partir d'une table. Il doit bien sûr être thread sûr, donc quand je vérifie le dernier nombre et obtenir '3', puis stocker '4' dans la base de données, je ne veux pas que quelqu'un d'autre entre ces deux actions (obtenir le nombre et stocker un plus dans la base de données) également obtenir « 3 » en arrière, et puis aussi le stockage « 4 »Serveur sql: Est-ce que l'imbrication dans une transcation est suffisante pour obtenir un numéro unique de la base de données?

donc je pensais, le mettre dans une transaction comme celle-ci:

begin transaction 
     declare @maxNum int 
     select @maxNum = MAX(SequenceNumber) from invoice 
      where YEAR = @year 
     if @maxNum is null 
     begin 
      set @maxNum = 0 
     end 
     set @maxNum = @maxNum + 1 
     INSERT INTO [Invoice] 
      ([Year] 
      ,[SequenceNumber] 
      ,[DateCreated]) 
    VALUES 
      (@year 
      ,@maxNum 
      ,GETUTCDATE() 
) 

    commit transaction 

    return @maxNum 

Mais je demandé, est-ce suffisant, pour le mettre dans une transaction? ma première pensée était: il verrouille ce sp pour l'utilisation par d'autres personnes, mais est-ce exact? Comment SQL Server peut-il savoir ce qu'il faut verrouiller à la première étape?

Est-ce que cette construction me garantir que personne d'autre fera la partie select @maxnum juste au moment où je suis updating the @maxnum valeur, et à ce moment recevant le même @maxnum comme je l'ai fait, je suis en difficulté.

J'espère que vous comprenez ce que je veux accomplir, et aussi si vous savez si j'ai choisi la bonne solution.

EDIT: également décrit comme « Comment monothread une procédure stockée »

+1

Et pourquoi - oh pourquoi - n'es-tu pas simplement en utilisant une colonne 'IDENTITY' et laisser tous les nicky-gravely tough-as-ongles travailler sur SQL Server ??? –

+0

@marc_s: Prolly car le nombre doit être réinitialisé chaque 'YEAR'. – GSerg

+1

@GSerg: exécuter un 'DBCC CHECKIDENT (table, RESEED, 1)' une fois tous les Janvier 1 et vous avez terminé .... –

Répondre

1

Comme il s'est avéré, je ne voulais pas verrouiller la table, je voulais juste exécuter la procédure stockée un à la fois. Dans le code C# je placer un verrou sur un autre objet, et c'est ce qui a été discuté ici http://www.sqlservercentral.com/Forums/Topic357663-8-1.aspx

est donc ce que je l'ai utilisé

declare @Result int 
EXEC @Result = 
sp_getapplock @Resource = 'holdit1', @LockMode = 'Exclusive', @LockTimeout = 10000 --Time to wait for the lock 
IF @Result < 0 
BEGIN 
ROLLBACK TRAN 
RAISERROR('Procedure Already Running for holdit1 - Concurrent execution is not supported.',16,9) 
RETURN(-1) 
END 

où « holdit1 » est juste un nom pour la serrure. @result renvoie 0 ou 1 s'il réussit à obtenir le verrou (l'un d'entre eux est celui où il réussit immédiatement et l'autre lorsque vous obtenez le verrou en attente)

2

Non, il ne suffit pas. Le verrou partagé défini par la sélection n'empêche personne de lire cette même valeur en même temps.

Modifier ceci:

select @maxNum = MAX(SequenceNumber) from invoice where YEAR = @year 

à ceci:

select @maxNum = MAX(SequenceNumber) from invoice with (updlock, holdlock) where YEAR = @year 

De cette façon, vous remplacez le verrou partagé avec un verrou de mise à jour, et deux verrous de mise à jour ne sont pas compatibles entre plus.
Le holdlock signifie que le verrou doit être conservé jusqu'à la fin de la transaction. Vous avez donc toujours besoin du bit de transaction.

Notez que cela n'aidera pas s'il y a une autre procédure qui veut aussi faire la mise à jour. Si cette autre procédure lit la valeur sans fournir l'indice updlock, elle sera toujours capable de lire la valeur précédente du compteur. Cela peut être une bonne chose, car il améliore la simultanéité dans les scénarios où les autres lecteurs n'ont pas l'intention de faire une mise à jour plus tard, mais il peut aussi ne pas être ce que vous voulez, ou mettre à jour updlock ou xlock au lieu de placer un verrou exclusif, non compatible avec les verrous partagés.

+0

Qu'en est-il simplement d'utiliser simplement un SET TRANSACTION ISOLATION NIVEAU SERIALIZABLE pour cette transaction? Cela verrouillerait exclusivement tout ce qui est sélectionné, jusqu'à ce que la transaction soit validée ou annulée ... –

+0

@marc_s: Oui, à quel point la concurrence pourrait être pire qu'elle ne pourrait l'être. Je préfère n'utiliser que l'ensemble de serrures minimum requis. S'il y a des gens qui devraient lire la valeur mais n'ont pas l'intention de la changer, je ne les énerverai pas. – GSerg

+0

@marc_s: devez-vous faire cela avant ou après l'instruction de transaction de début? – Michel

5

Si vous voulez avoir l'année et un numéro de séquence stocké dans la base de données, et de créer un numéro de facture de cela, j'utiliser:

  • une colonne InvoiceYear (qui pourrait tout à fait être calculé comme YEAR(InvoiceDate))
  • une colonne InvoiceID INT IDENTITY que vous pouvez réinitialiser chaque année à 1
  • créer une colonne calculée InvoiceNumber comme:

    ALTER TABLE dbo.InvoiceTable 
        ADD InvoiceNumber AS CAST(InvoiceYear AS VARCHAR(4)) + '-' + 
          RIGHT('000000' + CAST(InvoiceID AS VARCHAR(6)), 6) PERSISTED 
    

De cette façon, vous obtenez les numéros de facture automagiquement:

2010-000001 
...... 
2010-0
...... 
2010-123456 

Bien sûr, si vous avez besoin de plus de 6 chiffres (= factures 1 million) - juste ajuster les déclarations RIGHT() et CAST() pour la colonne InvoiceID .

En outre, comme il s'agit d'une colonne calculée persistante, vous pouvez l'indexer pour une récupération rapide.

De cette façon: vous n'avez pas à vous soucier de la simultanéité, des processus stockés, des transactions et des choses comme ça - SQL Server le fera pour vous - gratuitement!