当另一半失败时,交易的一半完成?

Dan*_*l F 4 t-sql transaction sql-server-2012 acid

我在 SO、MSDN 和其他各种来源上进行了一些搜索,发现了很多关于事务的问题,但似乎没有一个正是我正在处理的问题。

诚然,尽管我确实了解 ACID 和事务的概念,但我仍然是初级 DBA。我正在处理一个 SQL 脚本:

  1. 向 2 个不同的表添加一行,前提是这些值尚不存在
  2. 如果存在,则删除列
  3. 如果不存在则添加一列
  4. 更改 2 个生成动态 SQL 以构建报表查询的存储过程

上述所有操作都是独立工作的,并且在(我认为是)单个事务中包含时,它们也会协同工作。我试图理解的是,当事务的另一部分失败时,事务的“部分”如何完成(并可能提交)。

为简洁起见,假设我有名为“ProcessReport”和“RetrieveReport”的工作程序。这两个过程中的动态 SQL 都以“SELECT”开头,然后从那里开始构建。测试用例:

CREATE TABLE ReportTable (FirstName VARCHAR(100), LastName VARCHAR(100), OrderId INT);
GO
CREATE PROCEDURE ProcessReport
AS
BEGIN
  DECLARE @SQL VARCHAR(4000)

  -- start creating dynamic sql   
  SET @SQL = 'SELECT FirstName, LastName, '
  --create rest of dynamic SQL here...

  INSERT INTO ReportTable (FirstName, LastName) EXEC(@SQL)
END
GO 

CREATE PROCEDURE RetrieveReport
AS
BEGIN
  DECLARE @SQL VARCHAR(4000)

  -- start creating dynamic sql   
  SET @SQL = 'SELECT FirstName, LastName '
  --create rest of dynamic SQL here...

  SET @SQL = @SQL + ' FROM ReportTable '
  EXEC (@SQL)
END
GO   
Run Code Online (Sandbox Code Playgroud)

现在程序已经存在,这就是让我感到困惑的地方:当我故意为第一个输入错误名称时ALTER PROCEDURE,我对第二个程序所做的编辑就完成了。以下是我写的交易的简短版本:

BEGIN TRANSACTION 
GO 

ALTER PROCEDURE ProcessReport2  -- this procedure does not exist, causing an error
AS
BEGIN
  DECLARE @SQL VARCHAR(4000)

  -- start creating dynamic sql   
  SET @SQL = 'SELECT FirstName, LastName, '
  --create rest of dynamic SQL here...

  INSERT INTO ReportTable(FirstName, LastName) EXEC(@SQL)
END
GO
ALTER PROCEDURE RetrieveReport
AS
BEGIN
  DECLARE @SQL VARCHAR(4000)

  -- start creating dynamic sql   
  SET @SQL = 'SELECT DISTINCT FirstName, LastName '  --DISTINCT added
  --create rest of dynamic SQL here...

  SET @SQL = @SQL + ' FROM ReportTable '
  EXEC (@SQL)

END
GO 

IF @@ERROR = 0
  COMMIT TRANSACTION;
ELSE
  ROLLBACK TRANSACTION;
Run Code Online (Sandbox Code Playgroud)

运行上述语句时,出现以下错误:

消息 208,级别 16,状态 6,过程 ProcessReport2,第 2 行 对象名称“ProcessReport2”无效。

消息 3902,级别 16,状态 1,第 3 行 COMMIT TRANSACTION 请求没有相应的 BEGIN TRANSACTION。

关于 ACID 和事务,我的理解是,BEGIN TRANSACTION 和 COMMIT TRANSACTION 之间的一切要么完成,要么什么都不做。在 SSMS 中,当我在 RetrieveReport 过程中单击“修改”时,我现在看到以“SELECT DISTINCT ...”开头的动态 SQL

谁能告诉我我哪里出错了?是不是我的东西

IF @@ERROR = 0
COMMIT TRANSACTION;

ELSE
ROLLBACK TRANSACTION;
Run Code Online (Sandbox Code Playgroud)

我的意图是“如果没有错误,就提交整个事情,如果有错误,什么都不做。”

我正在使用 SQL Server 2012 企业版。

任何帮助和/或见解将不胜感激。

Aar*_*and 6

@@ERROR在每条语句后重置。您尝试更改第一个程序的错误不再可通过以下方式检测到@@ERROR成功更改第二个程序后,。这是一个更简单的重现:

SELECT 1/0;
GO
SELECT @@ERROR; -- 8134
Run Code Online (Sandbox Code Playgroud)

但是,如果我在两者之间添加一个成功的声明:

SELECT 1/0;
GO
SELECT 1/1;
GO
SELECT @@ERROR; -- 0
Run Code Online (Sandbox Code Playgroud)

在您的情况下,您正在这样做:

ALTER dbo.p1 ... -- fails
GO
-- @@ERROR here would be <> 0, but you're not checking it here!

ALTER dbo.p2 ... -- succeeds
GO
-- You're checking @@ERROR here, but success is 0
Run Code Online (Sandbox Code Playgroud)

您当前的逻辑将所有内容回滚的唯一方法是最后一次 ALTER失败。您需要@@ERROR每个 ALTER. 这是一个例子。

USE tempdb;
GO
CREATE TABLE #x(err INT);
GO

BEGIN TRANSACTION;
GO

CREATE PROCEDURE dbo.foo -- fails
AS 
  SELECT foo FROM sys.objects;
GO
IF @@ERROR <> 0 
  INSERT #x(err) SELECT 1;
GO

CREATE PROCEDURE dbo.blat -- succeeds, but will get rolled back
AS 
  SELECT 1;
GO
IF @@ERROR <> 0 
  INSERT #x(err) SELECT 1;
GO

IF EXISTS (SELECT 1 FROM #x)
  ROLLBACK TRANSACTION;
ELSE
  COMMIT TRANSACTION;

DROP TABLE #x;
Run Code Online (Sandbox Code Playgroud)

  • 你不能说“IF &lt;something&gt; ALTER PROCEDURE”。`ALTER` 需要*在它自己的批次中*。因此,您可以像我在回答中建议的那样使用标签和“GOTO”,或者您可以在 #temp 表中维护每个操作的错误状态,并在最后回滚整个操作(这就是 Red Gate SQL 比较的方式)是不是 IIRC)。请阅读 http://www.sommarskog.se/error-handling-I.html 和 http://www.sommarskog.se/error-handling-II.html 和 http://www.sommarskog.se/error_handling_2005。 html (4认同)