2013-10-03 6 views
5

私は、アプリケーションの(SQL Serverを使用している)請求書のコレクションには、番号付けにギャップがないことが法的に要求されています。したがって、これらが請求書番号の場合、これは許可されません:[1, 2, 3, 4, 8, 10]これは順次ではないためです。そのためにはInvoicesテーブルにInvoiceNumberという列があります。これに加えて、組織ごとに現在の請求書番号を保持するInvoiceNumbersテーブルがあります(組織ごとに独自のシーケンスが必要なため)。ストアドプロシージャは、InvoiceNumberInvoicesに原子的に埋め込む責任があります。 InvoiceNumbersテーブルで現在のカウンタを1増加させ、その新しい値をInvoicesテーブルに入力するか、エラーが発生した場合にトランザクションをロールバックします。これはうまくいく。競合状態のないSQL Serverで順次請求書番号を作成

新しい要件が追加されました。特定の受注は同じ請求書と同じ請求書番号を共有する必要があります。以前はすべての受注が個別に請求されていました。このため、当日​​の午前中に請求書を作成し、それを現在のFinancialPeriod(基本的に勤務日)に関連付けます。これはすべての注文に使用される請求書になります。しかし、組織は共有請求書を必要とするタイプの注文を作成しないため、最初に作成した請求書を浪費する翌日に請求書を作成することはなく、ギャップ。

私にとって最も簡単な解決策は、開始日に作成される共有請求書のInvoiceNumberを遅延して埋めることでした。その日に注文が作成され、InvoiceNumberがまだNULLの場合は、番号を作成します。これにより、InvoiceNumberは決して使用されなくなります(Invoiceレコードが使用されなくなっても意味がありません)。

この目的のために、既存のInvoiceの場合はNULLの場合にのみ、InvoiceNumberが入力されます。私はSQL Serverのロック方法と、2つのデータベーストランザクションがであると判断し、競合状態が発生する可能性があるかどうかを確信しており、カウンタを増分して1つの数値を浪費してギャップを作成します。

本質的に、この長い質問は次のように述べられます:2つの同時データベーストランザクションが同じ@invoiceIDif(@currentNumber is null)ブロックを入力することができますか?

あなたは私がここから得ているが、私はそれが私の場合に適用されるかわからない参照ロック部:

Pessimistic lock in T-SQL

CREATE PROCEDURE [dbo].[CreateInvoiceNumber] 
    @invoiceID int, 
    @appID int 
AS 
BEGIN 

    SET NOCOUNT ON; 

    if not exists (select 1 from InvoiceNumbers where ApplicationID = @appID) insert into InvoiceNumbers values (@appID, 1) 

    declare @currentNumber int = null; 

    select @currentNumber = convert(int, i.InvoiceNumber) 
    from Invoices i 
    with (HOLDLOCK, ROWLOCK) 
    where i.ID = @invoiceID 

    if(@currentNumber is null) 
    begin 
     update InvoiceNumbers set @currentNumber = Value = Value + 1 
      where ApplicationID = @appID 

     update Invoices set InvoiceNumber = @currentNumber where ID = @invoiceID   
    end 

    select convert(nvarchar, @currentNumber) 
END 

EDIT

で述べたように私のコメント、これらおよびその他の書き込み操作は、C#アプリケーションロジックから開始されたデータベーストランザクションの一部です。デフォルトオプションのSqlConnectionの通常のBeginTransactionのみです。例外が発生した場合はもちろん、ロールバックされます。

+0

この質問を参照してください。それは、あなたの問題を解決する真実の解決策の答えです。 http://dba.stackexchange.com/questions/36603/handling-concurrent-access-to-a-key-table-without-deadlocks-in-sql-server –

+0

@MaxVernon - 私が間違っている場合は私を修正します。それは私がやっていることをもっとやりたいと思っているようだが、デッドロックを避けて回復することは私の優先事項ではない。いずれにせよ、それは私がこれまでに得たものよりもかなり複雑であるので、私はその答えから必要なものを蒸留するのは難しいです。 – JulianR

+0

だから私は答えとして投稿していない。 –

答えて

1

データベース分離レベルがREAD COMMITTEDに設定されていることを確認してください。

SET TRANSACTION ISOLATION LEVEL READ COMMITTED 

これはデフォルトの分離レベルです。行が読み取られる前にすべてのトランザクションをコミットする必要があるため、ダーティ・リードは発生しません。

InvoiceNumbersテーブルを更新するときには、トランザクション内にあることを確認し、ACIDの原則をすべてここに適用し、すべてをアトミック(単位としてコミットまたはトランザクションがロールバック)するようにします。

+0

ありがとうございます。私は、C#アプリケーションコードがこの操作とDbTransactionで発生する他の書き込みをラップすることに言及しておきます。その場合、このストアドプロシージャは競合条件なしである必要がありますか? – JulianR

関連する問題