Postgres:按日期时间优化查询

问题描述 投票:5回答:4

我有一个日期时间字段为“updated_at”的表。我的很多查询都会使用范围查询来查询此字段,例如update_at>某个日期的行。

我已经为updated_at添加了一个索引,但是我的大多数查询仍然非常慢,即使我对返回的行数有限制。

我还可以做些什么来优化查询日期时间字段的查询?

sql performance postgresql postgresql-performance
4个回答
4
投票

对于任何给定的查询,索引的使用取决于使用该索引与顺序扫描相比的成本

开发人员经常认为,因为存在索引,查询应该运行得更快,如果查询运行缓慢,则索引就是解决方案。当查询返回少量元组时,通常会出现这种情况。但随着结果中元组数量的增加,使用索引的成本可能会增加。

你正在使用postgres。 Postgres不支持围绕给定属性进行聚类。这意味着postgres在遇到范围查询(att> a和att <b类型)时需要计算结果中元组数量的估计(确保经常清空数据库)和使用成本与执行顺序扫描相比的索引。然后它将决定使用什么方法。

你可以通过跑步检查这个决定

EXPLAIN ANALYZE <query>; 

在psql中。它会告诉你它是否使用索引。

如果你真的,真的想要使用索引而不是顺序扫描(有时需要它)并且你真的知道你在做什么,你可以在计划器常量中更改顺序扫描的成本或者禁用顺序扫描任何其他方法。有关详细信息,请参阅此页面:

http://www.postgresql.org/docs/9.1/static/runtime-config-query.html

确保浏览文档的正确版本。

--dmg


1
投票

通常,数据库优化器不会选择使用索引作为开放式范围,例如updated_at > somedate

但是,在许多情况下,datatime列不会超过“now”,因此您可以通过使用qazxsw poi将条件转换为范围来保留qazxsw poi的语义,如下所示:

> somedate

between谓词更有可能导致优化器选择使用索引。


请发布这种方法改善了采石场的性能。


0
投票

假设正在使用索引但性能仍然很差,我能想到的唯一补救方法是通过该索引对表进行聚类:where updated_at between somedate and current_timestamp

这将使具有相同update_at值的行移动到物理上共处,从而提高通过索引访问该表的查询的性能,尤其是对于大范围扫描。

但请注意文档中的警告,并注意在更新行时不会保留群集。

也:

在对表进行群集时,会在其上获取ACCESS EXCLUSIVE锁。这可以防止任何其他数据库操作(读取和写入)在CLUSTER完成之前在表上运行。

基于这些限制,它可能不适合您的案例,但可能对其他人有用。


0
投票

我在一个表中有类似的情况,有近1M行。

所以我在visited_at(datetime字段)上创建了一个索引b-tree,并尝试查询所有行:

between

我有:

http://www.postgresql.org/docs/9.1/static/sql-cluster.html

这意味着在索引之前没有对查询的改进。

但所以我将行减少到current_date-31

explain analyze select mes,count(usuario) as usuarios
from (
   SELECT distinct coalesce(usuario, ip) as usuario, (extract(year from visited_at), extract(month from visited_at))   AS mes
     FROM pageview 
     ) as usuarios
group by 1
order by 1

得到了

GroupAggregate (cost=445468.78..451913.54 rows=200 width=64) (actual time=31027.876..31609.754 rows=8 loops=1)
-> Sort (cost=445468.78..447616.37 rows=859035 width=64) (actual time=31013.501..31439.350 rows=358514 loops=1)
Sort Key: usuarios.mes
Sort Method: external merge Disk: 24000kB
-> Subquery Scan on usuarios (cost=247740.16..263906.75 rows=859035 width=64) (actual time=23121.403..28200.175 rows=358514 loops=1)
-> Unique (cost=247740.16..255316.40 rows=859035 width=48) (actual time=23121.400..28129.538 rows=358514 loops=1)
-> Sort (cost=247740.16..250265.57 rows=1010166 width=48) (actual time=23121.399..27559.241 rows=1010702 loops=1)
Sort Key: (COALESCE(pageview.usuario, (pageview.ip)::text)), (ROW(date_part('year'::text, pageview.visited_at), date_part('month'::text, pageview.visited_at)))
Sort Method: external merge Disk: 66944kB
-> Seq Scan on pageview (cost=0.00..84842.49 rows=1010166 width=48) (actual time=0.012..1909.324 rows=1010702 loops=1)
Total runtime: 31632.012 ms

到目前为止,我有一个小的改进铸造日期时间(visited_at :: date)

explain analyze select mes,count(usuario) as usuarios
from (
   SELECT distinct coalesce(usuario, ip) as usuario, (extract(year from visited_at), extract(month from visited_at))   AS mes
     FROM pageview 
     where visited_at > current_date - 31
     ) as usuarios
group by 1
order by 1

得到了

 -> Sort (cost=164735.62..165310.93 rows=230125 width=64) (actual time=9532.343..9602.743 rows=90871 loops=1)
Sort Key: usuarios.mes
Sort Method: external merge Disk: 5872kB
-> Subquery Scan on usuarios (cost=122598.79..126929.62 rows=230125 width=64) (actual time=7251.344..9178.901 rows=90871 loops=1)
-> Unique (cost=122598.79..124628.37 rows=230125 width=48) (actual time=7251.343..9157.837 rows=90871 loops=1)
-> Sort (cost=122598.79..123275.32 rows=270610 width=48) (actual time=7251.341..8932.541 rows=294915 loops=1)
Sort Key: (COALESCE(pageview.usuario, (pageview.ip)::text)), (ROW(date_part('year'::text, pageview.visited_at), date_part('month'::text, pageview.visited_at)))
Sort Method: external merge Disk: 18864kB
-> Bitmap Heap Scan on pageview (cost=5073.60..81528.85 rows=270610 width=48) (actual time=111.950..1877.603 rows=294915 loops=1)
Recheck Cond: (visited_at > (('now'::cstring)::date - 31))
Rows Removed by Index Recheck: 338268
-> Bitmap Index Scan on visited_at_index (cost=0.00..5005.94 rows=270610 width=0) (actual time=109.874..109.874 rows=294915 loops=1)
Index Cond: (visited_at > (('now'::cstring)::date - 31))
Total runtime: 9687.460 ms

这是对我有用的调整:

1)索引b-tree(主要)2)铸造到目前为止(小差异)

10s仍然是回应用户的重要时刻。

所以我的解决方案是创建table month_users并使用一次

explain analyze select mes,count(usuario) as usuarios
from (
   SELECT distinct coalesce(usuario, ip) as usuario, (extract(year from visited_at::date), extract(month from visited_at::date))   AS mes
     FROM pageview 
     where visited_at::date > current_date - 31
     ) as usuarios
group by 1
order by 1

并使用

GroupAggregate (cost=201976.97..204126.56 rows=200 width=64) (actual time=9040.196..9102.098 rows=2 loops=1)
-> Sort (cost=201976.97..202692.83 rows=286345 width=64) (actual time=9035.624..9058.457 rows=88356 loops=1)
Sort Key: usuarios.mes
Sort Method: external merge Disk: 5704kB
-> Subquery Scan on usuarios (cost=149102.66..154491.53 rows=286345 width=64) (actual time=7511.231..8840.270 rows=88356 loops=1)
-> Unique (cost=149102.66..151628.08 rows=286345 width=48) (actual time=7511.229..8823.647 rows=88356 loops=1)
-> Sort (cost=149102.66..149944.47 rows=336722 width=48) (actual time=7511.227..8666.667 rows=287614 loops=1)
Sort Key: (COALESCE(pageview.usuario, (pageview.ip)::text)), (ROW(date_part('year'::text, ((pageview.visited_at)::date)::timestamp without time zone), date_part('month'::text, ((pageview.visited_at)::date)::timestamp without time zone)))
Sort Method: external merge Disk: 18408kB
-> Seq Scan on pageview (cost=0.00..97469.57 rows=336722 width=48) (actual time=0.018..1946.139 rows=287614 loops=1)
Filter: ((visited_at)::date > (('now'::cstring)::date - 31))
Rows Removed by Filter: 722937
Total runtime: 9108.644 ms

结果:

insert from month_users select mes,count(usuario) as usuarios
from (
   SELECT distinct coalesce(usuario, ip) as usuario, (extract(year from visited_at), extract(month from visited_at))   AS mes
     FROM pageview 
     ) as usuarios
group by 1
order by 1

现在可接受的结果!

最终解决方案仍然需要考虑如何定期更新表结果。

© www.soinside.com 2019 - 2024. All rights reserved.