我有一个表,用于存储有关客户,时间戳和事件的时间范围的信息。
我使用的索引如下:
event_index
(customer_id,时间)
state_index
(customer_id,结束,开始desc)
绝大多数查询查询最近几天的状态和事件。
这是一个示例查询文本(events
具有与我将为states
描述的相同的相同问题):
SELECT "states".*
FROM "states"
WHERE ("states"."customer_id" = $1 AND "states"."start" < $2)
AND ("states"."end" IS NULL OR "states"."end" > $3)
AND ("states"."obsolete" = $4)
ORDER BY "states"."start" DESC
我看到有时查询计划者仅使用customer_id进行过滤,然后使用堆进行过滤以扫描客户的所有行:
Sort (cost=103089.00..103096.17 rows=2869 width=78)
Sort Key: start DESC
-> Bitmap Heap Scan on states (cost=1222.56..102924.23 rows=2869 width=78)
Recheck Cond: (customer_id = '----'::bpchar)
Filter: ((NOT obsolete) AND ((start)::double precision < '1557711009'::double precision) AND ((end IS NULL) OR ((end)::double precision > '1557666000'::double precision)))
-> Bitmap Index Scan on states_index (cost=0.00..1221.85 rows=26820 width=0)
Index Cond: (customer_id = '----'::bpchar)
这与我在手动会话中看到的相反:
Sort Key: start DESC
Sort Method: quicksort Memory: 25kB
-> Bitmap Heap Scan on states (cost=111.12..9338.04 rows=1 width=78) (actual time=141.674..141.674 rows=0 loops=1)
Recheck Cond: (((customer_id = '-----'::bpchar) AND (end IS NULL) AND (start < '1557349200'::numeric)) OR ((customer_id = '----'::bpchar) AND (end > '1557249200'::numeric) AND (start < '1557349200'::numeric)))
Filter: ((NOT obsolete) AND ((title)::text = '---'::text))
Rows Removed by Filter: 112
Heap Blocks: exact=101
-> BitmapOr (cost=111.12..111.12 rows=2333 width=0) (actual time=4.198..4.198 rows=0 loops=1)
-> Bitmap Index Scan on states_index (cost=0.00..4.57 rows=1 width=0) (actual time=0.086..0.086 rows=0 loops=1)
Index Cond: ((customer_id = '----'::bpchar) AND (end IS NULL) AND (start < '1557349200'::numeric))
-> Bitmap Index Scan on state_index (cost=0.00..106.55 rows=2332 width=0) (actual time=4.109..4.109 rows=112 loops=1)
Index Cond: ((customer_id = '---'::bpchar) AND (end > '1557262800'::numeric) AND (start < '1557349200'::numeric))
换句话说,查询计划者有时选择仅使用 索引的第一列,这会大大降低查询速度。
我可以理解为什么只有当客户数据足够小并存储在内存中时才带走整个客户数据才有意义,但是问题是该数据非常稀疏并且可能没有完全缓存(一年前的数据可能没有为客户缓存的数据库为数百GB)。如果索引将最大程度地使用时间戳(如第二个示例中所示),则由于缓存了最近的数据,因此结果应该更快。
我在上周使用了部分索引,以查看查询时间是否减少,但postgres有时仅使用它。这解决了使用局部索引时的问题,因为该索引中不存在旧行-但是遗憾的是,postgres仍然选择了较大的索引,即使它不必这样做。我运行了vacuum analyze
,但效果不明显。
我尝试使用以下方法查看缓存命中数:
Database Name | Temporary files | Size of temporary files | Block Hits | Block Reads
------------------+-----------------+-------------------------+---------------+-------------
customers | 1922 | 18784440622 | 69553504584 | 2401546773
然后我计算了(block_hits/(block_hits + block_reads))
:
>>> 69553504584.0 / (69553504584.0 + 2401546773.0)
0.9666243477322406
所以这显示了〜96.6%的缓存(我希望它接近100,因为我知道查询的性质)
我还尝试增加customer_id
,start
和end
的统计信息(SET STATISTICS),因为这似乎是面临查询计划者问题的人们的建议。它也没有帮助(我在...之后进行分析)。
进一步阅读此问题后,我发现有一种方法可以使查询计划者更喜欢使用比默认值(4)低的random_page_cost
进行索引扫描。我还在这里看到了一篇支持这篇文章的文章:
这对我的用例有意义吗?是否会使查询计划者更频繁地(最好总是)充分利用索引?
如果没有,我还有其他方法可以减少查询时间吗?我知道分区可能非常有效,但似乎有点过头了,据我所阅读的内容来看,当前的最新postgres版本(9.5.9)并未完全支持分区。
更新:降低random_page_cost
后,我看不出任何决定性的区别。有时候查询计划者选择只使用其中的一部分。索引会导致结果慢得多。
任何建议都非常欢迎。
谢谢:)