この章では、JdbcRunnerが提供する独自ファンクションの使い方を説明します。
SQL発行ファンクションはいずれも1番目の引数に独自記法のSQL文をとります。このSQL文は基本的にPreparedStatementの記法に準じますが、一つ異なる点があります。それはPreparedStatementでプレースホルダとして用いる「?」記号の代わりに、以下の独自プレースホルダを記述するということです。
JDBCでクエリを発行する例を以下に示します。
pstmt = conn.prepareStatement("SELECT ename FROM emp WHERE empno = ?");
pstmt.setInt(1, id);
rs = pstmt.executeQuery();
while (rs.next()) {
count++;
}
rs.close();
pstmt.close();
これと同じ処理を、JdbcRunnerでは以下のように書きます。
var count = query("SELECT ename FROM emp WHERE empno = $int", id);
本記法においては「$」は特殊記号として扱われます。もしSQLに「$」という文字そのものを使いたい場合は、「$$」と書いてください。次に示すのは、PostgreSQLにおいて「ドル引用符付け」という記法を用いる例です。
query("SELECT $$tag$$Dianne's horse$$tag$$");
NULL値をバインドする場合は、JavaScriptのnullを指定してください。
var value = null;
execute("INSERT INTO test (c1) VALUES ($int)", value);
SQL文に日付・時刻をバインドする方法は複数用意されています。
以下はJavaScriptのDateオブジェクトをバインドする例です。
var d = new Date(2010, 0, 2, 3, 4, 5); // 2010年1月2日 3時4分5秒
query("SELECT ename FROM emp WHERE hiredate < $timestamp", d);
Javaのjava.util.Dateオブジェクトをバインドする例です。
var d = new java.util.Date(1262369045000); // 2010年1月2日 3時4分5秒
query("SELECT ename FROM emp WHERE hiredate < $timestamp", d);
数値をバインドする例です。数値として、1970年1月1日 0時0分0秒 GMTからの経過ミリ秒を指定します。
var d = 1262369045000; // 2010年1月2日 3時4分5秒
query("SELECT ename FROM emp WHERE hiredate < $timestamp", d);
文字列をバインドする例です。文字列はJDBCタイムスタンプエスケープ形式(yyyy-mm-dd hh:mm:ss[.f...])で記述します。
var d = "2009-01-02 03:04:05"; // 2010年1月2日 3時4分5秒
query("SELECT ename FROM emp WHERE hiredate < $timestamp", d);
RDBMSに対してクエリを発行するファンクションです。内部的にはPreparedStatement#executeQuery()のラッパになっています。
RDBMSに対してクエリを発行するファンクションです。内部的にはPreparedStatement#executeQuery()のラッパになっています。
query()では結果セットのレコード数しか得ることができませんが、fetchAsArray()では結果セットをJavaScriptの二次元配列として得ることができます。
mysql> SELECT * FROM dept ORDER BY deptno;
+--------+------------+----------+
| deptno | dname | loc |
+--------+------------+----------+
| 10 | accounting | new york |
| 20 | research | dallas |
| 30 | sales | chicago |
| 40 | operations | boston |
+--------+------------+----------+
4 rows in set (0.00 sec)
以下は、このdeptテーブルからデータを取得するサンプルスクリプトです。
var rs = fetchAsArray("SELECT * FROM dept ORDER BY deptno");
info("rows : " + rs.length);
info("columns : " + rs[0].length);
info("row1col1 : " + rs[0][0]);
info("row2col3 : " + rs[1][2]);
この例では次のようなログが出力されます。
13:47:24 [INFO ] rows : 4
13:47:24 [INFO ] columns : 3
13:47:24 [INFO ] row1col1 : 10
13:47:24 [INFO ] row2col3 : dallas
fetchAsArray()はクライアントの負荷が大きくなってしまうため、結果セットが必要ない場合はquery()を用いるようにしてください。
RDBMSに対してDMLを発行するファンクションです。内部的にはPreparedStatement#executeUpdate()のラッパになっています。
RDBMSに対してJDBCバッチ更新を行うファンクションです。内部的にはPreparedStatement#addBatch()、PreparedStatement#executeBatch()のラッパになっています。
paramArrayにはJavaScriptの配列を指定します。パラメータが複数ある場合は、それらの要素数を揃えておく必要があります。
var c1Array = new Array(1, 2, 3);
var c2Array = new Array("Apple", "Orange", "Banana");
executeBatch("INSERT INTO test (c1, c2) VALUES ($int, $string)", c1Array, c2Array);
この例では、3つのレコードを一度にINSERTすることができます。
mysql> SELECT * FROM test ORDER BY c1;
+----+--------+
| c1 | c2 |
+----+--------+
| 1 | Apple |
| 2 | Orange |
| 3 | Banana |
+----+--------+
3 rows in set (0.00 sec)
エージェントが現在使用している、データベースへの接続を返すファンクションです。このファンクションは、JDBCの機能を直接呼び出す際に利用します。
オートコミットモードを切り替える例を以下に示します。
var conn = takeConnection();
conn.setAutoCommit(true);
トランザクション分離レベルを設定する例を以下に示します。
var conn = takeConnection();
conn.setTransactionIsolation(java.sql.Connection.TRANSACTION_SERIALIZABLE)
このファンクションは新たにコネクションプールからデータベースへの接続を払い出すのではなく、現在すでに使用している接続を返すという点に注意してください。
RDBMSの製品名を返すファンクションです。内部的にはDatabaseMetaData#getDatabaseProductName()のラッパになっています。
データベースへの変更を確定するファンクションです。このメソッドを使う場合は、オートコミットモードが無効になっている必要があります。
データベースへの変更を取り消すファンクションです。このメソッドを使う場合は、オートコミットモードが無効になっている必要があります。
run()ファンクションの停止フラグを立てるファンクションです。このファンクションを実行すると、run()ファンクションをそれ以上繰り返さなくなります。ロードモードと組み合わせて、指定回数だけ処理を行わせる際に利用します。
var isLoad = true;
var counter = 0;
function run() {
if (++counter <= 10) {
execute("INSERT INTO test (id, data) VALUES ($int, $string)",
counter, "ABCDEFGHIJKLMNOPQESTUVWXYZ");
} else {
setBreak();
}
}
トランザクション種別を設定するファンクションです。トランザクション種類数が5の場合、このファンクションには0以上4以下の値を設定することができます。
トランザクション種類数を2以上に設定してこのファンクションを用いることで、複数の処理をミックスさせた負荷テストを行い、それぞれのスループットとレスポンスタイムを分計することができます。
var nTxTypes = 2;
function run() {
var r = random(1, 100);
if (r <= 60) {
setTxType(0);
orderFunc();
} else {
setTxType(1);
paymentFunc();
}
}
エージェント間で共有しているデータを取得するファンクションです。内部的にはjava.util.concurrent.ConcurrentHashMap#get()のラッパになっています。
エージェント間で共有したいデータを登録するファンクションです。内部的にはjava.util.concurrent.ConcurrentHashMap#put()のラッパになっています。
負荷テストの初期化処理でテーブルの主キー一覧を取得し、それを各エージェントに共有させる例を以下に示します。
var empArr;
function init() {
putData("emp", fetchAsArray("SELECT empno FROM emp"));
}
function run() {
if (!empArr) {
empArr = getData("emp");
}
var empno = empArr[random(0, empArr.length - 1)][0];
query("SELECT empno, ename FROM emp WHERE empno = $int", empno);
}
mix以上max以下のランダムな整数を返すファンクションです。maxを含みます。
try~catch文で受け取った例外オブジェクトを引数にして、スタックトレースを取得するファンクションです。以下に例を示します。
try {
...
} catch (e) {
warn("[Agent " + getId() + "] " + e.javaException + getScriptStackTrace(e));
rollback();
}
こうすると、以下のように例外の発生箇所を特定することができます。
2010-02-18 01:26:27 [WARN ] [Agent 8] org.postgresql.util.PSQLException: ERROR: deadlock detected
Detail: Process 5534 waits for ShareLock on transaction 1274540; blocked by process 5529.
Process 5529 waits for ShareLock on transaction 1274542; blocked by process 5534.
Hint: See server log for query details.
at scripts\tpcc.js:282
at scripts\tpcc.js:68