|
ダイレクトSQLコマンドインジェクション対策として、BIND機能を利用することができます。
例えばシングルクオート「'」を含むデータをINSERTするとき、データを含むSQL文を文字列で作る場合ですと、「''」に変換する必要があります。エンドユーザーにテキストボックスに入力させる場合などはここら辺を考慮しないと、画面上に「ORA-XXXXX」なんてエラーを表示してしまいます。ここでBINDを使うと、データの内容を意識しないで済むようになります。以下のサンプルで、HTMLフォームからシングルクオートやバックスラッシュ「\」などを含む文字列を入力し、INSERTされた値と比較してみてください。
●テスト用テーブル定義
CREATE TABLE test(
col1 VARCHAR2(10),
col2 VARCHAR2(10)
);
●HowToUseBind2.html
<form action="HowToUseBind2.php" method="post">
<li>val1<input type="text" name="val1">
<li>val2<input type="text" name="val2">
<input type="submit">
</form>
●HowToUseBind2.php
<?php
$val1 = "";
if (isset($_POST['val1'])) {
$val1 = $_POST['val1'];
}
$val2 = "";
if (isset($_POST['val2'])) {
$val2 = $_POST['val2'];
}
echo "\$val1=" . htmlspecialchars($val1) . "<br>";
echo "\$val2=" . htmlspecialchars($val2) . "<br>";
$conn = OCILogon( "scott", "tiger", "orcl");
$sql = "INSERT INTO test VALUES(:col1, :col2) ";
$parse = OCIParse($conn, $sql);
OCIBindByName($parse, ":COL1", $val1, -1);
OCIBindByName($parse, ":COL2", $val2, -1);
@OCIExecute($parse, OCI_DEFAULT );
if (!isset($php_errormsg)) {
echo "COMMITします<br>";
OCICommit($conn);
} else {
echo "ROLLBACKします<br>";
echo "MSG=$php_errormsg<br>";
OCIRollback($conn);
}
OCIFreeStatement($parse);
$stmt = OCIParse($conn,"SELECT * FROM test ");
OCIExecute($stmt);
$ncols = OCINumCols($stmt);
echo "<table border='1'>";
echo "<tr>";
for ( $i = 1; $i <= $ncols; $i++ ) {
$column_name = OCIColumnName($stmt,$i);
echo "<th>" . $column_name . "</th>";
}
echo "</tr>";
while(OCIFetch($stmt))
{
echo "<tr>";
for ( $i = 1; $i <= $ncols; $i++ ) {
$column_name = OCIColumnName($stmt,$i);
echo "<td>" . OCIResult($stmt, $column_name) . "</td>";
}
echo "</tr>";
}
echo "</table>";
OCIFreeStatement($stmt);
OCILogoff($conn);
?>
また、SQLの実行には、
- 条件を埋め込んだSQL文を作成
- parse
- execute
のような手順と思いますが、SQLのparseは意外とコストがかかるステップになります。ここでBIND関数を使うことにより、parseの部分を1回だけ実行すれば良くなります。これは、forループなどで複雑なSQLを複数回実行する必要がある場合などにパフォーマンスの面からも有効と思います。また、OCI関数を使う場合、毎回OCIFreeStatement関数を呼び出す必要がなくなります。(BINDを使用しない場合、毎回OCIFreeStatement関数を呼ばないと、オープンしたカーソル数の最大値をオーバーする可能性があります)
ついでに、どのくらいパフォーマンスが変わるのか、こちらにあるソースを使って実行時間を測定してみました。テストに使用した環境は以下の通りです。
●PHPスクリプト
<?php
$conn = OCILogon("scott", "tiger", "orcl");
$sql = "INSERT INTO test VALUES (:col1, :col2) ";
$parse = OCIParse($conn, $sql);
for ($i = 0; $i < 100; $i++) {
$col2 = "TEST" . $i;
OCIBindByName($parse, ":COL1", $i, -1);
OCIBindByName($parse, ":COL2", $col2, -1);
OCIExecute($parse, OCI_DEFAULT);
}
OCIFreeStatement($parse);
OCILogoff($conn);
?>
今回の測定結果は以下の通りとなりました。秒数は5回の平均値です(10000回ループのみ2回の平均です)。
結果からループ回数が増加すると共に差が明らかとなりました。まあ、10000回ループするような処理ならコンパイル言語で書いた方が断然早いと思いますが。。。また、今回はかなり簡単なINSERT文で行ってみましたが、1レコードあたりのカラム数が非常に多い場合や条件が複雑なSQLでは結果は違ってくるかも知れません。
|