【问题标题】:Unable to update database by calling stored procedure with a commit inside无法通过调用内部提交的存储过程来更新数据库
【发布时间】:2020-07-20 05:23:05
【问题描述】:

我有一个这样的存储过程。

CREATE PROCEDURE StudentSproc
    (@StudentID VARCHAR(50),
     @Name VARCHAR(50))
AS
BEGIN
  BEGIN TRAN

  BEGIN TRY
    INSERT INTO Student(StudentID, Name)
    VALUES (@StudentID,@Name)
    COMMIT TRAN;
  END TRY
  BEGIN CATCH
     ROLLBACK TRAN
  END CATCH
END;

我正在尝试从 python 执行它:

db_conn_str = 'DRIVER={ODBC Driver 17 for SQL Server};SERVER=' + server + ';PORT=1433;DATABASE=' + database + ';UID=' + username + ';PWD=' + password
cnxn = pyodbc.connect(db_conn_str)

cursor = cnxn.cursor()
st = f"exec master.dbo.StudentSproc @StudentID = ?, @Name = ? "
s_id = "101"
name = "Charles"
params = (s_id, name)
cursor.execute(st, params)
print(f"executed sproc by {st}")

这没有错误并执行存储过程,但它没有更新数据库,我很惊讶。我知道我必须在connect() 调用中使用autocommit=True,但是如果存储过程中有提交,为什么还要这样做呢?

【问题讨论】:

  • 你有一个很好的基于假设和猜测的解释。但正如已经解释的那样,您的 sql 代码会捕获错误但不会重新引发错误,因此您的应用程序(和您)无法知道插入是否成功。更糟糕的是,您显然在 MASTER 数据库中创建了一个用户存储过程(显然也是一个表)。在我看来,您对您的连接、您应该使用的数据库以及实际执行的代码有些困惑。

标签: python sql-server stored-procedures pyodbc


【解决方案1】:

没有错误,因为您使用的是try/catch。这就像在其他语言中一样 - 如果您 catch 出现异常,它不会返回给客户端。被抓到了。

您可以回滚catch中的tran,然后再次throw,以便将错误返回给客户端。

CREATE PROCEDURE StudentSproc(
@StudentID VARCHAR(50),
@Name VARCHAR(50)) 
AS
  BEGIN
    BEGIN TRAN;

    BEGIN TRY
        INSERT INTO Student(StudentID, Name)
        VALUES (@StudentID,@Name);    
    COMMIT TRAN;
    END TRY
    BEGIN CATCH
         if (@@trancount > 0) ROLLBACK TRAN; -- make sure that a transaction still exists before trying to roll back
         THROW; -- now that we have dealt with the transaction, return the error to the client
    END CATCH
  END;

我还刚刚注意到您在master 数据库中有您的过程。你可能并不真正想要它,master 是一个系统数据库。

您还提到您知道必须使用自动提交,但根据我们的讨论,我现在了解到您实际上并没有使用它,您只是想知道为什么必须使用它。这引入了第二种可能性。

在 SQL Server 中,唯一重要的提交是“最外层”提交。任何“嵌套”提交实际上除了减少 @@trancount 的值之外并没有做任何事情。

例如:

begin tran;
insert MyTable values (1);
begin tran; -- this doesn't really do anything, it just increments @@trancount
insert MyTable values (2);
commit; -- this does nothing other than decrement @@trancount
-- if we were to execute a rollback here, all of the data would be gone
commit; -- only this commit matters

另一方面,rollback 的工作方式不同。单个rollback 将回滚所有 嵌套事务,将@@trancount 值减少为零。

如果您启动两个事务(一个在客户端代码中,一个在存储过程中),但只发出一次提交,那么您的“真实”事务实际上仍然处于打开状态。

【讨论】:

  • SP中没有错误,括号错误是我错误复制到问题时的错误。
  • @user2399453 干杯,我已经从我的答案中编辑了该评论
  • 只是为了明确您的答案不起作用或解释我所描述的问题。我完全按照我现在运行的方式粘贴了代码,以消除任何拼写错误等
  • @user2399453 如果您传入的 @studentid@name 值违反了表上的任何类型的约束(主键、外键、检查约束、数据类型等),那么 catch 块将被命中,事务将回滚,不会出现错误,并且您的数据不会更新,这与您问题中观察到的条件相匹配。
  • @user2399453 哦,我明白了,你说你需要自动提交,但实际上你的代码中并没有。好的,我会更新我的答案来解决这个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-12
  • 2016-02-20
相关资源
最近更新 更多