EDB Postgresを.NET Frameworkから使ってみよう(バインド変数を使ったSQLの実行)
前回はEDB Postgresの.NET Framework用の接続ドライバであるEDBDataProviderの導入手順から簡単なSQLの実行までを紹介しました。今回は、バインド変数をつかったSQLの実行方法を紹介します。
環境
本手順は以下環境で実施しています。
実行環境
## Database
* OS: OracleLinux 6.4 64bit
* DB: EPAS 9.6.2 (64bit)
## Client
* OS: Windows 7
* EDB Connectors 9.6.2
* .NET Framewrok 4.5
* VisualStudio 2017 Community Editon
バインド変数
まず、バインド変数について簡単におさらいをしておきましょう。SQLを実行する場合、EDB Postgresでは大まかに以下のステップを経ます。
* parse(解析)
* bind(バインド処理)
* execute(実行)
上述の3つのステップは同じセッションであっても、SQLが異なる場合は必ず実行されます。一般的なWEBアプリケーションの場合、発行されるSQLはWHERE句の条件を除き殆どが同じであることが多いですが、WHERE句の条件が異なるため別のSQLとして処理されてしまいます。
// deptnoが10の社員一覧
select ename from emp where deptno=10;
// deptnoが20の社員一覧(異なるSQLとみなされる)
select ename from emp where deptno=20;
しかし、バインド変数を使用することでparse時点ではWHERE句の具体的な値を指定せずに解析させることができるため、条件が異なるSQLでも2度目以降はparse処理を回避することができます。
Oracleとの違い
Oracleにもバインド変数があり、SQLのパフォーマンス改善の基本として利用されています。Oracleでも同様に2度目以降の実行でparse処理が回避できますが、アーキテクチャの違いによりEDB Postgres(PostgreSQL含む)とOracle ではparse処理が回避できるタイミングが異なります。
* EDB Postgres(PostgreSQL含む)
- parseをしたセッションでの2度目以降の実行でparse処理が回避される(異なるセッションでは再度parseが発生する)
* Oracle
- parseを何れかのセッションが実行した場合、メモリ上にその結果が残っている限り、他のセッションも含めてparse処理が回避される
このような差があるため、Oracleに比べるとEDB Postgresでバインド変数を使うことでのパフォーマンス上のメリットは限定的です。
パフォーマンス以外のメリット
バインド変数はパフォーマンスだけではなく、セキュリティ面のメリットもあります。アプリケーションには、ユーザが直接検索条件を指定できる機能も多くあります。ユーザが入力した条件をそのままSQLに結合する作りの場合、SQLインジェクションが発生しえます。
例えば、deptno=10の社員についてmgr列での検索ができるアプリケーションを想定すると、以下の様にEDBCommandを作成できます。
'' UserInputStrにはユーザが入力したmgrの社員番号が入る想定
New EDBCommand("select * from emp where deptno=10 and mgr =" + UserInputStr, conn)
アプリケーションとしては、deptno=10という条件は固定し、mgr列での自由検索を意図しています。しかし、UserInputStrに"0 or 1 = 1"という文字列が入った場合は以下の様にdeptno=10以外も含めて全行が戻るSQLになってしまいます。
select * from emp where deptno=10 and mgr =0 or 1 = 1;
このようなコーディングの不備を付いた攻撃をSQLインジェクションと呼びます。SQLインジェクションを防ぐには、ユーザからの入力を直接SQLと連結するのではなくバインド変数として処理することが有効です。
'' UserInputStrにはユーザが入力したmgrの社員番号が入る想定
New EDBCommand("select * from emp where deptno=10 and mgr=:UserInputStr", conn)
上述の様なコーディングの場合、UserInputStrに先程の"0 or 1 = 1"を指定しようとしても、不正な条件が指定されたとしてエラーにすることができます。このように、セキュリティの観点からもバインド変数を正しく使用することが望ましいです。
EDBDataProviderでのバインド変数の使用方法
EDBDataProviderでバインド変数を使用する際は、バインド変数付で作成したEDBCommandに対してEDBParameterを追加する形で使用します。以下はdeptnoの条件をバインド変数化したSELECT文の実行サンプルです。
Dim command As EDBCommand = New EDBCommand("select empno, ename from emp where deptno = :deptno", conn) '' point1
command.Parameters.Add(
New EDBParameter(
"deptno",
EDBTypes.EDBDbType.Integer
)
) '' point2
command.Parameters("deptno").Value = 10 '' point3
Dim reader As EDBDataReader
reader = command.ExecuteReader()
:
このサンプルでのポイントは以下です。
* point1
- EDBCommandで指定するSQL文に:deptnoとしてバインド変数を埋め込む
* point2
- EDBCommand.Parameters.Addで:deptnoに対応するEDBParameterを追加する
* point3
- :deptnoのバインド変数に実際に使用する値(サンプルでは10)を指定する
バインド変数をEDBDataProviderで使用する場合は基本的にこの流れになります。これは、EDBDataProviderにかぎらずADO.NET全般でもほぼ同じ流れですので他のRDBMSを使用されたことがある方であれば理解しやすいかと思います。
なお、EDBParameterのコンストラクタはオーバーロードされていますので、初期化時点でバインド値を渡すなどいくつかのやり方があります。この点についてはVisual Studio等でEDBParameterの定義をご覧ください。
まとめ
バインド変数の概念とメリット、EDBDataProviderでの利用方法を紹介させていただきました。バインド変数はパフォーマンスや安全性に優れたアプリケーションを作成する上では必須とも言える機能です。本記事が皆様の参考になれば幸いです。
筆者情報
2011年にアシストに入社してからサポートセンターでOracle DatabaseやPostgreSQL、EDB Postgres Advanced Server(EPAS)といったデータベース製品を中心に担当しています。個人的にJavaやNode.js、Python等を触っていますので、PostgreSQLと絡めてアプリケーション周りの情報をお送りしていきます。
■本記事の内容について
本記事に記載されている製品およびサービス、定義及び条件は、特段の記載のない限り本記事執筆時点のものであり、予告なく変更になる可能性があります。あらかじめご了承ください。
■商標に関して
・Oracle®、Java、MySQL及びNetSuiteは、Oracle、その子会社及び関連会社の米国及びその他の国における登録商標です。
・Amazon Web Services、AWS、Powered by AWS ロゴ、[およびかかる資料で使用されるその他の AWS 商標] は、Amazon.com, Inc. またはその関連会社の商標です。
文中の社名、商品名等は各社の商標または登録商標である場合があります。
関連している記事
PostgreSQL開発に多く貢献しているEnterpriseDB社による WALアーカイブ設定に関するベストプラクティスをご紹介します。
EDB社が提供するPostgreSQLの拡張機能「system_stats」はPostgreSQL ユーザーがパフォーマンス問題に取り組む際の非常に強力なツールになります。SQLクエリでOS情報を取得できるため、DBエンジニアにとってはパフォーマンスの監視が格段に簡単になります。テストした結果をご紹介します。
PostgreSQLのオプティマイザがインデックスを適切に使用できない理由は様々ですが、本記事ではJDBC⇔PostgreSQL間でデータ型の不一致がインデックスの使用にどういった悪影響を及ぼすかを見ていきます