I言語自習書_04:トランザクション処理

●RDBMSは複数個所から同時に更新しても矛盾が起きないようにトランザクション処理で対応しますが、結構難しいのでここで自習します。

  1. デスクトップ上に作られているシステム名称のアイコンから起動します。


  2. 先頭メニューで"01"を入力して「システム 支援 メニュー」に行きます。


  3. 次のメニューで"02"を入力して「システム 開発 メニュー」に行きます。


  4. "21"を入れて「テスト 全 プログラム 更新」に行きます。


  5. 2行目に使用者とパスワードを入力します。

  6. ☆使用者に"ZZZZZZZY"とインストール時点で変更したパスワードを入れます。

  7. メニューでZZZZ303032にトランザクション処理を作成します


  8. トランザクション無しのプログラムを作ります

  9. 100 PROGRAM=NOT
    テーブルを単に更新や検索をするプログラムでは無いのPROGRAM=NOTとします。
    200 BEGIN
    BEGIN以下に起動直後に実行するプログラムを書きます。実行命令は先頭に=を書く文法になっています。
    300 =IF{#ARG=2}SET{NO=2}JUMP{P1};
    2つ目のジョブをARG=2のパラメータ付きで起動するので、1個目か2個目かの判定を#ARGでしています。
    2つ目のジョブはNOに2を設定しP1にジャンプします。
    400 =SET{NO=1};
    1つ目はNOを1に設定します。
    500 =DROP_TABLE{?_WW?_T};
    念のため作業テーブルを消します。(DROP_TABLEはテーブルが無い場合はエラーとしないで次に行きます)
    作業テーブルは本来は?_WORK_TABLE?に英字1文字を追加した物を使いますが、ジョブ毎に内容が異なり、今回は2個のジョブを使う処理の為使えません。なので、?_WW?のシステム変数を使っていますがジョブが異なっても同じ内容となてしまうので、通常は使いません。
    600 =CREATE_TABLE{?_WW?_T,K,C,1}{D,D,5,0};
    作業テーブルを作ります。
    ★注意:CREATE_TABLEは本番テーブルを使って予約語判定のをする為接続ユーザーが切り替えられるのでトランザザクション処理内では使えないので注意して下さい;
    K,C,1で列名kでデータ型がCHAR(1)の主キー(Primary key)を作ります。
    D,D,5,0で列名Dでデータ型がDECIMAL(5,0)の列を作ります。
    700 =SQL{INSERT INTO ?_WW?_T(K,D) VALUES('K',50)};
    Kの値が'K'の行を作成します。
    800 =P1:
    JUMPの飛び先の名前です。
    900 =SQL_SET{D1}{SELECT D FROM ?_WW?_T WHERE K='K'};
    Kの値が'K'の行のDの値をD1に受け取ります。
    1000 =IF{NO=1}EXE_I{?_JOB?,ARG=2};
    1個目のプログラムの場合同じジョブをARG=2のパラメータ付きで起動します。
    1100 =IF{D1<40}SET{NG='NG'}EXIT{};
    Dが40未満の場合NGに'NG'を設定し引けなかったとしてBEGIN処理を迂回し次に行きます。
    1200 =SLEEP{5};
    2番目のジョブ起動の同時処理を保証する為5秒待ちます。
    1300 =SQL{UPDATE ?_WW?_T SET D=D-40 WHERE K='K'};
    Dのデータから40を引いた物で修正します。
    1400 =SQL_SET{D2}{SELECT D FROM ?_WW?_T WHERE K='K'};
    更新の結果をD2に受け取ります。
    1500 =IF{NO=2}DROP_TABLE{?_WW?_T};
    2つ目のジョブが終了したので作業テーブルを消します。
    1600 DATA=NO{1}
    PROGRAM=NOTの場合DATA=に入力項目が無いとプログラムが終了してしますので,{O}の無い入力項目としています。
    ★I言語では入力状態に成ると、データベースの接続を切ってしまうので、トランザクションを入力を挟んで継続する事は出来ません。
    1700 DATA={O}D1{5,,-}
    {5,,-}で負の値も表示する有効桁数5桁の数値項目として表示します。
    1800 DATA={O}D2{5,,-}
    1900 DATA={O,,,R}NG{2}
    {O,,,R}のRは枠の背景色を赤(Red)にします。
    操作上でTESTの意味のTと[Enter]でテスト処理が起動します。


    結果は当然ですが40が2回引かれt-30と成っています。
  10. トランザクション有りのプログラムを作ります


  11. 結果は今回も40が2回引かれ-30と成っています。
    ★トランザクションを開始しても、COMMITまでロックが掛かるのは更新系のSQL文の排他ロックの場合のみです、通常のSELECTは共有ロックと呼ばれ、他のSELECTも同時に動き、更にSELECTが終了すれば共有ロックが解除されてしまいます、なのでトランザクション無しと同じ結果と成っています」。
    ★SELECTに対しCOMMITまでロックが掛けるには排他ロック(実際はアップデートロック)付きのSQL文に改める必要が有ります。
  12. SELECTに排他ロックを付加したプログラムを作ります


  13. 900 SQL_SET{D1}{SELECT D FROM ?_WW?_T ?_LOCK1? WHERE K='K' ?_LOCK2?}
    ?_LOCK1?と?_LOCK2?はRDBMS毎にロックの書き方と場所が異なるのでこのようにシステム変数で与えています。

    これで2個目の処理ではUPDATEを迂回し2回引く事が無く、トランザクション処理による2重の引き算を防止出来ています。
  14. なおI言語ではSQLでの一件処理ではPROGRAM命令と言う便利な命令が有るのでそれに変更してみます


  15. トランザクションにはデッドロックと呼ばれる互いにロック解除待ちで永久に待たされる困った状態が起きるのでここで再現してみます


  16. 410 REPEAT=2
    REPEAT=2は次のREPEAT=まで2回繰り返す命令です。
    500 =DROP_TABLE{?_WW?_##T};
    REPEAT=内では##はプログラムの読み込み時最初に繰り返しの回数で置き換えられます。
    1000 1+=IF{NO1=1}EXE_I{?_JOB?,ARG=2};
    先頭の1+は繰り返しの1回目のみ実行される意味と成ります。

  17. 片方の処理がデッドロックで異常終了してしまいました、これは異常発生時の対応をプログラムでなにも対策してない為です、一応エラーの対応をしてみます


  18. 同じく異常終了をしてしまいました、これは、I言語側の問題で、I言語では入力後にエラーが起きるとエラーで再入力と成りますが、BEGINで処理している為再入力出来ないので、異常終了してしまっています。


  19. 910 =IF_ERROR_PROGRAM{0,?_WW?_?NO##?T,D1##.D}{WC.K='K'}{}ERROR{};
    命令の前にIF_ERROR_を追加するとエラーの時異常終了しないで、次の処理を実行します。
    ERROR{}にようにエラーの内容を省略するとI言語が作ったエラーの内容に成ります。
  20. 処理を入力直後に移動に改めました。


  21. 210 CONTROL=BEGIN_ENTER
    最初の入力で停止しては困るので、開始直後で[Enter]が入力された事とします。
    805 DATA=DUMMY{1}
    ダミーの入力項目を確保しました。これ以降は最初の入力後に実行されるプログラムと成ります。
  22. これで通常の異常終了しないで通常のエラーとして再入力出来るようになています。


  23. ★このように、トランザクション処理はRDBMSで同時更新しても矛盾が起きないようにするための重要な機能ですが、デッドロックが起きる原因と成るので、ロックするテーブルの順番をなるべく同じにするような対応が必要です。
    ★PRGRAM=INPUTの標準的なテーブルの更新プログラムでは更新直前にトランザクションを開始し、再度テーブルの内容を読み込んで画面の情報と一致していない場合はエラーと成るように作られています。
    ★I言語はICOBOLやIRPROGRAMの時代を経て長い時間を掛けて作り込んできた物なので、全ての事を網羅した自習書は作れないので、とりえず、次は請求書発行システムを作る自習書で終わりとします。
    All Rights Reserved, Copyright (C) 2016-2016 Nobumichi Harasawa.