如何获取序列中的下一个数字

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)使用以下表提示之一选择:

  1. WITH(TABLOCKX, HOLDLOCK)
  2. 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已经通过隔离级别或细粒度的表提示提供锁定时,我永远不会使用外部资源来提供锁定.