表T(user, timestamp,...)
有100毫升+记录(PostgreSQL 9.1)。
表单的查询
SELECT *
FROM T
WHERE user='abcd'
ORDER BY timestamp
LIMIT 1
当有大约100000个用户记录时,使用timestamp
索引而不是用户索引。
使用时间戳索引总是会得到较差的结果(20秒以上),因为它最终会扫描所有记录。通过更改查询以使用timestamp
来绕过ORDER BY DATE(timestamp)
索引将导致查询求助于用户索引并提供小于100毫秒的结果。
为什么postgresql忽略user
索引并且正在使用timestamp
索引(时间戳索引需要查看所有记录)?
是否有任何postgresql配置参数可以更改,以使查询使用用户名索引本身?
答案 0 :(得分:6)
好问题,我刚才解决了这个问题。
您应该像这样查看stats中的user='abcd'
值的数量:
SELECT attname, null_frac, ag_width, n_distinct,
most_common_vals, most_common_freqs, histogram_bounds
FROM pg_stats
WHERE table_name='T';
我的猜测是 - 这个值经常发生,你会在most_common_vals
输出中找到它。
从most_common_freqs
中挑选相同的元素,获取值的比率,将其乘以总行数(可以从pg_class
获得),以获得估计有'abcd'
值。
Planner假设所有值都具有线性分布。实际上,事情当然是不同的。 此外,目前还没有correlated stats(although some work is being done in this direction)。
因此,让我们在相应的user='abcd'
条目中获取0.001
值,most_common_freqs
比率(每个问题)。这意味着每1000行会出现一次值(假设为线性分布)。看来,如果我们以任何方式扫描表 ,我们会在大约1000行中点击user='abcd'
。听起来应该快!规划师"认为"相同并选择timestamp
列上的索引。
但事实并非如此。如果我们假设您的表T
包含用户活动日志,并且user='abcd'
在过去3周内正在休假,那么这意味着我们必须阅读相当的内容在我们真正达到我们想要的行之前,来自timestamp
索引的大量行(3周的数据)。嗯,你作为DBA知道这一点,但计划者假设线性分布。
您必须欺骗规划人员使用您需要的内容,因为您对数据有更多了解。
将OFFSET 0
trick与子查询一起使用:
SELECT *
FROM
(
SELECT * FROM T WHERE user='abcd' OFFSET 0
)
ORDER BY timestamp
LIMIT 1;
这个技巧可以保护查询不被内联,因此内部部分可以自己执行。
使用CTE
(命名子查询):
WITH s AS (
SELECT * FROM T WHERE user='abcd'
)
SELECT *
FROM s
ORDER BY timestamp
LIMIT 1;
每份文件:
WITH查询的一个有用属性是,每次执行父查询时仅评估,即使父查询或兄弟WITH查询多次引用它们。
使用count(*)
进行聚合查询:
SELECT min(session_id), count(*) -- instead of simply `min(session_id)`
FROM T
WHERE user='abcd'
ORDER BY timestamp
LIMIT 1;
这不太适用,但我想提一下。
请考虑升级到9.3。
P.S。更多关于行estiamtes in the docs of course。