存储过程只运行一次

Stored procedure only runs once

我有一个 SQL 服务器存储过程:

CREATE PROCEDURE dbo.GetNextNumber
    @NextNumber int OUT
AS
    UPDATE SystemSettings 
    SET REGISTRY_NUMBER = REGISTRY_NUMBER + 1  
    WHERE ID = 1;

    SELECT @NextRegistryNumber = REGISTRY_NUMBER
    FROM SystemSettings
    WHERE ID = 1;
  
    SELECT 'NextNumber' = @NextRegistryNumber;

我从 C# API 方法调用它:

public int GetNextRegistryNumberFC()
{
    string procedure = "GetNextNumber";
        
    // Create ADO.NET objects.
    // SqlConnection con = new SqlConnection(connectionString);
    // SqlCommand cmd = new SqlCommand(procedure, con);

    using (var con = new SqlConnection(ConfigurationManager.ConnectionStrings[<ConnectionString>].ConnectionString))
    using (var cmd = new SqlCommand(procedure, con))

    try
    {
        // Configure command and add input parameters.
        cmd.CommandType = CommandType.StoredProcedure;

        // Add the output parameter.               
        cmd.Parameters.Add("@NextNumber", SqlDbType.Int).Direction = ParameterDirection.Output;                

        // Execute the command.
        con.Open();
        cmd.ExecuteNonQuery();

        NextRegistryNumber = Convert.ToInt32(cmd.Parameters["@NextNumber"].Value);
    }
    catch (Exception ex)
    {
        throw;
    }
    finally
    {
        cmd.Dispose();
        con.Close();
        con.Dispose();
    }

    return NextRegistryNumber;
}

这是我第一次使用 运行 它。之后它每次只是 return 相同的数字而不是增加它。我不确定我发生了什么。如果我 运行 存储过程本身每次都有效。有人知道这里出了什么问题吗?

这是打字错误吗?

SELECT 'NextNumber' = @NextRegistryNumber;

这将输出一个 table,其中包含一个名为 'NextNumber' 的列,其值在 @NextRegistryNumber 中,即:您已经完成了一个列别名。

应该是:

SELECT @NextNumber = @NextRegistryNumber;

SET @NextNumber = @NextRegistryNumber;

如果两个或多个连接同时调用此存储过程,将产生意外结果。两次执行都会增加该字段,然后 return 相同的值。即使此问题已修复,更新查询也会在单行中创建一个热点,并发请求在等待彼此更新该行时阻塞。

为了避免这种情况,SQL 服务器(和其他数据库)有 SEQUENCE 可以使用最小锁定自动递增的对象:

CREATE SEQUENCE Registration_Numbers START WITH 1;

NEXT VALUE FOR 函数将增加一个序列和 return 新值:

SELECT NEXT VALUE FOR Registration_Numbers;

这个查询很短,不需要存储过程:

var sql="SELECT NEXT VALUE FOR Registration_Numbers;";
using(var con=new SqlConnection(connectionString))
using(var cmd=new SqlCommand(sql,con))
{
    var new_id=(long)cmd.ExecuteScalar();
    return new_id;
}

这里不需要try/catchusing 块确保连接和命令对象将被释放,即使发生异常也是如此。 using 即使在 finally 不会被调用的情况下也能正常工作。

也不需要重新抛出。下面的 catch 块与没有 catch 完全没有区别。

catch (Exception ex)
{
    throw;
}

如果异常被处理(例如记录),这样的块将有意义。

原子更新和读取

如果使用 table 是绝对必要的(为什么?),OUTPUT 子句可用于 return 来自 UPDATE 命令的新值:

CREATE PROCEDURE dbo.GetNextNumber
AS
    UPDATE SystemSettings 
    SET REGISTRY_NUMBER = REGISTRY_NUMBER + 1  
    OUTPUT inserted.REGISTRY_NUMBER
    WHERE ID = 1;

可以使用相同的代码来调用存储过程。只有 CommandType 需要更改

var proc_name=" dbo.GetNextNumber";
using(var con=new SqlConnection(connectionString))
using(var cmd=new SqlCommand(proc_name,con))
{
    cmd.CommandType = CommandType.StoredProcedure;

    var new_id=(long)cmd.ExecuteScalar();
    return new_id;
}