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つの操作で同じ顧客の新しい注文を同時に挿入したい場合があります。
では、デッドロックを回避し、顧客ごとに一意の増分注文番号を維持できるようにするロック方法または手法は何でしょうか。
注文生成と同じトランザクションでクエリを実行して更新するために、顧客ごとの最後の番号を保持するテーブルを導入します。
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
SQL Server 2005以降では、これはトランザクションやロックを使用せずに、アトミックに行うのが最適です。
update ORDERS
set OrderNumber=OrderNumber+1
output inserted.OrderNumber where CustomerID=@ID
あなたはこれを行うことができます:
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人が同時に同じ顧客の注文を追加しようとすると、最初に顧客をロックした人が勝ち、他の人は待つ必要があります。 。
人々が異なる顧客に注文を挿入している場合、彼らはお互いに邪魔をしません!
これがどのように機能するかを次に示します。
デフォルトのトランザクションレベル 読み取りコミット は、ファントム読み取りからユーザーを保護しません。ファントム読み取りは、別のプロセスがselect
とinsert
の間に行を挿入する場合です。
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未満の場合は確かです。
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
顧客ごとにIDENTITYフィールドを含むテーブルを作成することは可能でしょうか。次に、顧客のテーブルに新しいレコードを挿入し、そこから値を取得できます。
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であることを意味するわけではありません(実際には意味するべきではありません)。一意性の要件を満たしている限り、何でもかまいません。