こんにちは、現場のエンジニア諸君。
これまでに数えきれないほどの大型バッチ改修や、オープン系へのリプレース、そして何より「絶対に止めてはならない」基幹システムの保守をくぐり抜けてきた私だ。
さて、今日はPL/Iの基本構文そのものというよりは、CICSとDB2が手を取り合う現場において、最も事故りやすい「トランザクション制御と論理作業単位(UOW: Unit of Work)の同期」について、骨の髄まで叩き込んでやろうと思う。
PL/Iの変数の命名規則や予約語の話も重要だがね、実際の現場では「なぜここでコミットされないのか」「なぜデータがデッドロックするのか」といった、サブシステム間の連携部分で血を流すことが多い。特にCICS環境下でのDB2アクセスにおいて、トランザクションの境界をどう取るかは、システムの生死を分ける生命線だ。
心して聞いてほしい。
—
1. 現場の鉄則:CICSとDB2の同期制御のメカニズム
CICS(Customer Information Control System)環境で動くPL/IオンラインプログラムからDB2を叩くとき、私たちは常に「2相コミット(Two-Phase Commit)」の世界に足を踏み入れていることを意識しなければならない。
ここでPL/Iの特性を思い出してほしい。PL/Iには、COBOLのようなどこか野暮ったい制約が少なく、非常に洗練された言語仕様を持っているがゆえに、CICSコマンド(`EXEC CICS`)とDB2の埋め込みSQL(`EXEC SQL`)の境界を曖昧にしがちだ。
論理作業単位(UOW)の罠
CICS環境下では、トランザクションの同期点(コミット)は、原則としてCICSの管理下に置かれなければならない。
具体的にどういうことか? DB2単体であれば `EXEC SQL COMMIT;` を書きたい衝動に駆られるだろう。しかし、CICS配下でこれをやると、コンパイルエラーになるか、最悪の場合、ランタイムで致命的な異常終了(AICAやABend)を引き起こす。
- CICS環境での正解: トランザクションの確定(コミット)は `EXEC CICS SYNCPOINT` を使用する。
- 同期の連動: `EXEC CICS SYNCPOINT` が発行されると、CICSはリソースマネージャであるDB2に対して「ここまでの作業を確定しろ」というシグナルを自動的に送り、DB2側のトランザクションも同時にCOMMITされる。
逆に、ロールバックが必要な場合も同様だ。DB2の `ROLLBACK` ではなく、`EXEC CICS SYNCPOINT ROLLBACK` を使わなければ、CICSが管理するVSAMなどのリソースと、DB2の整合性が完全に破壊される。
—
2. 実践コード:CICS/DB2環境でのトランザクション制御とONユニット
百聞は一見にしかずだ。実際のオンライン・プログラムを想定した、実務でそのまま使えるPL/Iのコード例を示そう。
このコードでは、CICSの画面から受け取ったデータをDB2に登録しつつ、途中でエラーが発生した場合には適切にロールバックを行い、さらにPL/Iの強みである `ON` ユニットを使って例外を捕捉する構成にしている。
1
/ ———————————————— /
/ プログラム名: CUSTUPD1 /
/ 概要: CICS/DB2環境におけるUOW制御とエラー処理 /
/ ———————————————— /
CUSTUPD1: PROC OPTIONS(MAIN) REENTRANT;
/ — 埋め込みSQL通信エリア(SQLCA)の宣言 — /
EXEC SQL INCLUDE SQLCA;
/ — ワーキング・ストレージ変数の定義 — /
DCL WK_CUST_ID CHAR(8);
DCL WK_STATUS CHAR(1);
DCL WK_ERR_MSG CHAR(78);
DCL RET_CODE FIXED BIN(31) INIT(0);
/ — CICS画面入出力用の領域(BMSマップなど想定) — /
/ 略 /
/ — 1. データベース接続確認および初期処理 — /
/ CICS環境では通常DB2のCONNECTは暗黙的だが、必要に応じて記述 /
/ — 2. ONユニットによる異常系トラップの仕掛け — /
/ PL/Iの真骨頂であるONユニットで予期せぬ条件を捕捉する /
ON UNDEFINEDFILE(SYS011)
BEGIN;
WK_ERR_MSG = ‘ファイル定義エラーが発生しました。’;
GO TO ERROR_RTN;
END;
/ — 3. メイン処理ループの開始(UOWの単位) — /
/ トランザクションの開始点 /
/ DB2へのインサート処理 /
EXEC SQL
INSERT INTO CUSTOMER_TBL (CUST_ID, STATUS, UPDATE_TIMESTAMP)
VALUES (:WK_CUST_ID, :WK_STATUS, CURRENT TIMESTAMP);
IF SQLCODE ^= 0 THEN DO;
/ SQLエラー発生時の処理 /
CALL HANDLE_DB_ERROR(‘INSERT FAILED’);
GO TO ROLLBACK_RTN;
END;
/ — 4. 正常同期点(SYNCPOINT)の取得 — /
/ ここでCICSとDB2の変更内容が同時にコミットされる /
EXEC CICS SYNCPOINT;
IF EIBRESP ^= DFHRESP(NORMAL) THEN DO;
/ CICS同期点処理で異常が発生した場合 /
GO TO ROLLBACK_RTN;
END;
RETURN;
/ — 異常処理・ロールバック・ルーチン — /
ROLLBLCK_RTN:
/ DB2およびCICSリソースのロールバックを指示 /
/ 注意: DB2のROLLBACKではなくCICSのSYNCPOINT ROLLBACKを使うこと! /
EXEC CICS SYNCPOINT ROLLBACK;
/ 画面へのエラーメッセージ送信処理など /
/ EXEC CICS SEND … /
RETURN;
/ — 内部サブルーチン: DBエラー解析 — /
HANDLE_DB_ERROR: PROC(P_ERR_TYPE);
DCL P_ERR_TYPE CHAR() PARM;
/ BUILTIN関数などを活用したログ出力処理 /
DISPLAY(‘
‘ || P_ERR_TYPE || ‘ SQLCODE = ‘ || TRIM(SQLCODE));
END HANDLE_DB_ERROR;
END CUSTUPD1;
—
3. ベテランからのデバッグのコツと注意点
上記のコードを見て、「おや?」と思った鋭い後輩もいるかもしれない。実務でこの構成を扱う際、以下の罠にハマる人間が後を絶たない。
① 混用禁秘:SQLのCOMMIT/ROLLBACKはオンラインでは厳禁
しつこいようだが、バッチプログラム(非CICS環境)の感覚で `EXEC SQL COMMIT WORK;` をCICSオンラインプログラム内に書くプログラマがたまにいる。
これをやると、CICS側は「何勝手にコミットしてやがる!」とばかりに `AICA` や `APCT` などのアブエンドを吐いてタスクを強制終了させる。DB2のカーソルも無効化され、整合性はズタズタになる。オンライン中は必ず `EXEC CICS SYNCPOINT`、これだけは絶対に忘れないでほしい。
② ONユニットとSQLCODEの二重チェック
PL/Iには強力な `ON` 条件処理(`ON ERROR`, `ON CONDITION` など)があるが、DB2の埋め込みSQLに関しては、PL/Iのランタイムエラーとしては検知されず、単に `SQLCODE` にマイナス値が返るだけだ。
したがって、上記のコード例のように、SQL発行直後の `SQLCODE` のチェックと、CICSのレスポンスコード(`EIBRESP`)のチェック、そしてPL/I独自の例外(`ON` ユニット)の3つを、適切なレイヤーで切り分けてハンドリングすることが、堅牢なメインフレームシステムを作る唯一の道となる。
—
おわりに
レガシーシステムの寿命は、私たちが思っているよりも長い。そして、私たちが書く1行のコードの裏側で、何万件、何億件という企業の資産が動いている。
PL/Iという言語は、構文規則が柔軟であるゆえに、書き手の技量がコードの品質にダイレクトに反映される。
「なぜこの命令を使うのか」「この制御がハードウェアやサブシステム(CICS/DB2)にどう伝わるのか」を常に意識し、ただ動くだけではなく、「絶対に壊れない、保守しやすいコード」をこれからも追求してほしい。
期待しているぞ、次の基幹システムを背負うのは君たちだ。
