(以下所有标识符均已匿名。)
我在 Postgres 中有以下查询:
SELECT id, dt
FROM table1 t1
WHERE status is not null
AND (
(NOT EXISTS (SELECT 1 FROM table2 t2 WHERE t2.big_int_id = t1.big_int_id and t2.count is not null)
AND t1.dt > NOW() - INTERVAL '15 minutes'
) OR
(NOT EXISTS
(SELECT 1 FROM table3 t3
JOIN table2 t2 ON t2.another_id = t3.another_id AND
t2.big_int_id = t3.big_int_id
)
AND t1.dt BETWEEN NOW() - INTERVAL '15 minutes' AND NOW()
)
)
执行大约需要1秒。
然后我改变了:
(SELECT 1 FROM table2 t2 WHERE t2.big_int_id = t1.big_int_id and t2.count is not null)
至:
(SELECT 1 FROM table2 t2 WHERE t2.text_id = t1.text_id and t2.count is not null)
现在查询需要 10 分钟以上才能执行。我对 SQL 的经验有限,不知道为什么这会导致速度变慢。
从比较
big_int_id
(大整数类型的列)到 text_id
(text
类型的列,但文本的形式为 "XYZ-#####"
,其中 ##### 表示某个整数值)的变化1 到 10 亿之间)似乎会导致 2 个变化:
比较
text
而不是 bigint
- 不知道会慢多少?
在
t1
中,每个不同的 t1.big_int_id
恰好有 2 行,但每个不同的 t2.text_id
恰好有 1 行,并且 98% 的行将 text_id
设为 null
(尽管可能会发生变化)将来)。我认为从 SELECT
语句创建的笛卡尔积可能会大很多?
未修改查询的查询计划:
Seq Scan on table t1 (cost=0.00..7362242.49 rows=1 width=24)
Filter: ((statis is null) AND (((NOT (SubPlan 1)) AND (dt > (now() - '00:15:00'::interval))) OR ((NOT (SubPlan 2)) AND (dt >= (now() - '00:15:0
0'::interval)) AND (dt <= now()))))
SubPlan 1
-> Index Scan using idx_t2_QRID on table2 t2 (cost=0.56..80.66 rows=17 width=0)
Index Cond: (big_int_id = t1.big_int_id)
Filter: (count IS NOT NULL)
SubPlan 2
-> Nested Loop (cost=0.99..239.84 rows=2 width=0)
-> Index Only Scan using idx_t2_QRID on table2 q_1 (cost=0.56..22.64 rows=38 width=8)
Index Cond: (big_int_id = t1.big_int_id)
-> Index Only Scan using idx_table3_qid_rt on table3 t3 (cost=0.42..5.71 rows=1 width=8)
Index Cond: (big_int_id = q_1.big_int_id)
修改查询的查询计划:
Seq Scan on table t1 (cost=0.00..2318157244.23 rows=1 width=24)
Filter: ((statis is null) AND (((NOT (SubPlan 1)) AND (dt > (now() - '00:15:00'::interval))) OR ((NOT (SubPlan 2)) AND (dt >= (now() - '00:15:0
0'::interval)) AND (dt <= now()))))
SubPlan 1
-> Seq Scan on table2 t2 (cost=0.00..671602.70 rows=17 width=0)
Filter: ((count IS NOT NULL) AND (text_id = t1.text_id))
SubPlan 2
-> Nested Loop (cost=0.99..239.84 rows=2 width=0)
-> Index Only Scan using idx_t2_QRID on table2 q_1 (cost=0.56..22.64 rows=38 width=8)
Index Cond: (big_int_id = t1.big_int_id)
-> Index Only Scan using idx_table3_qid_rt on table3 t3 (cost=0.42..5.71 rows=1 width=8)
Index Cond: (big_int_id = q_1.big_int_id)
t1
的索引:
CREATE INDEX t1_text_id ON t1 (text_id)
CREATE INDEX t1_id ON t1 (id)
CREATE UNIQUE INDEX t1_pkey ON t1 (id10)
t2
的索引:
CREATE UNIQUE INDEX idx_t2_text_id ON t2 (text_id)
CREATE INDEX idx_t2_QRID ON t2 (big_int_id, id5)
CREATE UNIQUE INDEX t2_pkey ON t2 (id5)
t3
的索引:
CREATE INDEX idx_table3_qid_rt ON t3 (big_int_id, response_type)
CREATE UNIQUE INDEX table3_pkey ON t3 (big_int_id)
第一步,我将查询整理为:
SELECT t1.id, t1.dt
FROM table1 t1
WHERE t1.status IS NOT NULL
AND t1.dt > now() - interval '15 minutes'
AND (
NOT EXISTS (SELECT FROM table2 t2 WHERE t2.big_int_id = t1.big_int_id AND t2.count IS NOT NULL)
-- WHERE t2.text_id = t1.text_id
OR NOT EXISTS (SELECT FROM table2 t2 JOIN table3 t3 USING (another_id, big_int_id)
AND t1.dt <= now()
)
最重要的是,您可以从
t1.dt > now() - interval '15 minutes'
分支中提取过滤器 OR
。这应该会使计划更简单/查询本身更快一些。
此外,您的原始版本中存在一个隐藏的极端情况问题。
BETWEEN
包括下限(和上限),而您的另一个分支具有用于下限的运算符 >
。通常,您希望避免使用时间戳值BETWEEN
。
text
与 bigint
的原始比较通常会稍微贵一些,原因有两个:(1) text
通常占用更多空间。 (2) text
通常涉及排序规则。一开始就将整数包装在 text
中,这听起来像是一个设计错误。但这是一个旁注。不是主要问题。
主要问题是在桌子
Index Scan
上从Seq Scan
切换到t2
。 t2(text_id)
上有索引,但没有使用,这可能有多种原因。也许它与我修改后的查询一起使用。也许只是跑步的问题
ANALYZE t2;
或者:
REINDEX INDEX idx_t2_text_id;
或者:
VACUUM FULL t2;
后者具有侵入性,会在持续时间内阻止并发访问。也许一些非阻塞等价物......
无论哪种方式,所有这些都是无聊的维护。更有趣的细节是这样的:
98% 的行的
为空text_id
虽然该百分比不会大幅下降,但部分索引是一个有趣的候选者(作为现有索引的补充或替代):
CREATE UNIQUE INDEX t2_text_id_notnull_idx ON t2 (text_id) WHERE text_id IS NOT NULL;
更小的索引应该更容易使用(也更便宜)。