0

簡単なテスト条件 (クエリごとに 1 回だけ評価する必要があります) を追加すると、クエリ全体が返されるまでに 10 倍の時間がかかるという問題があります。私は PostgreSQL 9.2 を実行しています。使用中のテーブルは次のとおりです。

CREATE TABLE tags (
    tid     int4 UNIQUE NOT NULL,
    name    text UNIQUE NOT NULL,
    PRIMARY KEY (tid));

CREATE TABLE bugs (
    bid     int4 UNIQUE NOT NULL,
    type    int2 NOT NULL,
    title   text NOT NULL,
    PRIMARY KEY (bid));

CREATE TABLE bug_tags (
    bid     int4 REFERENCES bugs(bid) NOT NULL,
    tid     int4 REFERENCES tags(tid) NOT NULL,
    PRIMARY KEY (bid, tid));

CREATE INDEX bug_tags_bid_idx ON bug_tags (bid);
CREATE INDEX bug_tags_tid_idx ON bug_tags (tid);

クエリは、入札で並べ替えられた最初の 20 件の一致するバグを返す必要があります。一致条件は、バグのタイプが指定されたビットマスク (変数 $TYPE) に準拠する必要があり、バグに関連付けられたタグのセットが指定されたタグのスーパーセットである必要があります。タグ名のセット (準備済みステートメントでは $TAGNAMES で示されます)。いくつかのアプローチを試した後、以下は最良の結果を生み出すものです. 基本的な考え方は、最初に $TAGNAMES 条件を満たすすべてのバグを取得し、次に $TYPE 条件についてそれぞれをテストすることです。さらに、$TAGNAMES が空の場合、それらのバグのフェッチをスキップし、代わりにテーブル全体をループすることに集中します (最初の 20 行のみが必要なので、これはおそらくはるかに高速です)。この後者のテストは、不可解な結果を引き起こしているものです。

WITH tids AS
            (SELECT tid
            FROM    tags
            WHERE   name = ANY ($TAGNAMES :: text[])),
    bugs_from_tags AS
            (SELECT DISTINCT t1.bid
            FROM    bug_tags AS t1
            WHERE   NOT EXISTS
                    (SELECT *
                    FROM    tids
                    WHERE   NOT EXISTS
                            (SELECT *
                            FROM    bug_tags AS t2
                            WHERE   t2.tid = tids.tid AND t2.bid = t1.bid)))
SELECT bid, type, title
FROM bugs
WHERE (type & $TYPE <> 0) AND
  (($TAGNAMES :: text[]) = '{}' OR bid IN (SELECT * FROM bugs_from_tags))
ORDER BY bid
LIMIT 20;

テストなしのバージョンはもちろん、WHERE 句を除いて同じです。代わりに次のようになります。

WHERE (type & $TYPE <> 0) AND (bid IN (SELECT * FROM bugs_from_tags))

以下は、1,000,000 個のバグ、2,000 個のタグ、および bug_tags の 200,000 行で構成されるテスト データベース (ランダム データを含む) のおよその ANALYZE 結果です。「あり」および「なし」の列は、空に対するテストあり/なしのクエリ バージョンを参照します。数値はミリ秒単位です。

                            with    without
len($TAGNAMES) = 0:         4       1500
len($TAGNAMES) = 1:         13000   1600

明らかに、テスト条件を追加すると、len($TAGNAMES) = 0 の場合はうまくいきますが、それ以外の場合は悲惨な結果になります。条件は各行から独立しているため、これは不可解です。したがって、一度だけ評価する必要があります。さらに、EXPLAIN ANALYZE (非常に長い) の結果は、len($TAGNAMES) = 1 の場合に使用されるプランと使用されないプランが非常に異なることを示していますが、それらは同一である必要があります!

とにかく、PostgreSQL のクエリ プランナーの癖 (またはバグ?) に出くわしたのではないかと思います。どうすれば回避できるかについてのアイデアはありますか?

注:両方のテストの EXPLAIN ANALYZE の結果は、 ここここにあります。

4

1 に答える 1

0

プランを読むと、主な違いは、高速プランが最初に結果をフィルタリングしてから結合することです。2 番目のフィルターとネストされたループ結合。

遅いクエリについて 2 つのことが気になります。

1つ目は、悪い計画を選んでいるということです。これは、別のプランの方が優れていることを確認するのに十分なメモリがない場合に発生する可能性があります。effective_cache_size を少し高く設定し、work_mem を少し高く設定してみてください。

2 つ目は、スロー クエリにハッシュ集計がないことです。DISTINCT を GROUP BY に変更して、これが役立つかどうかを確認することをお勧めします。私の推測では、遅いものでは、おそらく CTE を具体化し、それに対してネストされたループとしてクエリを実行していると思います。

ただし、これらを超えて、システムにメモリを追加する可能性があるため、すぐに答えられるものは何もありません。これがお役に立てば幸いです。ここでヘルプが得られない場合は、PostgreSQL の -perform リストが役立つことがよくあります。

于 2013-04-24T07:47:53.717 に答える