目次
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でモニタリングできます。
(6) 後片付け(必要に応じて)
-- ジョブはauto_drop=TRUEのため完了後は消えます
-- テーブル削除例:
DROP TABLE acct_txn PURGE;
DROP TABLE accounts PURGE;
3. 運用上の注意
必ず検証用/非本番環境で実行してください。
負荷やREDO量、ストレージ消費が増えます。
同時実行数(FOR i IN 1..8)や実行時間、COMMIT頻度、DBMS_LOCK.sleepの有無で負荷を調整してください。
テーブルやインデックス名は既存オブジェクトと衝突しないよう調整してください。



