Database postgresql 9.6.4:大表上的时间戳范围查询永远耗时
我需要一些帮助来分析在一个包含83660.142万行的大表上执行的查询的不良性能,根据系统负载的不同,该查询需要25分钟到一个多小时的计算时间 我创建了下表,该表由一个复合键和3个索引组成:Database postgresql 9.6.4:大表上的时间戳范围查询永远耗时,database,postgresql,database-performance,Database,Postgresql,Database Performance,我需要一些帮助来分析在一个包含83660.142万行的大表上执行的查询的不良性能,根据系统负载的不同,该查询需要25分钟到一个多小时的计算时间 我创建了下表,该表由一个复合键和3个索引组成: CREATE TABLE IF NOT EXISTS ds1records( userid INT DEFAULT 0, clientid VARCHAR(255) DEFAULT '', ts TIMESTAMP, site VARCHAR(50) DEFAULT '', code VARCHA
CREATE TABLE IF NOT EXISTS ds1records(
userid INT DEFAULT 0,
clientid VARCHAR(255) DEFAULT '',
ts TIMESTAMP,
site VARCHAR(50) DEFAULT '',
code VARCHAR(400) DEFAULT '');
CREATE UNIQUE INDEX IF NOT EXISTS primary_idx ON records (userid, clientid, ts, site, code);
CREATE INDEX IF NOT EXISTS userid_idx ON records (userid);
CREATE INDEX IF NOT EXISTS ts_idx ON records (ts);
CREATE INDEX IF NOT EXISTS userid_ts_idx ON records (userid ASC,ts DESC);
在spring批处理应用程序中,我执行的查询如下所示:
SELECT *
FROM records
WHERE userid = ANY(VALUES (2), ..., (96158 more userids) )
AND ( ts < '2017-09-02' AND ts >= '2017-09-01'
OR ts < '2017-08-26' AND ts >= '2017-08-25'
OR ts < '2017-08-19' AND ts >= '2017-08-18'
OR ts < '2017-08-12' AND ts >= '2017-08-11')
我已经调整了一些Postgres调优参数的值(不幸的是没有成功):
- 有效_缓存_大小=15GB(可能没用,因为查询只执行一次)
- 共享缓冲区=15GB
- 工作记忆=3GB
userid\u ts\u idx
?由于索引中的时间戳列按相反顺序排序,我希望postgres使用它来查找查询范围部分的匹配元组,因为它可以依次遍历索引,直到条件ts<'2017-09-02 00:00:00
保持为真,并返回所有值,直到条件ts>=2017-09-01满足00:00:00
。相反,postgres使用昂贵的位图堆扫描,如果我理解正确,它会执行线性表扫描。我是否误解了db设置,或者我是否有概念上的误解
更新
不幸的是,评论中建议的CTE没有带来任何改进。位图堆扫描已被顺序扫描取代,但性能仍然很差。以下是更新后的执行计划:
Merge Join (cost=20564929.37..20575876.60 rows=685277 width=106) (actual time=2218133.229..2222280.192 rows=3907472 loops=1)
Merge Cond: (ids.id = r.userid)
Buffers: shared hit=2408684 read=181785
CTE ids
-> Values Scan on "*VALUES*" (cost=0.00..1289.70 rows=103176 width=4) (actual time=0.002..28.670 rows=103176 loops=1)
CTE ts
-> Values Scan on "*VALUES*_1" (cost=0.00..0.05 rows=4 width=32) (actual time=0.002..0.004 rows=4 loops=1)
-> Sort (cost=10655.37..10913.31 rows=103176 width=4) (actual time=68.476..83.312 rows=103176 loops=1)
Sort Key: ids.id
Sort Method: quicksort Memory: 7909kB
-> CTE Scan on ids (cost=0.00..2063.52 rows=103176 width=4) (actual time=0.007..47.868 rows=103176 loops=1)
-> Sort (cost=20552984.25..20554773.54 rows=715717 width=102) (actual time=2218059.941..2221230.585 rows=8085760 loops=1)
Sort Key: r.userid
Sort Method: quicksort Memory: 1410084kB
Buffers: shared hit=2408684 read=181785
-> Nested Loop (cost=0.00..20483384.24 rows=715717 width=102) (actual time=885849.043..2214665.723 rows=8085767 loops=1)
Join Filter: (ts.r @> r.ts)
Rows Removed by Join Filter: 707630821
Buffers: shared hit=2408684 read=181785
-> Seq Scan on records r (cost=0.00..4379760.52 rows=178929152 width=70) (actual time=0.024..645616.135 rows=178929147 loops=1)
Buffers: shared hit=2408684 read=181785
-> CTE Scan on ts (cost=0.00..0.08 rows=4 width=32) (actual time=0.000..0.000 rows=4 loops=178929147)
Planning time: 126.110 ms
Execution time: 2222514.566 ms
若您将时间戳转换为日期并按值列表过滤,那个么您应该得到不同的计划
CREATE INDEX IF NOT EXISTS userid_ts_idx ON records (userid ASC,cast(ts AS date) DESC);
SELECT *
FROM records
WHERE userid = ANY(VALUES (2), ..., (96158 more userids) )
AND cast(ts AS date) IN('2017-09-01','2017-08-25','2017-08-18','2017-08-11');
它是否会更好地执行取决于您的数据和日期范围,因为在我的案例中,我发现Postgres将继续使用该索引,即使日期值覆盖了整个表(所以序列扫描会更好)
它是否有可能受到磁盘读取的限制?下次请使用
解释(分析、缓冲)
。这将使您对缓冲有一些了解。正如我们所看到的,堆扫描占用了大部分时间。在这组日期范围内有几个OR,我认为它不会保持为真
最晚的日期向后移动到最早的给定日期。过滤器指示它处理每个范围(…或…)和(…或…)和(…或…)以及(…或…)和(…或…)
几个快速点。你真的是说83660142万行吗?与83x10^12行相同?还是只有8300万行?差别很大。第二,如果您的返回集很大,并且得到任何类型的排序等,它将溢出到磁盘。默认情况下,溢出到存储数据的同一磁盘。将临时表空间移到其他地方会有很大的不同,更大的work\u mem(在一定程度上)是否有机会将Any(值(2),…,(96158个用户ID))
放入子查询中?同样,如果没有正确的括号,TS过滤器和运算符将完全不执行任何操作。。。它应该是和((ts=…)或(ts=…)…)
类似的内容:将时间戳转换为日期不是一个选项,因为数据是基于小时粒度的。这个查询将过滤掉大量的结果集。那么你真的这样做了吗<代码>ts<'2017-08-12 05:00:00'和ts>='2017-08-11 13:00:00'对不起,我不知道('2017-09-01'、'2017-08-25'、'2017-08-18'、'2017-08-11')中的此部分在范围内过滤。我将测试你的解决方案。这只是因为时间戳在这种情况下被转换为日期,所以基本上它去掉了时间部分,只比较日期。卢卡斯你是个天才!!!它在大约11分钟内执行了查询,是的:),正如您所说,它现在正在运行索引扫描!非常感谢你
CREATE INDEX IF NOT EXISTS userid_ts_idx ON records (userid ASC,cast(ts AS date) DESC);
SELECT *
FROM records
WHERE userid = ANY(VALUES (2), ..., (96158 more userids) )
AND cast(ts AS date) IN('2017-09-01','2017-08-25','2017-08-18','2017-08-11');