簡単なテスト条件 (クエリごとに 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 のクエリ プランナーの癖 (またはバグ?) に出くわしたのではないかと思います。どうすれば回避できるかについてのアイデアはありますか?