Sql server 这些SQL Server过程是并发安全的吗?

Sql server 这些SQL Server过程是并发安全的吗?,sql-server,tsql,Sql Server,Tsql,我有下表,其中包含工作单位: create table [dbo].[Queue] ( [QueueId] bigint not null identity(1,1), [UserId] bigint default null, -- irrelevant foreign key here primary key clustered ([QueueId] asc) ); go 一组工作人员不断围绕此表旋转,并通过将UserId字段从null更改为正值来获取一个或多个工作

我有下表,其中包含工作单位:

create table [dbo].[Queue]
(
    [QueueId] bigint not null identity(1,1),
    [UserId] bigint default null, -- irrelevant foreign key here
    primary key clustered ([QueueId] asc)
);
go
一组工作人员不断围绕此表旋转,并通过将
UserId
字段从
null
更改为正值来获取一个或多个工作单元。不应该有两个工作人员能够同时更新相同的
QueueId
,而且他们不应该等待(readpass应该有帮助)

使用以下方法可以轻松地向表中添加工作:

/**
* Push some work units.
* (rewritten from basic while insert to @Larnu's Tally suggestion)
*/
create procedure [dbo].[spPushWork]
    @Count int
as
begin
    if @Count < 1 or @Count > 1000000 throw 50001, N'@Count must be 1-1M.', 1;

    with [num] as
    (
        select [num] from (values (null),(null),(null),(null),(null),(null),(null),(null),(null),(null)) [num]([num])
    ), [tally] as
    (
        select top (@Count)
            row_number() over (order by (select null)) as [ind]
        from [num] [num1]
            cross join [num] [num2]
            cross join [num] [num3]
            cross join [num] [num4]
            cross join [num] [num5]
            cross join [num] [num6]
    )
    merge into [dbo].[queue]
    using (select [ind] from [tally]) [t]
    on 1 = 0
    when not matched then insert default values;
end
go

方法2在并发环境中安全吗?选择和更新
用户ID
之间存在差距。但是行应该被锁定…

为什么在生成数字时使用
?这是一个非常慢的方法。@Larnu它是一个很容易为测试输入数据的助手。真正的表格要复杂得多。但这一切归结为更改
用户ID
。但是
WHILE
仍然是一种非常缓慢的方式。理货速度将大大加快,并且将
插入到一个批次中的所有行中;锁定基础表的时间要短得多。为什么
WHILE
是个坏主意:@Larnu我通常不需要像这样插入空数据,但这个技巧很好!
/**
* This grabs work units in a single operation (Select + Update).
*/
create procedure [dbo].[spGrabSafe]
    @UserId bigint
    ,@Count int = 1
as
begin
    if @UserId < 1 throw 50001, N'@UserId must be 1+.', 1;
    if @Count < 1 throw 50001, N'@Count must be 1+.', 2;

    declare @Ids table ([Id] bigint not null);

    -- fetch and claim via single query
    with [cte] as
    (
        select top(@Count) [QueueId]
        from [dbo].[Queue] with (readpast) -- skip locked
        where [UserId] is null
        order by [QueueId] asc
    )
    update [q]
    set [UserId] = @UserId
    output [inserted].[QueueId] into @Ids
    from [dbo].[Queue] [q]
    join [cte] on [cte].[QueueId] = [q].[QueueId];

    select [Id] from @Ids;
end;
go
/**
* This grabs work units in multiple operations (Select&lock + Update).
*/
create procedure [dbo].[spGrabUnsafe]
    @UserId bigint
    ,@Count int = 1
    ,@Delay time = null
as
begin
    if @UserId < 1 throw 50001, N'@UserId must be 1+.', 1;
    if @Count < 1 throw 50001, N'@Count must be 1+.', 1;

    declare @Ids table ([Id] bigint not null);

    begin transaction
        -- fetch the QueueId's
        insert into @Ids
        select top(@Count) [QueueId] 
        from [dbo].[Queue]
        with (xlock, rowlock, readpast) -- lock rows + skip locked
        where [UserId] is null
        order by [QueueId] asc;

        -- claim via UserId
        update [q]
        set [UserId] = @UserId
        from [dbo].[Queue] [q]
        join @Ids [ids] on [ids].[Id] = [q].[QueueId];

        -- this allows to wait a bit to test concurrency
        if @Delay is not null
        begin
            declare @Time varchar(12) = convert(varchar, @Delay, 114);
            waitfor delay @Time;
        end;
    commit transaction;

    select [Id] from @Ids;
end
go