web-dev-qa-db-ja.com

SQL Server2008でのMAX + 1整数の同時実行の問題の回避...独自のIDENTITY値の作成

SQL Server2008列の整数をインクリメントする必要があります。

IDENTITY列を使用する必要があるように思えますが、顧客ごとに個別のカウンターをインクリメントする必要があります。各顧客が1から始まる独自の増分注文番号を取得するeコマースサイトを考えてみてください。値は(顧客ごとに)一意である必要があります。

例えば、

Customer1  (Order #s 1,2,3,4,5...)
Customer2  (Order #s 1,2,3,4,5...)

基本的に、SQLのidentity関数の作業は手動で行う必要があります。これは、顧客の数に制限がなく、顧客ごとにorder #カウンターが必要だからです。

私は非常に快適です:

BEGIN TRANSACTION
  SELECT @NewOrderNumber = MAX(OrderNumber)+1 From Orders where CustomerID=@ID
  INSERT INTO ORDERS VALUES (@NewOrderNumber, other order columns here)
COMMIT TRANSACTION

私の問題は、ロックと並行性の懸念、および一意の値の保証です。 TABLOCKXでロックする必要があるようです。しかし、これは大量のデータベースであり、SELECT MAX+1プロセスを実行して新しい注文レコードを挿入する必要があるたびに、Ordersテーブル全体をロックすることはできません。

ただし、テーブル全体をロックしないと、その顧客に固有の値が得られない可能性があります。注文入力の一部は、マルチスレッドのWindowsプロセスによって事後にバッチで行われるため、2つの操作で同じ顧客の新しい注文を同時に挿入したい場合があります。

では、デッドロックを回避し、顧客ごとに一意の増分注文番号を維持できるようにするロック方法または手法は何でしょうか。

19
stackonfire

注文生成と同じトランザクションでクエリを実行して更新するために、顧客ごとの最後の番号を保持するテーブルを導入します。

TABLE CustomerNextOrderNumber
{
    CustomerID id PRIMARY KEY,
    NextOrderNumber int
}

Selectの更新ロックは、同じ顧客が2つの注文を同時に行った場合の競合状態を回避するのに役立ちます。

BEGIN TRANSACTION

DECLARE @NextOrderNumber INT

SELECT @NextOrderNumber = NextOrderNumber
FROM  CustomerNextOrderNumber (UPDLOCK)
WHERE CustomerID = @CustomerID

UPDATE CustomerNextOrderNumber
SET   NextOrderNumber = NextOrderNumber + 1
WHERE CustomerID = @CustomerID


... use number here


COMMIT

同様ですが、より直接的なアプローチ(Joachim Isakssonに触発された)の更新ロックは、最初の更新によって課されます。

BEGIN TRANSACTION

DECLARE @NextOrderNumber INT

UPDATE CustomerNextOrderNumber
SET   NextOrderNumber = NextOrderNumber + 1
WHERE CustomerID = @CustomerID

SELECT @NextOrderNumber = NextOrderNumber
FROM CustomerNextOrderNUmber
where CustomerID = @CustomerID

.。

COMMIT
9
alexm

SQL Server 2005以降では、これはトランザクションやロックを使用せずに、アトミックに行うのが最適です。

update ORDERS 
set OrderNumber=OrderNumber+1 
output inserted.OrderNumber where CustomerID=@ID
5
J.T. Taylor

あなたはこれを行うことができます:

BEGIN TRANSACTION
  SELECT ID
  FROM Customer WITH(ROWLOCK)
  WHERE Customer.ID = @ID

  SELECT @NewOrderNumber = MAX(OrderNumber)+1 From Orders where CustomerID=@ID
  INSERT INTO ORDERS VALUES (@NewOrderNumber, other order columns here)
COMMIT TRANSACTION

現在、すべての顧客ではなく、顧客テーブルから1人の顧客のみをロックしています。2人が同時に同じ顧客の注文を追加しようとすると、最初に顧客をロックした人が勝ち、他の人は待つ必要があります。 。

人々が異なる顧客に注文を挿入している場合、彼らはお互いに邪魔をしません!

これがどのように機能するかを次に示します。

  • User1は、ID1000の顧客の注文の挿入を開始します。
  • User2は、ID1000の顧客の注文を挿入しようとします。
  • User2は、User1が注文の挿入を完了するまで待つ必要があります。
  • User1が注文を挿入すると、トランザクションがコミットされます。
  • User2は注文を挿入できるようになり、顧客1000の真の最大orderIdを取得することが保証されます。
3
Bassam Mehanni

デフォルトのトランザクションレベル 読み取りコミット は、ファントム読み取りからユーザーを保護しません。ファントム読み取りは、別のプロセスがselectinsertの間に行を挿入する場合です。

BEGIN TRANSACTION
SELECT @NewOrderNumber = MAX(OrderNumber)+1 From Orders where CustomerID=@ID
INSERT INTO ORDERS VALUES (@NewOrderNumber, other order columns here)
COMMIT TRANSACTION

1レベル高い、繰り返し可能な読み取りでさえ、あなたを保護しません。シリアル化可能な最高の分離レベルのみが、ファントム読み取りから保護します。

したがって、1つの解決策は最高の分離レベルです。

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
...

別の解決策は、 tablockx、holdlock、およびupdlockテーブルヒント を使用して、トランザクションのみがテーブルを変更できるようにすることです。 1つ目はテーブルをロックし、2つ目はトランザクションが終了するまでロックを保持し、3つ目は選択用のupdateロックを取得するため、後でアップグレードする必要はありません。

SELECT @NewOrderNumber = MAX(OrderNumber)+1 
From Orders with (tablockx, holdlock, updlock)
where CustomerID=@ID

CustomerIDにインデックスがある場合、これらのクエリは迅速に実行されるため、同時実行性についてはあまり心配しません。1分あたりの注文数が10未満の場合は確かです。

2
Andomar
create table TestIds
(customerId int,
nextId int)

insert into TestIds
values(1,1)
insert into TestIds
values(2,1)
insert into TestIds
values(3,1)

go

create proc getNextId(@CustomerId int)
as

declare @NextId int

while (@@ROWCOUNT = 0)
begin
    select @NextId = nextId
    from TestIds
    where customerId = @CustomerId

    update TestIds
    set nextId = nextId + 1
    where customerId = @CustomerId
    and nextId = @NextId

end

select @NextId  
go
0
JBrooks

顧客ごとにIDENTITYフィールドを含むテーブルを作成することは可能でしょうか。次に、顧客のテーブルに新しいレコードを挿入し、そこから値を取得できます。

0
Antony Scott

2つの完全に異なる要件を関連付けようとしています。

あなたがこれを動かしたとしても。顧客Aの注文が削除された場合はどうなりますか?既存のすべてのレコードに番号を付け直して、連続して1から開始するようにします。これでロックの問題になります。

レコードにID(または場合によってはGUID)を付与します。カウントが必要な場合はクエリを実行し、行番号が必要な場合は(自分でそのポイントを見たことがない)、rownoを使用します。

顧客ごとに自動インクリメントの注文は必要ありません。必要ありません。大量のロックがなければ、注文を行うことはできません。

水平思考時間。

あなたが提示する場合

Order Description Date Due
1     Staples     26/1/2012
2     Stapler     1/3/2012
3     Paper Clips 19/1/2012

注文キーが1、2、3であることを意味するわけではありません(実際には意味するべきではありません)。一意性の要件を満たしている限り、何でもかまいません。

0
Tony Hopkinson