我有一个问题,即添加一个简单的测试条件(每个查询只应评估一次)导致整个查询需要十倍的时间才能返回。我正在运行 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 行。“with”和“without”列指的是带有/不带有空测试的查询版本。数字以毫秒为单位。
with without
len($TAGNAMES) = 0: 4 1500
len($TAGNAMES) = 1: 13000 1600
显然,当 len($TAGNAMES) = 0 时添加测试条件会带来可观的回报,否则会导致灾难。这令人费解,因为条件独立于每一行,因此只应评估一次。此外,EXPLAIN ANALYZE 的结果(很长)确实表明,在 len($TAGNAMES) = 1 的情况下使用/不使用的计划是非常不同的,尽管它们应该是相同的!
无论如何,我怀疑我偶然发现了 PostgreSQL 查询计划器的一个怪癖(或错误?)。关于如何解决它的任何想法?