Luk*_*101 10 sql sql-server sql-server-2014
我有这样一张桌子:
+----+-----------+------+-------+--+
| id | Part | Seq | Model | |
+----+-----------+------+-------+--+
| 1 | Head | 0 | 3 | |
| 2 | Neck | 1 | 3 | |
| 3 | Shoulders | 2 | 29 | |
| 4 | Shoulders | 2 | 3 | |
| 5 | Stomach | 5 | 3 | |
+----+-----------+------+-------+--+
Run Code Online (Sandbox Code Playgroud)
如何在Stomach
模型3 之后插入下一个seq的另一条记录.所以这是新表假设的样子:
+----+-----------+------+-------+--+
| id | Part | Seq | Model | |
+----+-----------+------+-------+--+
| 1 | Head | 0 | 3 | |
| 2 | Neck | 1 | 3 | |
| 3 | Shoulders | 2 | 29 | |
| 4 | Shoulders | 2 | 3 | |
| 5 | Stomach | 5 | 3 | |
| 6 | Groin | 6 | 3 | |
+----+-----------+------+-------+--+
Run Code Online (Sandbox Code Playgroud)
有没有办法设计一个插入查询,它只会在模型3的最高seq之后给出下一个数字.此外,寻找并发安全的东西.
TT.*_*TT. 12
如果您不维护计数器表,则有两种选择.在事务中,首先MAX(seq_id)
使用以下表提示之一选择:
WITH(TABLOCKX, HOLDLOCK)
WITH(ROWLOCK, XLOCK, HOLDLOCK)
TABLOCKX + HOLDLOCK
有点矫枉过正.它会阻止常规的select语句,即使事务很小,也可以认为它很重.
一个ROWLOCK, XLOCK, HOLDLOCK
表提示可能是一个更好的主意(但:进一步阅读与计数表中选择).优点是它不会阻止常规select语句,即当select语句没有出现在SERIALIZABLE
事务中,或者select语句没有提供相同的表提示时.使用ROWLOCK, XLOCK, HOLDLOCK
仍将阻止插入语句.
当然,您需要确保程序中没有其他部分选择MAX(seq_id)
没有这些表提示(或在SERIALIZABLE
事务外),然后使用此值插入行.
请注意,根据以这种方式锁定的行数,SQL Server可能会将锁升级为表锁.了解更多关于锁升级这里.
使用的插入过程WITH(ROWLOCK, XLOCK, HOLDLOCK)
如下所示:
DECLARE @target_model INT=3;
DECLARE @part VARCHAR(128)='Spine';
BEGIN TRY
BEGIN TRANSACTION;
DECLARE @max_seq INT=(SELECT MAX(seq) FROM dbo.table_seq WITH(ROWLOCK,XLOCK,HOLDLOCK) WHERE model=@target_model);
IF @max_seq IS NULL SET @max_seq=0;
INSERT INTO dbo.table_seq(part,seq,model)VALUES(@part,@max_seq+1,@target_model);
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
END CATCH
Run Code Online (Sandbox Code Playgroud)
另一种可能更好的想法是拥有一个计数器表,并在计数器表上提供这些表提示.该表如下所示:
CREATE TABLE dbo.counter_seq(model INT PRIMARY KEY, seq_id INT);
Run Code Online (Sandbox Code Playgroud)
然后,您将更改插入过程,如下所示:
DECLARE @target_model INT=3;
DECLARE @part VARCHAR(128)='Spine';
BEGIN TRY
BEGIN TRANSACTION;
DECLARE @new_seq INT=(SELECT seq FROM dbo.counter_seq WITH(ROWLOCK,XLOCK,HOLDLOCK) WHERE model=@target_model);
IF @new_seq IS NULL
BEGIN SET @new_seq=1; INSERT INTO dbo.counter_seq(model,seq)VALUES(@target_model,@new_seq); END
ELSE
BEGIN SET @new_seq+=1; UPDATE dbo.counter_seq SET seq=@new_seq WHERE model=@target_model; END
INSERT INTO dbo.table_seq(part,seq,model)VALUES(@part,@new_seq,@target_model);
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
END CATCH
Run Code Online (Sandbox Code Playgroud)
优点是使用较少的行锁(即每个模型一个dbo.counter_seq
),并且锁升级无法锁定整个dbo.table_seq
表,从而阻塞了select语句.
您可以测试所有这些并自己查看效果,方法是WAITFOR DELAY '00:01:00'
选择序列counter_seq
,然后在第二个SSMS选项卡中摆弄表格.
PS1:使用ROW_NUMBER() OVER (PARTITION BY model ORDER BY ID)
不是一个好方法.如果删除/添加行,或者ID已更改,则序列将更改(考虑应永远不会更改的发票ID).另外,在检索单行时必须确定所有先前行的行号的性能方面是个坏主意.
PS2:当SQL Server已经通过隔离级别或细粒度的表提示提供锁定时,我永远不会使用外部资源来提供锁定.
归档时间: |
|
查看次数: |
5559 次 |
最近记录: |