4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ADBでワークロードを計測するためのテスト用トランザクションを発生させるPLSQLサンプル

4
Last updated at Posted at 2026-01-21

目次

1.はじめに
2.本文
3.運用上の注意

1. はじめに

ADBでワークロードを計測するためのテスト用トランザクションを発生させるPLSQLサンプルになります。

以下は、シンプルな「口座振替」型の更新・挿入・参照を混在させたワークロードです。
複数ジョブを並列実行して一定時間負荷を与える想定です。
必ず検証用環境で実行してください。

2. 本文

2.1【作業手順】

(1) スキーマ作成(テーブル/インデックス)
(2) 初期データ投入(l_rowsで件数を調整)
(3) トランザクションを発生させるプロシージャ作成(run_mixed_workload)
(4) 並列ジョブ実行で負荷をかける(DBMS_SCHEDULERでrun_mixed_workloadを複数同時起動)

※単発実行で試す場合は

BEGIN
  run_mixed_workload(300);
END;
/

を実行してください。

※ADBの機能である自動ワークロードリプレイは仕様でDBMS_SCHEDULERで実行されるワークロードはキャプチャしないため、別途自動ワークロードを使ってワークロードのキャプチャテストをしたい場合には単一セッション実行か、他の方法で並列実行してください。

(5) プロシージャの実行状態はビューやADB付属のPerformance Hubなどでモニタリングしてください。
(6) 片付け(不要になったテーブルのDROPなど)

※補足

負荷を上げたい場合は、

(4)の並行数(FOR i IN 1..N)や
run_mixed_workloadの実行時間、COMMIT間隔/スリープを調整してください。

早期停止したい場合は

DBMS_SCHEDULER.drop_job('WL_JOB_X', force => TRUE); でジョブを落とせます。

実行は必ず非本番環境で。ストレージ/REDO/UNDOが増えるため注意してください。

2.2【作業手順詳細】

(1) スキーマ作成(テーブル/インデックス)
-- テスト用スキーマのオブジェクトを作成します
-- 口座テーブル(accounts) + 取引履歴テーブル(acct_txn) + インデックス(ix_acct_balance)

CREATE TABLE accounts (
    acct_id     NUMBER          PRIMARY KEY,
    cust_name   VARCHAR2(100),
    balance     NUMBER,
    updated_at  TIMESTAMP
);
CREATE TABLE acct_txn (
    txn_id      NUMBER
                    GENERATED BY DEFAULT AS IDENTITY
                    PRIMARY KEY,
    from_acct   NUMBER,
    to_acct     NUMBER,
    amount      NUMBER,
    created_at  TIMESTAMP,
    note        VARCHAR2(200)
);
CREATE INDEX ix_acct_balance
    ON accounts (balance);

(2) 初期データ投入(l_rowsで件数を調整)(例: 100,000口座)

DECLARE
    l_rows NUMBER := 100000;
BEGIN
    INSERT /*+ APPEND */
        INTO accounts (
            acct_id,
            cust_name,
            balance,
            updated_at
        )
    SELECT
        LEVEL                              AS acct_id,
        'CUST_' || TO_CHAR(LEVEL)          AS cust_name,
        TRUNC(DBMS_RANDOM.VALUE(1000, 100000))  AS balance,     -- 初期残高
        SYSTIMESTAMP                       AS updated_at
    FROM
        dual
    CONNECT BY
        LEVEL <= l_rows;

    COMMIT;
END;
/

(3) トランザクションを発生させるプロシージャ作成(run_mixed_workload)
-- 指定時間(秒)実行し、ランダムに送金、参照、更新、古い履歴の削除を行う

CREATE OR REPLACE PROCEDURE run_mixed_workload (
  p_seconds IN NUMBER DEFAULT 300
) IS
  l_end_time TIMESTAMP := SYSTIMESTAMP + NUMTODSINTERVAL(p_seconds, 'SECOND');
  l_from   NUMBER;
  l_to     NUMBER;
  l_amount NUMBER;
  l_choice NUMBER;
  l_cnt NUMBER := 0;
BEGIN
  DBMS_APPLICATION_INFO.set_module(
    module_name => 'TEST_WORKLOAD',
    action_name => 'RUN'
  );

  WHILE SYSTIMESTAMP < l_end_time LOOP
    l_choice := TRUNC(DBMS_RANDOM.value(1, 101)); -- 1..100

    IF l_choice <= 55 THEN
      ------------------------------------------------------------------
      -- 55%: 振替トランザクション(更新 + 履歴挿入)
      ------------------------------------------------------------------
      l_from := TRUNC(DBMS_RANDOM.value(1, 100001));
      l_to   := TRUNC(DBMS_RANDOM.value(1, 100001));

      IF l_from = l_to THEN
        l_to := CASE WHEN l_to < 100000 THEN l_to + 1 ELSE 1 END;
      END IF;

      l_amount := TRUNC(DBMS_RANDOM.value(10, 1000));

      -- 残高チェックしつつ行ロック(更新できたら SQL%ROWCOUNT=1)
      UPDATE accounts
         SET balance    = balance - l_amount,
             updated_at = SYSTIMESTAMP
       WHERE acct_id = l_from
         AND balance >= l_amount
      RETURNING acct_id INTO l_from;

      IF SQL%ROWCOUNT = 1 THEN
        UPDATE accounts
           SET balance    = balance + l_amount,
               updated_at = SYSTIMESTAMP
         WHERE acct_id = l_to;

        INSERT INTO acct_txn (
          from_acct, to_acct, amount, created_at, note
        ) VALUES (
          l_from, l_to, l_amount, SYSTIMESTAMP, 'transfer'
        );
      END IF;

    ELSIF l_choice <= 85 THEN
      ------------------------------------------------------------------
      -- 30%: 調査系 SELECT(インデックス活用やソートを誘発)
      ------------------------------------------------------------------
      DECLARE
        v_dummy NUMBER;
      BEGIN
        SELECT SUM(balance)
          INTO v_dummy
          FROM (
            SELECT balance
              FROM accounts
             WHERE balance BETWEEN TRUNC(DBMS_RANDOM.value(100, 50000))
                               AND TRUNC(DBMS_RANDOM.value(50001, 100000))
             ORDER BY balance DESC
          )
         WHERE ROWNUM <= 1000;

      EXCEPTION
        WHEN NO_DATA_FOUND THEN
          NULL;
      END;

    ELSIF l_choice <= 95 THEN
      ------------------------------------------------------------------
      -- 10%: ランダム更新(軽い更新)
      ------------------------------------------------------------------
      UPDATE accounts
         SET balance    = balance + TRUNC(DBMS_RANDOM.value(-50, 50)),
             updated_at = SYSTIMESTAMP
       WHERE acct_id = TRUNC(DBMS_RANDOM.value(1, 100001))
         AND ROWNUM = 1;

    ELSE
      ------------------------------------------------------------------
      -- 5%: 履歴のクリーンアップ(古い行を削除)
      ------------------------------------------------------------------
      DELETE FROM acct_txn
       WHERE created_at < SYSTIMESTAMP - INTERVAL '1' HOUR
         AND ROWNUM <= 1000;
    END IF;

    l_cnt := l_cnt + 1;

    IF MOD(l_cnt, 100) = 0 THEN
      COMMIT;

      -- 負荷が強すぎる場合はスリープで調整(例: 0.05秒)
      -- DBMS_LOCK.sleep(0.05);
    END IF;
  END LOOP;

  COMMIT;
END;
/

(4) 並列実行(DBMS_SCHEDULERで複数ジョブを同時起動)

-- 同時8本のジョブを300秒間走らせる
-- ジョブ作成(ジョブ実行)

BEGIN
  FOR i IN 1 .. 8 LOOP  -- 同時並行数(環境に合わせて調整)

    DBMS_SCHEDULER.create_job(
      job_name        => 'WL_JOB_' || i,
      job_type        => 'PLSQL_BLOCK',
      job_action      => 'BEGIN run_mixed_workload(300); END;',
      start_date      => SYSTIMESTAMP + INTERVAL '5' SECOND,

      -- スケジュール実行させたい場合に使用
      -- repeat_interval => 'FREQ=WEEKLY;
      --                     BYDAY=MON,TUE,WED,THU,FRI;
      --                     BYHOUR=7;
      --                     BYMINUTE=30;
      --                     BYSECOND=0',

      enabled         => TRUE,
      auto_drop       => TRUE
    );

  END LOOP;
END;
/

※単発実行させたい場合

BEGIN
  run_mixed_workload(300);
END;
/

※ジョブを早期停止したい場合

BEGIN
  FOR i IN 1..8 LOOP
    BEGIN
      DBMS_SCHEDULER.drop_job('WL_JOB_' || i, force => TRUE);
    EXCEPTION
      WHEN OTHERS THEN
        IF SQLCODE != -27475 THEN  -- job not found は無視
          RAISE;
        END IF;
    END;
  END LOOP;
END;
/

(5) 実行状況の確認
-- ジョブの状態(並列実行の場合)

SELECT
    job_name,
    state,
    last_start_date,
    run_count,
    next_run_date
FROM
    user_scheduler_jobs
WHERE
    job_name LIKE 'WL_JOB_%';

-- ジョブの状態(単発実行の場合)

SELECT
    sid,
    key,
    status,
    username,
    sql_id,
    sql_text,
    last_refresh_time
FROM
    v$sql_monitor
WHERE
    username = USER
ORDER BY
    last_refresh_time DESC;

-- トランザクションを発生させたことで取引履歴テーブル(acct_txn)のレコード数変化を確認

select count(*) from ACCT_TXN;

※ADB付属のPerformance Hubでモニタリングできます。

image.png

image.png

image.png

image.png

(6) 後片付け(必要に応じて)
-- ジョブはauto_drop=TRUEのため完了後は消えます
-- テーブル削除例:

DROP TABLE acct_txn PURGE;
DROP TABLE accounts PURGE;

3. 運用上の注意

必ず検証用/非本番環境で実行してください。
負荷やREDO量、ストレージ消費が増えます。
同時実行数(FOR i IN 1..8)や実行時間、COMMIT頻度、DBMS_LOCK.sleepの有無で負荷を調整してください。
テーブルやインデックス名は既存オブジェクトと衝突しないよう調整してください。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?