我定义了以下表格和索引:
CREATE TABLE ticket
(
wid bigint NOT NULL DEFAULT nextval('tickets_id_seq'::regclass),
eid bigint,
created timestamp with time zone NOT NULL DEFAULT now(),
status integer NOT NULL DEFAULT 0,
argsxml text,
moduleid character varying(255),
source_id bigint,
file_type_id bigint,
file_name character varying(255),
status_reason character varying(255),
...
)
我在created
时间戳上创建了一个索引,如下所示:
CREATE INDEX ticket_1_idx
ON ticket
USING btree
(created );
这是我的查询
select * from ticket
where created between '2012-12-19 00:00:00' and '2012-12-20 00:00:00'
这种情况很好,直到记录数量开始增长(约500万),现在它将永远回归。
解释分析揭示了这一点:
"Index Scan using ticket_1_idx on ticket (cost=0.00..10202.64 rows=52543 width=1297) (actual time=0.109..125.704 rows=53340 loops=1)"
" Index Cond: ((created >= '2012-12-19 00:00:00+00'::timestamp with time zone) AND (created <= '2012-12-20 00:00:00+00'::timestamp with time zone))"
"Total runtime: 175.853 ms"
到目前为止,我已尝试设置
random_page_cost = 1.75
effective_cache_size = 3
还创建了
create CLUSTER ticket USING ticket_1_idx;
没有任何作用。我究竟做错了什么?为什么选择顺序扫描?索引应该使查询快速。有什么可以做的来优化它吗?
答案 0 :(得分:19)
CLUSTER
如果您打算使用CLUSTER
,则显示的语法无效。
<击> create CLUSTER ticket USING ticket_1_idx;
击>
运行一次:
CLUSTER ticket USING ticket_1_idx;
这个可以对更大的结果集有很大的帮助。返回单行并非如此 Postgres会记住用于后续调用的索引。如果您的表格不是只读的,则效果会随着时间的推移而恶化,您需要以一定的间隔重新运行:
CLUSTER ticket;
可能只在易失性分区上。见下文。
但是,如果您有大量更新,CLUSTER
(或VACUUM FULL
)可能实际上对性能不利。适量的膨胀允许UPDATE
将新行版本放在同一数据页面上,并且避免了太频繁地在OS中物理扩展底层文件的需要。您可以使用经过精心调整的FILLFACTOR
来充分利用这两个方面:
pg_repack
CLUSTER
对表进行独占锁定,这可能是多用户环境中的问题。 Quoting the manual:
当群集表时,会获取
ACCESS EXCLUSIVE
锁 在上面。这可以防止任何其他数据库操作(读取和 写入)来对表进行操作,直到CLUSTER
完成。
大胆强调我的。考虑alternative pg_repack
:
与
CLUSTER
和VACUUM FULL
不同,它可以在线使用,无需持有 在处理期间对处理过的表进行独占锁定。 pg_repack是 高效启动,性能与直接使用CLUSTER
相当。
和
pg_repack需要在重组结束时进行独占锁定。
版本1.3.1适用于:
PostgreSQL 8.3,8.4,9.0,9.1,9.2,9.3,9.4
版本1.4.2适用于:
PostgreSQL 9.1,9.2,9.3,9.4,9.5,9.6,10
查询很简单,不会导致任何性能问题。
但是,关于正确性的一句话:BETWEEN
构造包含边框。您的查询将从12月20日00:00开始选择所有12月19日的 plus 记录。这是极不可能的要求。机会是,你真的想要:
SELECT *
FROM ticket
WHERE created >= '2012-12-19 0:0'
AND created < '2012-12-20 0:0';
首先,你问:
为什么选择顺序扫描?
您的EXPLAIN
输出清楚地显示索引扫描,而不是顺序表扫描。必定存在某种误解。
如果你为了更好的表现而努力,你可以改善一些事情。但是必要的背景信息不在问题中。可能的选项包括:
您只能查询所需的列而不是*
,以降低转移成本(以及可能的其他性能优势)。
您可以查看partitioning并将实际时间片放入单独的表中。根据需要为分区添加索引。
如果分区不是一个选项,另一个相关但较少侵入性的技术是添加一个或多个partial indexes。
例如,如果您主要查询当前月份,则可以创建以下部分索引:
CREATE INDEX ticket_created_idx ON ticket(created)
WHERE created >= '2012-12-01 00:00:00'::timestamp;
CREATE
在新月开始前新索引。您可以使用cron作业轻松自动执行任务。
可选择DROP
部分索引,过去几个月。
保留CLUSTER
(不能对部分索引进行操作)的总索引。如果旧记录永远不会更改,表分区将对此任务有很大帮助,因为您只需要重新集群较新的分区。
然后,如果记录永远不会改变,您可能不需要CLUSTER
。
如果你结合最后两个步骤,性能应该很棒。
您可能遗漏了其中一个基础知识。所有通常的表现建议均适用: