ストアドプロシージャのデバッグでは、再現条件をそろえ、処理の分岐・エラー・トランザクションの状態を順に調べます。ただし、使える構文やツールはデータベース製品によって異なります。本記事はSQL ServerのT-SQLを中心に解説し、最後にMySQLとPostgreSQLでの違いを整理します。
2026年10月確認:SSMSの組み込みT-SQLデバッガーは18.0で削除されています。「最新のSSMSでデバッグメニューを開けばステップ実行できる」という手順は使えません。また、Azure Data Studioは2026年2月28日に廃止されました。現行の調査方法と、安全に使える代替手段を選びましょう。
ストアドプロシージャの基本構造
ストアドプロシージャは、データベース側に保存する処理のまとまりです。入力パラメーターを受け取り、検索や更新を実行できます。ストアドプロシージャであること自体が処理の高速化や、処理全体の自動的なトランザクション化を保証するわけではありません。
SQL Serverで動かす基本例
次の例は、架空の2行だけを検索する学習用プロシージャです。既存の業務テーブルには触れませんが、プロシージャの作成自体はデータベースの変更なので、専用の開発用データベースで行います。dbo.usp_DebugSampleが既に存在する場合は、既存定義を上書きせず別名にしてください。例中のTHROWとCONCATはSQL Server 2012以降を前提にしています。
CREATE PROCEDURE dbo.usp_DebugSample
@Parameter1 INT,
@Parameter2 NVARCHAR(50),
@Debug BIT = 0
AS
BEGIN
SET NOCOUNT ON;
IF @Parameter1 IS NULL OR @Parameter2 IS NULL
THROW 50001, N'Required parameter is missing.', 1;
IF @Debug = 1
PRINT N'phase=start';
SELECT s.Column1, s.Column2
FROM (VALUES (1, N'sample'), (2, N'other'))
AS s(Column1, Column2)
WHERE s.Column1 = @Parameter1
AND s.Column2 = @Parameter2;
DECLARE @Rows INT = @@ROWCOUNT;
IF @Debug = 1
PRINT CONCAT(N'phase=complete; rows=', @Rows);
END;
GO
EXEC dbo.usp_DebugSample
@Parameter1 = 1,
@Parameter2 = N'sample',
@Debug = 1;期待する結果は1 / sampleの1行と、開始・完了のメッセージです。@@ROWCOUNTは対象のSQLの直後に保存します。PRINTなどの別の文を先に実行すると、確認したい行数を失うことがあります。パラメーターの中身はメッセージに出していません。根拠:@@ROWCOUNTの仕様。
GOはSSMSなどのクライアントが扱うバッチ区切りで、T-SQLの文ではありません。アプリケーションから実行する場合は作成文と呼び出し文を別々に送ります。定義と実行を区別する点はCREATE PROCEDUREの公式仕様とGOの説明でも確認できます。
デバッグ環境の設定
最初に接続先と再現条件を確認する
- 本番から隔離した開発・検証環境を使い、匿名化した最小限のテストデータを用意します。テスト用の復元でも、本番の個人情報や認証情報をそのまま持ち込まないでください。
- 接続先サーバー、データベース、SQL Serverのバージョン、プロシージャ定義、呼び出すパラメーターの型・長さ、実行ユーザーを照合します。アプリと手動実行で違う接続先を見ていないかが最初の確認点です。
- 正常ケースと異常ケースの期待結果を先に書きます。結果セットだけでなく、更新行数、戻り値・出力パラメーター、エラー番号、終了後のトランザクションの状態も対象にします。
- 通常の調査には必要な実行・参照権限だけを使います。調査のためだけに本番でsysadminを付与したり、ファイアウォールを広く開放したりしないでください。
-- 接続先・状態の確認用。業務データの変更は行わない。
SELECT
SERVERPROPERTY('ProductVersion') AS ProductVersion,
DB_NAME() AS DatabaseName,
@@TRANCOUNT AS TransactionCount,
XACT_STATE() AS TransactionState;「最後にROLLBACKするから本番で実行しても安全」とは限りません。実行中のロック、外部への送信、別接続での処理などは影響が残り得ます。更新処理の再現やステップ実行は検証環境で行い、本番では既存の監視情報から調べることを優先します。
デバッグツールの選定
SQL Server Management Studio(SSMS)
SSMSはクエリ実行、メッセージの確認、実行計画、Extended Eventsの調査に利用できます。一方、組み込みのブレークポイント、ステップ実行、ウォッチ機能はSSMS 18.0で削除されています。これは接続設定の不備ではありません。古いSSMSへのダウングレードを解決策にせず、目的に合う現行ツールを選びます。根拠:MicrosoftのSSMSロードマップ説明。
Visual StudioとSQL Server Data Tools(SSDT)
Microsoftは、T-SQLデバッグの選択肢としてVisual Studio向けSQL Server Data Tools(SSDT)を案内しています。これはVisual Studio Codeとは別製品です。SQL Serverのインスタンスが対象で、Azure SQL Database、Azure SQL Managed Instance、SQL database in FabricはこのT-SQLデバッガーの対象外です。利用するVisual Studio・SSDT・サーバーの組み合わせと必要なコンポーネントを確認してください。根拠:T-SQLデバッガーの対応範囲。
このデバッガーは高い権限を要し、Microsoftの手順では接続ログインにsysadminが必要とされています。通常のアプリケーションアカウントや本番の権限を拡大するのではなく、管理者の承認を得た隔離環境でのみ検討します。停止中にロックを長時間保持する可能性もあります。根拠:デバッガー実行条件とテスト環境の推奨。
Azure Data StudioとVisual Studio Code
Azure Data Studioは2026年2月28日に廃止され、更新やセキュリティ修正を受けられません。新しい調査環境としては推奨しません。MicrosoftはVisual Studio CodeのMSSQL拡張機能などへの移行を案内しています。ただし、クエリエディターを移行することと、T-SQLの対話的デバッガーが使えることは別です。根拠:Azure Data Studioの廃止と移行先。
dbForge Studio for SQL Server
既存の選択肢として、dbForge Studio for SQL ServerにはT-SQLのステップ実行や変数確認の機能があります。導入前に、使用エディション、ライセンス、対応バージョン、サーバー側コンポーネント、接続権限の条件を確認します。第三者製のデバッガーも本番環境の権限やネットワーク設定を無条件に変更して使うものではありません。根拠:DevartのDebugger overview。
デバッグの手法
最小の再現スクリプトを作る
まず、アプリケーションと同じ型・長さのパラメーターで、検証環境のプロシージャを呼び出します。成功例と失敗例で、入力、データの状態、権限、セッション設定のどれが違うかを一つずつ確認します。単体のSELECTに書き換えると、トランザクション、呼び出し順、トリガーなどの条件が変わる場合があるため、最終的には元の呼び出し経路でも確認します。
ステップ実行とブレークポイント
対話的な調査が必要な場合は、対応するVisual Studio/SSDTの環境で行います。次はSSMS向けの手順ではありません。
- 検証用SQL Serverへ接続し、対象の定義とテスト入力を確認します。
- SQL Server Object Explorerから接続先の新しいクエリを開き、対象プロシージャを呼ぶEXEC文を用意します。
- デバッグを開始し、プロシージャ呼び出しでステップインします。
- 目的の分岐にブレークポイントを置き、Locals、Watch、Call Stackで変数と呼び出し元を確認します。値の書き換えは再現条件を変えるため、まず観察だけにとどめます。
- 調査後は実行・トランザクション・接続の状態を確認し、テスト用の変更を片付けます。「デタッチ」は残りの文の実行を続ける場合があるため、停止と同じ操作だと考えないでください。
具体的な操作はMicrosoftのストアドプロシージャのデバッグ手順を参照してください。
PRINTステートメントで進捗を確認する
PRINTはSQL Serverのクライアントへメッセージを返す方法で、永続的なログ保存機能ではありません。先ほどの@Debugのように切り替えを設け、処理段階と件数など、必要最小限の値だけを出します。NULLを含む文字列連結や暗黙の型変換にも注意してください。
PRINTの文字列には上限があり、非Unicodeでは8,000文字、Unicodeでは4,000文字を超えると切り詰められます。長いSQL文やエラー全文の完全な保存先としては使えません。根拠:PRINTの仕様。
クライアントへ直ちに進捗を送る必要がある場合は、SQL Serverでは低い重大度のRAISERROR ... WITH NOWAITを使う方法もあります。これは情報通知用であり、失敗の通知やエラーの再送出とは分けます。実際の画面表示はクライアント側の扱いにも依存します。
-- 学習用の固定値。入力データや秘密情報を含めない。
DECLARE @Phase NVARCHAR(30) = N'validation';
RAISERROR (N'phase=%s', 10, 1, @Phase) WITH NOWAIT;正しい綴りはRAISERRORです。RAISEERRORではありません。RAISERRORはSET XACT_ABORTを尊重しないため、新しいエラー処理ではTHROWを基本にします。根拠:RAISERRORの仕様。
TRY…CATCHでエラーを握りつぶさない
CATCHでメッセージを表示するだけで終了すると、呼び出し元が失敗を検知できなくなる場合があります。エラー番号、発生行、プロシージャ名などを調べたうえで、回復できないエラーはCATCH内の引数なしのTHROW;で再送出します。エラー本文には入力値が含まれる場合があるため、利用者への表示や保存先を分けてください。
-- 上の学習用プロシージャを作成した後、検証環境で実行する。
BEGIN TRY
EXEC dbo.usp_DebugSample
@Parameter1 = NULL,
@Parameter2 = N'sample';
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_STATE() AS ErrorState,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_LINE() AS ErrorLine;
THROW;
END CATCH;この例は意図的にエラー50001を発生させます。エラーが表示されることが期待結果です。ERROR_STATE()はエラーの補助的な状態番号であり、トランザクションの状態ではありません。また、TRY…CATCHは同じ実行階層のコンパイルエラー、接続の切断やクライアントからの中断など、すべての失敗を捕捉できるわけではありません。根拠:TRY…CATCHの制約、THROWの再送出仕様。
XACT_STATEでトランザクションの状態を確認する
XACT_STATE() = 0:現在のユーザートランザクションはありません。XACT_STATE() = 1:トランザクションがあり、コミット可能です。ただし業務上コミットしてよいかは別の判断です。XACT_STATE() = -1:コミットできず、書き込みもできません。完全なROLLBACKが必要です。ログテーブルへのINSERTもこの状態では失敗します。
@@TRANCOUNTだけではコミット可能かを判定できません。次は、自分で開始したトランザクションだけを扱う独立した学習用バッチです。専用の新しい接続で、実行前に@@TRANCOUNT = 0かつIMPLICIT_TRANSACTIONSがOFFであることを確認してください。既存の呼び出し元トランザクションへそのまま組み込むためのテンプレートではありません。
-- 専用の検証接続でのみ実行する。
-- 前提: @@TRANCOUNT = 0、IMPLICIT_TRANSACTIONS は OFF。
-- 前提を満たさなければ、既存トランザクションに触れず中止する。
IF @@TRANCOUNT <> 0 OR (@@OPTIONS & 2) = 2
BEGIN
RAISERROR (N'Use a dedicated connection without a transaction.', 16, 1);
RETURN;
END;
-- この例は意図的に主キー違反を起こす。
SET XACT_ABORT ON;
CREATE TABLE #ProcedureDebugDemo (
Id INT PRIMARY KEY,
Value INT NOT NULL
);
INSERT INTO #ProcedureDebugDemo (Id, Value) VALUES (1, 10);
BEGIN TRY
BEGIN TRANSACTION;
UPDATE #ProcedureDebugDemo SET Value = 20 WHERE Id = 1;
INSERT INTO #ProcedureDebugDemo (Id, Value) VALUES (1, 30);
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
DECLARE @ErrorNumber INT = ERROR_NUMBER();
DECLARE @ErrorState INT = ERROR_STATE();
DECLARE @TransactionState SMALLINT = XACT_STATE();
IF XACT_STATE() <> 0
ROLLBACK TRANSACTION;
SELECT @ErrorNumber AS ErrorNumber,
@ErrorState AS ErrorState,
@TransactionState AS StateBeforeRollback,
XACT_STATE() AS StateAfterRollback;
SELECT Id, Value FROM #ProcedureDebugDemo;
THROW;
END CATCH;冒頭のガードは既存トランザクションや暗黙のトランザクションがある接続を拒否します。このガードだけはXACT_ABORTに従わないRAISERRORとRETURNで中止します。前提を満たした場合の期待結果は、主キー違反のエラー番号2627、ロールバック後の状態0、Value = 10への復元です。最後のTHROWによるエラーは想定内です。調査後は専用接続を閉じて、一時テーブルとセッション設定を残さないようにします。これは仕様から示した確認項目であり、各環境で実行結果を検証してください。
呼び出し元が開始したトランザクションを、子のプロシージャが無条件にROLLBACKすると、呼び出し元の変更まで取り消します。開始・コミット・ロールバックの担当を明確にし、必要ならセーブポイントを含めて設計してください。XACT_STATE() = -1ではセーブポイントへのロールバックもできません。根拠:XACT_STATE、SET XACT_ABORT。
ログの活用方法
テーブルに構造化したログを記録する
専用テーブルを使う場合は、自由文だけでなく、UTC時刻、処理名、相関ID、処理段階、エラー番号などを分けて保存すると検索しやすくなります。次は開発用の設計例です。保存する項目、アクセス権、保持期間、削除方法、書き込み量を決めたうえで導入します。
CREATE TABLE dbo.ProcedureLogDemo (
LogId BIGINT IDENTITY(1,1) PRIMARY KEY,
LoggedAtUtc DATETIME2(3) NOT NULL DEFAULT SYSUTCDATETIME(),
CorrelationId UNIQUEIDENTIFIER NOT NULL,
ProcedureName SYSNAME NOT NULL,
Phase NVARCHAR(30) NOT NULL,
ErrorNumber INT NULL,
ErrorState INT NULL
);ログへのINSERTを業務処理と同じトランザクション内で行うと、ROLLBACK時にログも消えます。逆に、コミット不能なトランザクション内ではINSERTできません。「CATCHにINSERTを書けば必ず残る」という設計にしないでください。必要ならトランザクションの整理後にアプリケーション側のログへ記録するなど、保存先と失敗時の動作を別途設計します。ログ書き込みの失敗で元のエラーを隠さないことも重要です。
パスワード、接続文字列、アクセストークン、カード情報、個人情報、入力パラメーターの全量は出力しません。エラー本文、SQL本文、実行計画にも値やオブジェクト名が含まれることがあります。必要な情報だけを許可リストで選び、閲覧権限を制限します。相関IDも実データとの対応をたどれる場合は適切に保護してください。
ファイルにログを記録する場合
ストアドプロシージャからxp_cmdshellを呼び、ログ文字列をechoなどのOSコマンドへ連結する方法は勧めません。SQL ServerからOS上のコマンドを実行する機能であり、入力がコマンドとして解釈される危険や、強い権限での実行につながります。デバッグ目的だけで有効化しないでください。根拠:xp_cmdshellのセキュリティ上の注意。
ファイル保存が必要なら、アプリケーションの既存ロガーや、管理者が設計したExtended Eventsの保存先を使う方法を検討します。ファイル名や保存先を利用者入力から組み立てず、アクセス権・容量上限・ローテーション・保持期間を決めます。
Extended Eventsとイベントログを使い分ける
SQL Serverのエラーや実行状況の観察にはExtended Eventsが選択肢になります。まず既存セッションで必要な情報が取れているか確認し、追加する場合も対象データベース、イベント、時間帯を絞ります。SQL本文やパラメーターの全量収集は避け、負荷と保存データの機密性を確認してください。新規セッションの作成・変更には環境に応じた権限が必要です。根拠:Extended Eventsの概要と権限。
RAISERROR ... WITH LOGは、SQL Serverのエラーログとアプリケーションログに記録する指定です。sysadminまたはALTER TRACE権限が必要で、ログに記録されるエラーのサイズにも制限があります。通常の開始・終了メッセージのために権限を追加したり、ループ内で大量に記録したりしないでください。根拠:RAISERRORのLOGオプション。
よくあるデバッグの問題と対処法
パフォーマンスの問題
- まず遅い条件と速い条件の入力、件数、実行計画、待機状態を比較します。ステップ実行の停止時間を本来の処理時間に含めないでください。
- 実行計画で推定行数と実際の行数、読み取り量、結合や並べ替えを確認してから、索引やクエリの変更を検証します。索引追加や全体のキャッシュ消去を、根拠なしに本番で行わないでください。
- 実際の実行計画は対象SQLを実行して得る情報です。UPDATEやDELETEの計画を見るつもりで本番データを変更しないよう、推定計画や既存の収集情報との違いを理解します。
実行計画の種類はSQL Serverのクエリ処理アーキテクチャを参照してください。
デッドロックの発生
単なる待機とデッドロックを区別します。SQL Serverのデッドロックでは被害トランザクションが選ばれ、通常はエラー1205が返ります。利用可能なExtended Eventsのデッドロック情報から、競合した処理とロック取得順序を調べます。
- 複数の処理でテーブル・行へアクセスする順序をそろえ、トランザクションを必要以上に長くしない。
- 適切な索引などで不要な読み取りやロックを減らす。ただし変更は再現試験で効果を確認する。
- 再試行するなら、対象エラー、回数上限、待機時間を決め、取り消された業務トランザクション全体をやり直す。外部送信や二重更新が起きないよう、冪等性を確認する。
TRY…CATCHを置くだけでデッドロックを防げるわけではありません。エラーを無視して処理を続けたり、すべてのエラーを無制限に再試行したりしないでください。根拠:SQL Serverのデッドロックガイド。
エラー処理の欠如
正常終了と異常終了を呼び出し元が区別できるかを確認します。エラーを捕捉する必要がなければ無理にCATCHを追加せず、捕捉した場合は必要な後始末と再送出を行います。アプリケーション側でも、タイムアウト・接続障害・キャンセル後の接続やトランザクションの扱いを確認してください。
無限ループの発生
終了条件だけでなく、反復ごとに条件へ近づく更新があるか、対象行が減っているか、NULLによって比較の意味が変わっていないかを確認します。検証用の回数上限を設ける場合、上限到達を正常終了として隠さず、明確なエラーにします。大量のPRINTやログ挿入で、かえって遅くしていないかも確認しましょう。
-- 上限判定の学習例。業務テーブルへの書き込みはない。
DECLARE @Counter INT = 0;
DECLARE @Limit INT = 100;
DECLARE @Finished BIT = 0;
WHILE @Finished = 0 AND @Counter < @Limit
BEGIN
SET @Counter += 1;
-- 実際には、処理の進行に応じて完了判定を更新する。
IF @Counter = 3
SET @Finished = 1;
END;
IF @Finished = 0
THROW 50002, N'Iteration limit exceeded.', 1;
SELECT @Counter AS IterationCount;MySQL・PostgreSQLでは構文と仕組みを分ける
MySQLのストアドプログラム
SQL ServerのPRINT、TRY...CATCH、THROW、XACT_STATE()は、そのままMySQLへ移植できません。MySQL 8.4では、DECLARE ... HANDLERで例外処理を定義し、GET DIAGNOSTICSで診断情報を取得します。ハンドラー内で元の条件を再送出する構文はRESIGNALです。ハンドラーを置いたこと自体が、必要なROLLBACKや再送出を自動で行うわけではありません。
MySQL 8.4の公式資料には、標準のストアドルーチン用デバッグ機能はないと記載されています。外部製品のデバッガーとサーバー標準機能を混同しないでください。また、途中確認用のSELECTは結果セットを増やし、アプリケーション側の受け取り方に影響することがあります。根拠:DECLARE … HANDLER、GET DIAGNOSTICS、RESIGNAL、ストアドプログラムの制約。
PostgreSQLのPL/pgSQL
PostgreSQLのPL/pgSQLでは、RAISE NOTICEで診断用メッセージを出し、BEGIN ... EXCEPTION ... ENDで例外を扱います。例外ハンドラー内のGET STACKED DIAGNOSTICSで詳細を取得でき、引数なしのRAISE;で元の例外を再送出できます。SQL Serverの構文とは別です。NOTICEがクライアントやログへ出るかは、メッセージレベルの設定にも依存します。
EXCEPTIONで捕捉した場合、そのブロック内で行った永続的なデータ変更は取り消されますが、ローカル変数の値はエラー時の状態を保持します。この動作をSQL Serverのトランザクション制御と同一視しないでください。メッセージにも秘密情報を含めず、既存のログ設定を確認してから調査します。根拠:PL/pgSQLのエラーとメッセージ、例外処理と診断情報。
修正後に確認するテスト
- 正常系:基本例に1とsampleを渡すと、期待する1行だけが返る。
- 0件:該当しない条件では空の結果になり、件数の表示も0になる。
- 入力エラー:必須パラメーターにNULLを渡すと、エラー50001が呼び出し元まで届く。
- トランザクション:検証用の主キー違反で更新が取り消され、終了後のトランザクション状態が0になる。
- 情報保護:通常実行で不要なデバッグ出力がなく、ログや診断結果に入力値・秘密情報が漏れていない。
- 回帰確認:結果セットの数や列、出力パラメーター、エラーの伝わり方が、実際のアプリケーションとの契約を壊していない。
サンプルは検証環境で試すための例です。本記事の掲載だけで、読者のデータベースや利用ツールでの動作が確認されたことにはなりません。実際の環境・バージョン・呼び出し元に合わせ、正常系と異常系をセットで確認してください。
まとめ
ストアドプロシージャのデバッグは、いきなり本番コードへログを追加することではありません。最初にDB製品とツールの対応範囲を確認し、隔離した環境で再現条件をそろえます。SQL Serverでは、SSMSのクエリ・実行計画・Extended Eventsによる調査を基本に、必要な場合だけ対応するVisual Studio/SSDTなどでステップ実行を検討します。
エラー処理では、エラーの状態番号とトランザクションの状態を区別し、ログを残すだけで失敗を隠さないようにします。最小権限、秘密情報を含めないログ、明確なトランザクションの担当、修正後の回帰テストを組み合わせることが、安全で再現性のある調査につながります。


コメント