web-dev-qa-db-ja.com

トランザクションの半分は完了し、残りの半分は失敗しますか?

SO、MSDN、およびその他のさまざまなソースで検索を行ったところ、トランザクションに関する質問がたくさん見つかりましたが、私が扱っているものとはまったく違うようです。

確かに、ACIDとトランザクションの概念は理解していますが、私はまだ非常にジュニアDBAです。私は次のようなSQLスクリプトに取り組んでいます。

  1. 値がまだない場合は、2つの異なるテーブルに行を追加します
  2. 存在する場合、列を削除します
  3. 列が存在しない場合は追加します
  4. 動的SQLを生成する2つのストアドプロシージャを変更して、レポートクエリを作成します。

上記の操作はすべて単独で機能し、1つのトランザクション(私がそうであると信じている)にまとめると、それらも一緒に機能します。私が理解しようとしているのは、トランザクションの別の部分が失敗したときに、トランザクションの「部分」がどのように完了する(そしておそらくコミットする)かです。

簡潔にするために、「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   

手順が存在するようになったので、ここで私は混乱しています。最初のALTER PROCEDUREに意図的に間違った名前を入力すると、2番目の手順で行った編集が完了します。以下は、私が書いたトランザクションの短いバージョンです。

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;

上記のステートメントを実行すると、以下のエラーが発生します。

メッセージ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;

私の意図は、「エラーがない場合はすべてをコミットし、エラーがある場合は何もしない」ことでした。

SQL Server 2012 Enterpriseを使用しています。

どんな助けや洞察も大歓迎です。

3
Daniel F

@@ERRORはすべてのステートメントの後でリセットされます。最初のプロシージャを変更しようとしたことによるエラーは、_成功が2番目のプロシージャを変更した後、@@ERRORで検出できなくなりました。これはさらに簡単な再現です。

SELECT 1/0;
GO
SELECT @@ERROR; -- 8134

ただし、間に成功したステートメントを置いた場合:

SELECT 1/0;
GO
SELECT 1/1;
GO
SELECT @@ERROR; -- 0

あなたの場合、あなたはこれをやっています:

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

現在のロジックがすべてをロールバックする唯一の方法は、lastALTERが失敗した場合です。 everyALTERの後に@@ERRORを確認する必要があります。例を示します。

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;
5
Aaron Bertrand

アーロンが説明したように、@@ERRORは、各ALTER PROCEDUREの後にチェックする必要があります。それを回避することはできませんが、トランザクションの使用に関しては、それについての提案にオープンであれば、別のアプローチに置き換えることができます。

SET NOEXEC 設定を操作して、トランザクションを使用しているのと同じ結果を得ることができます。両方のプロシージャが変更されたか、どちらも変更されていません。こうやって:

SET NOEXEC OFF  -- usually the default, but just in case
GO

ALTER PROCEDURE dbo.proc1
AS
  . . .
GO

IF @@ERROR <> 0  -- if an error occurred altering dbo.proc1
  SET NOEXEC ON  -- then skip altering the following one(s)
;
GO

ALTER PROCEDURE dbo.proc2
AS
  . . .
GO

IF @@ERROR <> 0  -- apply the same after each procedure that you want
  SET NOEXEC ON  -- to be either altered or skipped as a single batch
;
GO

. . .

ALTER PROCEDURE dbo.procN
AS
  . . .
GO

IF @@ERROR <> 0  -- strictly, no need to use it after the last one
  SET NOEXEC ON  -- but it is no harm if you do
;
GO

SET NOEXEC OFF  -- just reset to proceed with the rest of the script normally
GO
. . .

SET NOEXEC ONを指定すると、SQL Serverは、スクリプトの最後まで、またはSET NOEXEC OFFに到達するまで後続のステートメントの実行を停止し、後者は実行を再開します。

1
Andriy M