5

標準のBツリーインデックスでProjects呼び出される列で呼び出されるテーブルがあるとします。Budgetこのテーブルには50,000のプロジェクトがあり、そのうちの1%だけが100万を超える予算を持っています。SQLクエリを実行した場合:

SELECT * From Projects WHERE Budget > 1000000;

プランナーは、インデックス範囲スキャンを使用Budgetして、ヒープテーブルから行を取得します。ただし、クエリを使用する場合:

SELECT * From Projects WHERE Budget > 50;

プランナーは、このクエリがいずれにせよほとんどまたはすべての行を返すことになり、インデックスのすべてのページをメモリにロードする理由がないことを知っているため、テーブルに対して順次スキャンを実行する可能性があります。

ここで、クエリを実行するとします。

SELECT * From Projects WHERE Budget > :budget;

:budgetデータベースに渡されるバインドパラメータはどこにありますか。私が読んだものから、上記のクエリはキャッシュされ、カーディナリティに関するデータは推測できません。実際、ほとんどのデータベースは均等な分散を想定しており、キャッシュされたクエリプランはそれを反映します。これは私を驚かせました。通常、バインドパラメータの利点について読むと、SQLインジェクション攻撃を防ぐことができます。

明らかに、新しいプランをコンパイルする必要がないため、結果のクエリプランが同じである場合、これによりパフォーマンスが向上する可能性がありますが、の値が大幅に異なる場合はパフォーマンスが低下する可能性もあります。:budget

私の質問:クエリプランが生成されてキャッシュされる前にバインドパラメータが解決されないのはなぜですか?最新のデータベースは、クエリの最適な計画を生成するように努めるべきではありません。つまり、各パラメーターの値を調べて、正確なインデックス統計を取得する必要がありますか?

注: mySqlはSQLプランをキャッシュしないため、この質問はおそらくmySqlには当てはまりません。ただし、Postgres、Oracle、およびMSSQLでこれが当てはまる理由に興味があります。

4

2 に答える 2

7

特にOracleの場合は、状況によって異なります。

かなり長い間(少なくとも9i)、Oracleはバインド変数のピークをサポートしてきました。つまり、クエリが最初に実行されると、オプティマイザはバインド変数の値を確認し、そのカーディナリティの見積もりをその最初のバインド変数の値に基づいて行います。これは、クエリの実行のほとんどに、同様のサイズの結果を返すバインド変数値が含まれる場合に意味があります。クエリの99%が小さな予算値を使用している場合、最初の実行で小さな値が使用される可能性が高いため、キャッシュされたクエリプランは小さなバインド変数値に適しています。もちろん、これは、大きなバインド変数値を指定すると(さらに悪いことに、運が良ければ最初の実行が大きな値で行われる場合)、最適なクエリプランよりも少なくなることを意味します。

11gを使用している場合、Oracleはアダプティブカーソル共有を使用できます。これにより、オプティマイザーは単一のクエリに対して複数のクエリプランを維持し、バインド変数の値に基づいて適切なプランを選択できます。ただし、時間の経過とともにかなり複雑になる可能性があります。N個のバインド変数を含むクエリがある場合、オプティマイザは、新しいクエリをいつ再最適化するかを判断するために、そのN次元空間をさまざまなバインド変数値のさまざまなクエリプランに分割する方法を理解する必要があります。バインド変数値のセットと、以前のプランを単純に再利用する場合。その作業の多くは、生産的な日中にこれらのコストが発生するのを避けるために、夜間のメンテナンス期間中に行われることになります。

于 2012-09-05T17:13:20.107 に答える
6

これは私を驚かせました。通常、バインドパラメータの利点について読むと、SQLインジェクション攻撃を防ぐことができます。

パラメータ化されたクエリ準備されたステートメントと混同しないでください。どちらもパラメーター化を提供しますが、プリペアドステートメントはクエリプランの追加のキャッシュを提供します。

クエリプランが生成されてキャッシュされる前にバインドパラメータが解決されないのはなぜですか?

クエリプランの生成はコストのかかる手順である場合があるためです。プリペアドステートメントを使用すると、クエリプランニングのコストを償却できます。

ただし、探しているのがSQLインジェクション保護だけの場合は、プリペアドステートメントを使用しないでください。パラメータ化されたクエリを使用します。

たとえば、PHPでは、http://php.net/pg_query_paramsを使用して、クエリプランをキャッシュせずにパラメータ化されたクエリを実行できます。一方、 http ://php.net/pg_prepareとhttp://php.net/pg_executeは、準備されたステートメントのプランをキャッシュし、後でそれを実行するために使用されます。

編集:9.2は、プリペアドステートメントの計画方法を明らかに変更します

于 2012-09-05T17:11:49.237 に答える