ABS*_*ABS 5 sql-server stored-procedures transaction locking
注意:如果有类似的问题,请参考我。
我有两张桌子Request
& Receipt
。
我希望表Receipt
保持锁定状态,以便从任何地方进行 CRUD 操作,直到事务完成。我需要事务在存储过程中。
不可能吗?如何?
为什么要Receipt
保持锁定状态?
导致内部的插入或删除请求Receipt
将无法操作它!请注意,我需要从它的到它的每一个ReceiptId
都是连续的!Request
start
finish
使用密钥表以并发友好的方式分配收据编号,并且仍然保证收据编号永远不会被重复使用,并且(几乎)永远不会包含间隙。
我创建了一个最低限度完整的可验证示例,您可以将其用作了解并发性的基础,这将使您走上一条良好的道路。
在 tempdb 中完成这项工作,这样我们就不会杀死您实际工作中的任何重要内容:
USE tempdb;
Run Code Online (Sandbox Code Playgroud)
如果我们创建的对象已经存在,那么我们将首先删除它们。这使我们可以轻松修改下面的代码并多次重新运行以进行测试。
IF OBJECT_ID(N'dbo.Requests', N'U') IS NOT NULL
BEGIN
DROP PROCEDURE dbo.CreateRequests;
DROP TABLE dbo.Keys;
DROP TABLE dbo.Receipts;
DROP TABLE dbo.Requests;
END
GO
Run Code Online (Sandbox Code Playgroud)
请求表 - 根据需要添加列:
CREATE TABLE dbo.Requests
(
RequestID int NOT NULL IDENTITY(1,1)
CONSTRAINT Requests_PK
PRIMARY KEY CLUSTERED
, RequestDate datetime NOT NULL
, Notes nvarchar(100) NOT NULL
) ON [PRIMARY]
WITH (DATA_COMPRESSION = PAGE);
Run Code Online (Sandbox Code Playgroud)
收据表。ReceiptID 是系统生成的,ReceiptNum 是我们向用户展示的号码。
CREATE TABLE dbo.Receipts
(
ReceiptID int NOT NULL IDENTITY(1,1)
CONSTRAINT Receipt_PK
PRIMARY KEY CLUSTERED
, RequestID int NOT NULL
CONSTRAINT Receipt_Request_FK
FOREIGN KEY
REFERENCES dbo.Requests(RequestID)
, ReceiptNum int NOT NULL
, ReceiptCreatedOn datetime NOT NULL
) ON [PRIMARY]
WITH (DATA_COMPRESSION = PAGE);
Run Code Online (Sandbox Code Playgroud)
Key 表,我们存储最近分配的 ReceiptNum 值:
CREATE TABLE dbo.Keys
(
KeyName varchar(100) NOT NULL
CONSTRAINT Keys_PK
PRIMARY KEY CLUSTERED
, LastUsedKeyValue int NOT NULL
) ON [PRIMARY];
GO
Run Code Online (Sandbox Code Playgroud)
CreateRequests 存储过程:
CREATE PROCEDURE dbo.CreateRequests
(
@NumReceipts int = 1 /* default to 1 receipt */
, @RequestNote nvarchar(100) = N''
, @RequestID int OUTPUT /* this will contain the newly created RequestID */
)
AS
BEGIN
SET NOCOUNT ON; /* prevent stored procedure from displaying "x records affected" */
DECLARE @msg nvarchar(1000) = N''; /* used to display error messages */
DECLARE @ret int = 0; /* stored procedure return value */
DECLARE @LastUsedKey int;
DECLARE @keys TABLE
(
KeyValue int NOT NULL
);
DECLARE @req TABLE
(
RequestID int NOT NULL PRIMARY KEY
);
IF @NumReceipts > 0 AND @NumReceipts <= 10000 /* 10,000 is the arbitrarily chosen
maximum number of receipts this stored proc supports */
BEGIN
/* Atomically allocate @NumReceipts keys to use for Receipt Numbers */
UPDATE dbo.Keys
SET LastUsedKeyValue = LastUsedKeyValue + @NumReceipts
OUTPUT inserted.LastUsedKeyValue INTO @keys (KeyValue)
WHERE KeyName = 'ReceiptNum';
SELECT @LastUsedKey = k.KeyValue
FROM @keys k;
IF @LastUsedKey IS NULL /* the first time we run this code, we insert a new Key */
BEGIN
INSERT INTO dbo.Keys (KeyName, LastUsedKeyValue)
VALUES ('ReceiptNum', @NumReceipts)
SET @LastUsedKey = @NumReceipts;
END
/* Create a new Request row, outputting the inserted RequestID
into a temporary table for use in the Receipts table */
INSERT INTO dbo.Requests (RequestDate, Notes)
OUTPUT inserted.RequestID INTO @req(RequestID)
SELECT GETDATE(), @RequestNote;
/* Create the new Receipts rows */
;WITH src AS ( /* this is configured to support a maximum of 10,000 new receipts */
SELECT TOP(@NumReceipts)
ReceiptNum = (v5.Num + (v4.Num * 10) + (v3.Num * 100) + (v2.Num * 1000)
+ (v1.Num * 10000)) + 1 + (@LastUsedKey - @NumReceipts)
FROM (VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9)) v1(Num)
CROSS JOIN (VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9)) v2(Num)
CROSS JOIN (VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9)) v3(Num)
CROSS JOIN (VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9)) v4(Num)
CROSS JOIN (VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9)) v5(Num)
ORDER BY v1.Num
, v2.Num
, v3.Num
, v4.Num
, v5.Num
)
INSERT INTO dbo.Receipts (RequestID, ReceiptNum, ReceiptCreatedOn)
SELECT r.RequestID, ReceiptNum, GETDATE()
FROM @req r
CROSS JOIN src
ORDER BY src.ReceiptNum;
/* we'll return the newly created RequestID in @RequestID */
SELECT @RequestID = r.RequestID
FROM @req r;
/* -1 indicates success */
SET @ret = -1;
END
ELSE
BEGIN
SET @msg = N'Invalid number of receipts requested.';
RAISERROR (@msg, 14, 1);
SET @ret = 1;
END
RETURN @ret;
END
GO
Run Code Online (Sandbox Code Playgroud)
在这里,我们添加了三个不同的请求,使用不同数量的收据来显示存储过程的功能:
DECLARE @ReqID int;
DECLARE @ReturnValue int;
EXEC @ReturnValue = dbo.CreateRequests @NumReceipts = 19
, @RequestNote = N'Test Request #1'
, @RequestID = @ReqID OUT
SELECT ReturnValue = @ReturnValue, ReqID = @ReqID;
SELECT *
FROM dbo.Receipts rec
WHERE rec.RequestID = @ReqID;
Run Code Online (Sandbox Code Playgroud)
输出:
??????????????????????????? ? 返回值?请求 ID ? ??????????????????????????? ? -1 ? 1 ? ??????????????????????????? ?????????????????????????????????????????????????????? ??????????????? ? 收据ID ? 请求 ID ? 收据号?收据创建时间? ?????????????????????????????????????????????????????? ??????????????? ? 1 ? 1 ? 1 ? 2017-06-01 09:04:37.107 ? ? 2 ? 1 ? 2 ? 2017-06-01 09:04:37.107 ? ? 3 ? 1 ? 3 ? 2017-06-01 09:04:37.107 ? ? 4 ? 1 ? 4 ? 2017-06-01 09:04:37.107 ? ? 5 ? 1 ? 5 ? 2017-06-01 09:04:37.107 ? ? 6 ? 1 ? 6 ? 2017-06-01 09:04:37.107 ? ? 7 ? 1 ? 7 ? 2017-06-01 09:04:37.107 ? ? 8 ? 1 ? 8 ? 2017-06-01 09:04:37.107 ? ? 9 ? 1 ? 9 ? 2017-06-01 09:04:37.107 ? ? 10 ? 1 ? 10 ? 2017-06-01 09:04:37.107 ? ? 11 ? 1 ? 11 ? 2017-06-01 09:04:37.107 ? ? 12 ? 1 ? 12 ? 2017-06-01 09:04:37.107 ? ? 13 ? 1 ? 13 ? 2017-06-01 09:04:37.107 ? ? 14 ? 1 ? 14 ? 2017-06-01 09:04:37.107 ? ? 15 ? 1 ? 15 ? 2017-06-01 09:04:37.107 ? ? 16 ? 1 ? 16 ? 2017-06-01 09:04:37.107 ? ? 17 ? 1 ? 17 ? 2017-06-01 09:04:37.107 ? ? 18 ? 1 ? 18 ? 2017-06-01 09:04:37.107 ? ? 19 ? 1 ? 19 ? 2017-06-01 09:04:37.107 ? ?????????????????????????????????????????????????????? ???????????????
运行 #2:
EXEC @ReturnValue = dbo.CreateRequests @NumReceipts = 3
, @RequestNote = N'Test Request #2'
, @RequestID = @ReqID OUT
SELECT ReturnValue = @ReturnValue, ReqID = @ReqID;
SELECT *
FROM dbo.Receipts rec
WHERE rec.RequestID = @ReqID;
Run Code Online (Sandbox Code Playgroud)
输出:
??????????????????????????? ? 返回值?请求 ID ? ??????????????????????????? ? -1 ? 2 ? ??????????????????????????? ?????????????????????????????????????????????????????? ??????????????? ? 收据ID ? 请求 ID ? 收据号?收据创建时间? ?????????????????????????????????????????????????????? ??????????????? ? 20 ? 2 ? 20 ? 2017-06-01 09:04:37.160? ? 21 ? 2 ? 21 ? 2017-06-01 09:04:37.160? ? 22 ? 2 ? 22 ? 2017-06-01 09:04:37.160? ?????????????????????????????????????????????????????? ???????????????
运行 #3:
EXEC @ReturnValue = dbo.CreateRequests @NumReceipts = 9999
, @RequestNote = N'Test Request #3'
, @RequestID = @ReqID OUT
SELECT ReturnValue = @ReturnValue, ReqID = @ReqID;
SELECT *
FROM dbo.Receipts rec
WHERE rec.RequestID = @ReqID;
Run Code Online (Sandbox Code Playgroud)
输出:
??????????????????????????? ? 返回值?请求 ID ? ??????????????????????????? ? -1 ? 3 ? ??????????????????????????? ?????????????????????????????????????????????????????? ??????????????? ? 收据ID ? 请求 ID ? 收据号?收据创建时间? ?????????????????????????????????????????????????????? ??????????????? ? 23 ? 3 ? 23 ? 2017-06-01 09:04:37.210 ? ? 24 ? 3 ? 24 ? 2017-06-01 09:04:37.210 ? ? 25 ? 3 ? 25 ? 2017-06-01 09:04:37.210 ? ? 26 ? 3 ? 26 ? 2017-06-01 09:04:37.210 ? ..... ? 10021?3 ? 10021?2017-06-01 09:04:37.210 ? ?????????????????????????????????????????????????????? ???????????????
为了验证并发性以及收据编号的分配是否可靠,我在三个单独的 SSMS 窗口中同时运行以下代码,创建了近 30,000 个收据:
DECLARE @note nvarchar(100);
DECLARE @ReqID int;
DECLARE @ReturnValue int;
DECLARE @loop int = 1;
DECLARE @maxloops int = 1000;
WHILE @loop < @maxloops
BEGIN
SET @note = N'Concurrency request loop ' + CONVERT(nvarchar(10), @loop);
EXEC @ReturnValue = dbo.CreateRequests @NumReceipts = 5
, @RequestNote = @note
, @RequestID = @ReqID OUT;
SET @loop += 1;
END;
Run Code Online (Sandbox Code Playgroud)