SO、MSDN、およびその他のさまざまなソースで検索を行ったところ、トランザクションに関する質問がたくさん見つかりましたが、私が扱っているものとはまったく違うようです。
確かに、ACIDとトランザクションの概念は理解していますが、私はまだ非常にジュニアDBAです。私は次のようなSQLスクリプトに取り組んでいます。
上記の操作はすべて単独で機能し、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を使用しています。
どんな助けや洞察も大歓迎です。
@@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;
アーロンが説明したように、@@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
に到達するまで後続のステートメントの実行を停止し、後者は実行を再開します。