2025年12月09日

[JPOUG Advent Calendar] Oracle on Hyper-V 2025

charade_oo4oと申します。
この記事は、 JPOUG Advent Calendar 2025 9日目の記事です。
8日目は がきさん さんの記事『 【続・Blockchain Table入門】Oracle AI DB 26aiのV2が解く「運用のジレンマ」という鎖 』でした。

 <2025年>
Oracle Database 23aiはOracle AI Database 26aiに置き換わりました。
来年1月のオンプレミス版のリリースも期待しています。

Microsoftでは、Windows 10のサポートが終了しました。

F1では鈴鹿からOracle Red Bull Racingに角田選手が搭乗しました。
大きな期待と悔しさを味わいました。
2025_rb21sc

 <新PC>
今年仕事用のPCを新調しました。
CPUはIntel Core Ultra 7 165H、Pコアx6、Eコアx10、スレッド総数x22でした。
いくつか注意点がありました。

・電源オプション=高パフォーマンス
 習慣で、コントロールパネルの電源オプションを、バランスから高パフォーマンスへ変更しました。
 バランスでは、普段は低クロック、必要となった時に高クロックとなるので、過去に導入したWindows Serverで性能が出なかった事がありました。
 高パフォーマンスでは、普段から高クロックで動作します。

・USB Type-C アダプタ
 最近のPCは機器増設も電源もUSB Type-Cを使用します。
 標準のアダプタが巨大だった為、市販品のアダプタ(65W/20W 2ポート)へ変更しました。
 65Wでは3-4GHzのクロックで稼働しましたが、20Wでは2-3GHzに落ちました。
 もう一つ別の効果もありました。
 65Wでは何も使っていない時に冷却ファンが唸る時がありました。
 20Wでは余力が無いのか冷却ファンは静かなままでした。
 そこで、パワーが必要な場合は65W、冷却ファンが五月蠅い場合は20Wに切り替える運用になりました。

・Excel プロセッサ数指定
 仕事柄、Active Session HistoryやExaWatcherの大量データをExcelで分析する事があります。
 新PCでは、Excelの再計算が遅い様な気がしました。
 Eコアが足を引っ張っているのではないかと思い、Excelの設定で使用するプロセッサ数をPコアのスレッド数=12へ抑えました。
  ファイル=>オプション=>詳細設定=>数式=>マルチスレッド計算を行う
  計算スレッドの数
  ○このコンピューターのすべてのプロセッサを使用する(P): 22
  ●使用するプロセッサの数を指定する(M): 12
 もっさり感は解消しました。

 <フラグ管理>
26aiでは、ロックフリー予約という同時実行性を向上させる機能が追加されました。
今回は、悲観ロックや楽観ロックを使用する昔ながらの排他制御について整理したいと思います。
排他制御する事で、自分の更新を他者から守り、逆に他者の更新を無視して上書きしない処理を実現します。
2021年のネタで使用したテーブルを参考に、入力元テーブルから出力先テーブルへの連携処理(特に入力元テーブルへのフラグ更新)について検証します。
検証ではHyper-V上の仮想マシンを活用しました。

TABLE「INPTBL,OUTTBL」定義
項目名 データ型 PK I1 備考
PKCD VARCHAR2(3) 1   XXX
PKDT VARCHAR2(8) 2   YYYYMMDD
PKNO NUMBER(8) 3   PKCD,PKDT毎 1〜
FLG VARCHAR2(1)   1 1:未処理 (2:処理中) NULL:処理済
INFO VARCHAR2(100)     100文字分

INPTBL.FLG=1:未処理のレコードを読み込み、OUTTBLへレコードを書き出し、INPTBL.FLG=NULL:処理済へ更新します。
(フラグ管理ではなく、P-KEYの存在チェックで確認する方法もありますが、データ量が多いと大量のリソースを消費します。)

下図では、各テーブルの縦軸に各レコードを、横軸に時間経過(左→右)を、各レコード・時間経過毎のフラグ内容やロック状態を色別に表現します。

2025_loop

1.SELECT FOR UPDATE
INPTBL.FLG=1:未処理のレコードにFOR UPDATEで悲観ロックをかけて読み込みます。
悲観ロックで他の処理からの同時更新を防いでいるので必須ではありませんが、ORDER BYを付けて (※)更新のたすき掛けによるデッドロック を予防します。

2.INSERT
OUTTBLへ一行毎に出力します。

3.UPDATE
INPTBL.FLG=NULL:処理済へ一行毎に更新します。

PL/SQLでのコーディング例です。
BEGIN
  FOR IREC IN (
    --1.SELECT FOR UPDATE
    SELECT
     I.PKCD
    ,I.PKDT
    ,I.PKNO
    ,I.INFO
    ,I.ROWID
      FROM INPTBL I
     WHERE I.FLG = '1'
     FOR UPDATE
     ORDER BY
     I.PKCD
    ,I.PKDT
    ,I.PKNO
  ) LOOP
    --2.INSERT
    INSERT INTO OUTTBL O (
     O.PKCD
    ,O.PKDT
    ,O.PKNO
    ,O.FLG
    ,O.INFO
    ) VALUES (
     IREC.PKCD
    ,IREC.PKDT
    ,IREC.PKNO
    ,'1'
    ,IREC.INFO
    )
    ;
    --3.UPDATE
    UPDATE INPTBL I SET
     I.FLG = NULL
     WHERE I.ROWID = IREC.ROWID
    ;
  END LOOP;
  COMMIT;
END;
/
いわゆるCOBOL的なグルグル系のコードです。
一行毎に処理する為、処理が遅いという問題があります。

(※)更新のたすき掛けによるデッドロック
複数の処理が異なる順番で更新すると、デッドロックが発生する可能性があります。
昇順の処理A、降順の処理Dが、それぞれ一行毎に更新するとします。

2025_deadlock

1.処理AがPKNO=1のレコードを更新します。
2.処理DがPKNO=2のレコードを更新します。
3.処理AがPKNO=2のレコードを更新しますが、既に2.で処理Dが更新済です。
4.処理DがPKNO=1のレコードを更新しますが、既に1.で処理Aが更新済です。

SQL*Plusで処理A、処理Dを実行した結果を表にまとめました。

No 処理A 処理D 状況
1.
SQL> UPDATE INPTBL I SET
  2   I.FLG = NULL
  3   WHERE I.PKCD = '100'
  4     AND I.PKDT = '20251201'
  5     AND I.PKNO = 1
  6     AND I.FLG = '1'
  7  ;

1 row updated.

SQL>

処理AがPKNO=1のレコードを更新します。
2.

SQL> UPDATE INPTBL I SET
  2   I.FLG = NULL
  3   WHERE I.PKCD = '100'
  4     AND I.PKDT = '20251201'
  5     AND I.PKNO = 2
  6     AND I.FLG = '1'
  7  ;

1 row updated.

SQL>
処理DがPKNO=2のレコードを更新します。
3.
SQL> UPDATE INPTBL I SET
  2   I.FLG = NULL
  3   WHERE I.PKCD = '100'
  4     AND I.PKDT = '20251201'
  5     AND I.PKNO = 2
  6     AND I.FLG = '1'
  7  ;


処理AがPKNO=2のレコードを更新しますが、既に2.で処理Dがロック済の為、ブロッキングされます。
4.

SQL> UPDATE INPTBL I SET
  2   I.FLG = NULL
  3   WHERE I.PKCD = '100'
  4     AND I.PKDT = '20251201'
  5     AND I.PKNO = 1
  6     AND I.FLG = '1'
  7  ;

処理DがPKNO=1のレコードを更新しますが、既に1.で処理Aがロック済の為、ブロッキングされます。

   AND I.PKNO = 2
       *
ERROR at line 5:
ORA-00060: deadlock detected while waiting for resource


SQL>

暫くすると、処理AでORA-00060のデッドロックが検出されます。

SQL> ROLLBACK;

Rollback complete.

SQL>




1 row updated.

SQL>
エラー発生した処理AをROLLBACKすると、処理DがPKNO=1のレコードを更新します。


SQL> COMMIT;

Commit complete.

SQL>
処理DをCOMMITします。

ORA-00060のデッドロックが確認出来ました。

対策として、各処理での更新順番を統一すると、ORA-00060のデッドロックは回避可能です。

2025_blocking

1.処理AがPKNO=1のレコードを更新します。
2.処理A'がPKNO=1のレコードを更新しますが、既に1.で処理Aが更新済です。
3.処理AがPKNO=2のレコードを更新します。
4.処理A'がPKNO=2のレコードを更新しますが、既に3.で処理Aが更新済です。

SQL*Plusで処理A、処理A'を実行した結果を表にまとめました。

No 処理A 処理A' 状況
1.
SQL> UPDATE INPTBL I SET
  2   I.FLG = NULL
  3   WHERE I.PKCD = '100'
  4     AND I.PKDT = '20251201'
  5     AND I.PKNO = 1
  6     AND I.FLG = '1'
  7  ;

1 row updated.

SQL>

処理AがPKNO=1のレコードを更新します。
2.

SQL> UPDATE INPTBL I SET
  2   I.FLG = NULL
  3   WHERE I.PKCD = '100'
  4     AND I.PKDT = '20251201'
  5     AND I.PKNO = 1
  6     AND I.FLG = '1'
  7  ;

処理A'がPKNO=1のレコードを更新しますが、既に1.で処理Aがロック済の為、ブロッキングされます。
3.
SQL> UPDATE INPTBL I SET
  2   I.FLG = NULL
  3   WHERE I.PKCD = '100'
  4     AND I.PKDT = '20251201'
  5     AND I.PKNO = 2
  6     AND I.FLG = '1'
  7  ;

1 row updated.

SQL>

処理AがPKNO=2のレコードを更新します。

SQL> COMMIT;

Commit complete.

SQL>




0 rows updated.

SQL>
処理AをCOMMITすると、既に1.で処理Aがフラグ更新済の為、処理A'の更新は空振りします。
4.

SQL> UPDATE INPTBL I SET
  2   I.FLG = NULL
  3   WHERE I.PKCD = '100'
  4     AND I.PKDT = '20251201'
  5     AND I.PKNO = 2
  6     AND I.FLG = '1'
  7  ;

0 rows updated.

SQL>
処理A'がPKNO=2のレコードを更新しますが、既に3.で処理Aがフラグ更新済の為、処理A'の更新は空振りします。
2.が空振りしたので、4.自体を実行しない方法も考えられます。


SQL> ROLLBACK;

Rollback complete.

SQL>
更新が空振りした処理A'をROLLBACKします。

先に開始した処理Aの更新完了後、後から開始した処理A'の更新は空振りしました。

 <間違った一括更新>
処理速度向上の為、一行毎の処理ではなく、一括更新するコードへ変更します。
まず、あえて排他制御出来ていないコードを書いてみました。

2025_ng

1.SELECT FOR UPDATE
INPTBL.FLG=1:未処理のレコードにFOR UPDATEで悲観ロックをかけます。
INSERT SELECT文にはFOR UPDATEを指定出来ない事を今更ながら知りました。

2.INSERT SELECT
INPTBL.FLG=1:未処理のレコードをOUTTBLへ一括出力します。

3.UPDATE
INPTBL.FLG=1:未処理をNULL:処理済へ一括更新します。
BEGIN
  --1.SELECT FOR UPDATE
  EXECUTE IMMEDIATE '
    SELECT
     I.ROWID
      FROM INPTBL I
     WHERE I.FLG = ''1''
     FOR UPDATE
  ';
  --2.INSERT SELECT
  INSERT INTO OUTTBL O (
   O.PKCD
  ,O.PKDT
  ,O.PKNO
  ,O.FLG
  ,O.INFO
  )
  SELECT
   I.PKCD
  ,I.PKDT
  ,I.PKNO
  ,'1' AS FLG
  ,I.INFO
    FROM INPTBL I
   WHERE I.FLG = '1'
  ;
  --3.UPDATE
  UPDATE INPTBL I SET
   I.FLG = NULL
   WHERE I.FLG = '1'
  ;
  COMMIT;
END;
/
何故、排他制御出来ていないのか。
2.のINSERT SELECT開始後、3.のフラグ更新前のタイミングでINPTBLへ未処理レコードが追加されると、まだ連携されていないレコードが3.のフラグ更新で処理済になってしまいます。

2025_ng_add

冗談ではなく、本当にこんな感じの処理が動いているのを見た事があります。
「この処理、排他制御出来てませんよ。」
「大丈夫ですよ。入力処理が終わってから、同じ人が連携処理しますから。」
確かに、使い方次第では問題は起きないのかも知れません。

 <見直した一括更新>
排他制御を考慮した一括更新へ見直しました。

2025_batch

1.UPDATE
楽観ロックでINPTBL.FLG=1:未処理を2:処理中へ一括更新します。

2.INSERT SELECT
INPTBL.FLG=2:処理中のレコードをOUTTBLへ一括出力します。

3.UPDATE
INPTBL.FLG=2:処理中をNULL:処理済へ一括更新します。
BEGIN
  --1.UPDATE
  UPDATE INPTBL I SET
   I.FLG = '2'
   WHERE I.FLG = '1'
  ;
  --2.INSERT SELECT
  INSERT INTO OUTTBL O (
   O.PKCD
  ,O.PKDT
  ,O.PKNO
  ,O.FLG
  ,O.INFO
  )
  SELECT
   I.PKCD
  ,I.PKDT
  ,I.PKNO
  ,'1' AS FLG
  ,I.INFO
    FROM INPTBL I
   WHERE I.FLG = '2'
  ;
  --3.UPDATE
  UPDATE INPTBL I SET
   I.FLG = NULL
   WHERE I.FLG = '2'
  ;
  COMMIT;
END;
/
2.のINSERT SELECT開始後、3.のフラグ更新前のタイミングでINPTBLへ未処理レコードが追加されると、3.のフラグ更新後でも未処理レコードのまま残ります。

2025_batch_add

楽観ロックでも排他制御は実現可能です。

 <まとめ>
・更新順番統一でデッドロック予防。
・排他制御が実現出来ているか要注意。

Oracle Databaseはロックエスカレーションせずに行レベルロックが可能です。
しかし、処理を誤ると排他制御が出来ていないという事もあり得ます。
恥ずかしながら、今回の簡単な検証中にも問題が発生しました。
・INSERT SELECT文にはFOR UPDATEを指定出来ませんでした。
・処理A、処理A'ではWHERE句条件にFLG='1'を追加しないと、後から開始した処理A'が上書きしてしまいました。

今回のネタがDB処理の再確認に役立てば幸いです。
明日は Hideto Sawaki さんです。

 <閑話休題 AIの脅威>
ここ数年はAIの進化が凄まじく、AIに取って代わられ仕事が無くなるのではないかと思う事もありました。
私なりに考えてみました。

・他業種の事例
 AIをロボットに置き換えると、機械化が進んだ自動車産業が参考になるかも知れません。
 自動車工場では多数のロボットが導入されていますが、同時に多数の人間も働いています。
 溶接や塗装はロボットが行いほぼ無人、内装等の細かい組立は人間中心で住み分けしています。
 量産車以外の分野もあります。
 ワンオフカー等では、量産車では人間が携わらなくなった溶接や塗装においても、今でも職人の技術が活きています。

・歴史上の事例
 歴史上、時代が大きく変わった節目があります。
 明治維新では武士という職業が無くなりました。
 一人の事例として、新撰組の斎藤一は、その後警察官という道を選びました。

技術を極めれば、道は繋がると信じています。
posted by charade at 00:00| Comment(0) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント: [必須入力]

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。

この記事へのトラックバック
×

この広告は90日以上新しい記事の投稿がないブログに表示されております。