为什么我的UPDATE存储过程多次执行?

时间:2018-07-17 11:32:03

标签: sql-server stored-procedures

我使用存储过程来管理仓库。 PDA扫描仪扫描添加的库存并将其批量(插回时)发送到SQL数据库(SQL Server 2016)。

SQL数据库相当偏远(另一个国家/地区),因此某些查询有时会延迟,但是这一特定查询是有问题的:即使库存表很好,我在更新的占用率时也遇到了一些问题仓库地点。 PDA以SMALLINT的形式跟踪每个位置中添加的项目,然后将该值发送回下面的存储过程。

PDA“ send_spots”查询:

SELECT spot, added_items FROM spots WHERE change=1

存储过程:

CREATE PROCEDURE [dbo].[update_spots]
@spot VARCHAR(10),
@added_items SMALLINT
AS
BEGIN
    BEGIN TRAN
    UPDATE storage_spots
    SET remaining_capacity = remaining_capacity - @added_items
    WHERE storage_spot=@spot
    IF @@ROWCOUNT <> 1
    BEGIN
        ROLLBACK TRAN
        RETURN - 1
    END
    ELSE
    BEGIN
        COMMIT TRAN
        RETURN 0
    END
END
GO

如果remaining_capacity的值为0,则PDA在下一轮不能向其添加更多项目。但是在此过程中,我有负值,因为据称该查询运行了两次(因此两次减去@added_items)。

有没有办法做到这一点?我该如何解决?据我了解,如果受影响的行是!= 1,则应该取消该事务(ROLLBACK),但这也许是其他原因。

编辑:借助于@Zero的当前解决方案:

CREATE PROCEDURE [dbo].[update_spots]
    @spot VARCHAR(10),
    @added_racks SMALLINT
AS
BEGIN
    -- Recover current capacity of the spot
    DECLARE @orig_capacity SMALLINT
    SELECT TOP 1
        @orig_capacity = remaining_capacity
    FROM storage_spots
    WHERE storage_spot=@spot

    -- Test if double is present in logs by comparing dates (last 10 seconds)
    DECLARE @is_double BIT = 0
    SELECT @is_double = CASE WHEN EXISTS(SELECT *
    FROM spot_logs
    WHERE log_timestamp >= dateadd(second, -10, getdate()) AND storage_spot=@spot AND delta=@added_racks)
    THEN 1 ELSE 0 END

    BEGIN
        BEGIN TRAN
        UPDATE storage_spots
        SET remaining_capacity= @orig_capacity - @added_racks
        WHERE storage_spot=@spot

        IF @@ROWCOUNT <> 1 OR @is_double <> 0
            -- If double, rollback UPDATE
            ROLLBACK TRAN
        ELSE
            -- If no double, commit UPDATE
            COMMIT TRAN

        -- write log
        INSERT INTO spot_logs
            (storage_spot, former_capacity, new_capacity, delta, log_timestamp, double_detected)
        VALUES 
            (@spot, @orig_capacity, @orig_capacity-@added_racks, @added_racks, getdate(), @is_double)
    END
END
GO

2 个答案:

答案 0 :(得分:0)

我找到了here的替代SQL查询,该查询按照我需要的方式进行更新,但是使用DECLARE临时值。对于我来说,它会更好吗?还是我的初始查询正确?

初始查询:

UPDATE storage_spots
    SET remaining_capacity = remaining_capacity - @added_items
    WHERE storage_spot=@spot

替代查询:

DECLARE @orig_capacity SMALLINT
SELECT TOP 1 @orig_capacity = remaining_capacity 
    FROM storage_spots 
    WHERE spot=@spot
UPDATE Products 
    SET remaining_capacity = @orig_capacity - @added_items 
    WHERE spot=@spot

此外,我应该摆脱ROLLBACK / COMMIT指令吗?

答案 1 :(得分:0)

我正在考虑可能的原因(以及一种追踪原因的方法),然后它就打了我-您没有价值验证!

这是一个简单的例子来说明问题:

 Spot | capacity 
 ---------------
  x1  |     1

Update spots set capacity = capacity - 2 where spot = 'X1'

您的扫描仪很可能为您提供的数量超出了您的承受能力。 我不确定您的业务逻辑如何发展,但是您需要执行

Update spots set capacity = capacity - @added_items where spot = 'X1' and capacity  >= @added_items
if @@rowcount <> 1;
   rollback;

编辑:不执行验证即可跟踪问题的几种方法: 创建一个日志记录表(使用timestampuser id(连接到数据库的用户),session idold valuenew valuedelta value (添加的项目)。


选项1: 记录所有将值从正更改为负的更新(至少直到您找出问题为止)。 此选项的缺点是它不会注册不会导致容量减负的重复调用。


选项2:(记录相同的更新): 创建脚本,该脚本创建一个全局临时表并从该表中删除早于...的时间戳(假设每分钟大约10分钟(使用数字))。

此临时表应保存传递给您的更新语句的数据,以便“ spot”,“ added_items” +“ timestamp”(用于跟踪)。 现在至关重要的部分:当您调用更新语句时,请检查临时表中是否存在类似的记录(相同的位置和add_items,以及当前时间戳为between timestamp and [timestamp + 1 second]的位置-再次使用数字)。如果存在这样的记录,则该记录会更新,如果没有,则将其添加到临时表中。

这将注册彼此之间在一秒钟内(或您选择的任何时间范围内)的相同更新。