web-dev-qa-db-ja.com

クエリでJDBCの準備済みステートメントを使用する場合、WHERE NOT NULLの部分インデックスを使用する必要がありますか?

Postgresで部分的なインデックスを作成して、無駄なNULLデータを回避するとします。

 CREATE INDEX my_ix ON my (col1) WHERE col1 IS NOT NULL;

SELECTクエリを次のように記述する必要があります。

SELECT * FROM my WHERE col1 = 'abc';

またはとして:

SELECT * FROM my WHERE col1 = 'abc' AND col1 IS NOT NULL;

JDBC接続ライブラリの場合、次のようになります。

SELECT * FROM my WHERE col1 = ?;

そして、私はPostgresが以下なしでインデックスの適用性を推測できるかどうかわかりません:

 AND col1 IS NOT NULL

また、Postgresが推移性を推測できるかどうかも興味深いです。

 CREATE INDEX my_ix ON my (col1) WHERE col1 > 0;

私は書く必要がないと思います:

SELECT * FROM my WHERE col1 > ? AND col1 > 0;

パラメータが10、23など(> 0)の場合。

3
gavenkoa

次のスクリプトで簡単に試すことができます。

CREATE TABLE my (col1) AS
   SELECT * FROM generate_series(1, 100000);

ANALYZE my;

CREATE INDEX my_null ON my (col1) WHERE col1 IS NOT NULL;

/* use a prepared statement similar to JDBC */
PREPARE stmt1(integer) AS SELECT * FROM my WHERE col1 = $1;

/* the first five executions use custom plans */
EXPLAIN (COSTS off) EXECUTE stmt1(1);
EXPLAIN (COSTS off) EXECUTE stmt1(2);
EXPLAIN (COSTS off) EXECUTE stmt1(NULL);
EXPLAIN (COSTS off) EXECUTE stmt1(4);
EXPLAIN (COSTS off) EXECUTE stmt1(5);
/* from the sicth execution on a generic plan is used */
EXPLAIN (COSTS off) EXECUTE stmt1(6);
EXPLAIN (COSTS off) EXECUTE stmt1(NULL);

DROP INDEX my_null;

CREATE INDEX my_pos ON my (col1) WHERE col1 > 0;

/* use a prepared statement similar to JDBC */
PREPARE stmt2(integer) AS SELECT * FROM my WHERE col1 > $1;

/* the first five executions use custom plans */
EXPLAIN (COSTS off) EXECUTE stmt2(99997);
EXPLAIN (COSTS off) EXECUTE stmt2(99997);
EXPLAIN (COSTS off) EXECUTE stmt2(1);
EXPLAIN (COSTS off) EXECUTE stmt2(NULL);
EXPLAIN (COSTS off) EXECUTE stmt2(99998);
/* PostgreSQL continues to use a custom plan! */
EXPLAIN (COSTS off) EXECUTE stmt2(0);
EXPLAIN (COSTS off) EXECUTE stmt2(NULL);
EXPLAIN (COSTS off) EXECUTE stmt2(99997);

DROP TABLE my;
DEALLOCATE ALL;

ここ は、コードを調整するSQLフィドルです。

説明:

準備済みステートメントを使用すると、PostgreSQLはデータベースセッションのクエリプランをキャッシュできます。このような一般的な計画を使用すると、パラメーター値に関係なく同じままで、計画時間を節約できるという利点があります。

ただし、PostgreSQLは常にジェネリックプランを使用するとは限りません。これは、パラメータ値を尊重するカスタムプランを生成する方が良い場合があるためです。

何をすべきかを決定するために、PostgreSQLは次のヒューリスティックを使用します。

  • 最初の5つの実行は常にカスタムプランを使用します。

  • カスタムプランの推定コストがジェネリックプランの推定コストよりも安くない場合、PostgreSQLは6回目以降のジェネリックプランを使用します。

PostgreSQL v12以降では、plan_cache_mode構成パラメーターを使用して動作を構成できます。

最初のステートメントが一般的なプランを使用するのはなぜですか?

最初のステートメントでは、引数がNULLであっても、常にインデックススキャンを使用できます(この場合は何も実行する必要がないため)。したがって、PostgreSQLは5回目の実行後に汎用プランを使用します。

EXPLAIN出力の$1から一般的なプランを認識できます。

2番目のステートメントがカスタムプランを引き続き使用するのはなぜですか?

一般的なプランでは、インデックスは一部のパラメーター値にしか使用できないため、PostgreSQLはシーケンシャルスキャンしか使用できません。最初の5回の実行の一部では、カスタムプランは一般的なプランよりも大幅に安価であるため、PostgreSQLは引き続きカスタムプランを使用します。

質問に答えるには:

WHERE条件を追加する必要がある場合、PostgreSQLがインデックスを使用できることをPostgreSQLに認識させるには、個々のケースによって異なります。

不利な条件なしで追加の条件を追加できます。これは、オプティマイザに役立ち、害を及ぼすことはありません。

しかし、追加の条件があっても、インデックスが使用されるかどうかはわかりません。

3
Laurenz Albe