首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >postgresql 9.6.4:大表上的时间戳范围查询需要花费很长时间

postgresql 9.6.4:大表上的时间戳范围查询需要花费很长时间
EN

Stack Overflow用户
提问于 2017-10-09 20:39:28
回答 1查看 964关注 0票数 2

我需要一些帮助来分析在包含83.660142万行的大型表上执行的查询的糟糕性能,根据系统负载的不同,这需要25分钟到一个多小时的时间来计算。

我创建了下表,该表由一个复合键和3个索引组成:

代码语言:javascript
复制
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批处理应用程序中,我正在执行如下查询:

代码语言:javascript
复制
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') 

用户id在运行时被确定(id的数量在95.000到110.000之间)。对于每个用户,我需要提取当前和最后三个工作日的页面视图。查询总是返回3-4百万行之间的行。

使用EXPLAIN ANALYZE选项执行查询将返回以下执行计划。

代码语言:javascript
复制
Nested Loop  (cost=1483.40..1246386.43 rows=3761735 width=70) (actual time=108.856..1465501.596 rows=3643240 loops=1)
   ->  HashAggregate  (cost=1442.38..1444.38 rows=200 width=4) (actual time=33.277..201.819 rows=96159 loops=1)
     Group Key: "*VALUES*".column1
     ->  Values Scan on "*VALUES*"  (cost=0.00..1201.99 rows=96159 width=4) (actual time=0.006..11.599 rows=96159 loops=1)
   ->  Bitmap Heap Scan on records  (cost=41.02..6224.01 rows=70 width=70) (actual time=8.865..15.218 rows=38 loops=96159)
     Recheck Cond: (userid = "*VALUES*".column1)
     Filter: (((ts < '2017-09-02 00:00:00'::timestamp without time zone) AND (ts >= '2017-09-01 00:00:00'::timestamp without time zone)) OR ((ts < '2017-08-26 00:00:00'::timestamp without time zone) AND (ts >= '2017-08-25 00:00:00'::timestamp without time zone)) OR ((ts < '2017-08-19 00:00:00'::timestamp without time zone) AND (ts >= '2017-08-18 00:00:00'::timestamp without time zone)) OR ((ts < '2017-08-12 00:00:00'::timestamp without time zone) AND (ts >= '2017-08-11 00:00:00'::timestamp without time zone)))
     Rows Removed by Filter: 792
     Heap Blocks: exact=77251145
     ->  Bitmap Index Scan on userid_ts_idx  (cost=0.00..41.00 rows=1660 width=0) (actual time=6.593..6.593 rows=830 loops=96159)
           Index Cond: (userid = "*VALUES*".column1)

我调整了一些Postgres调优参数的值(不幸的是,没有成功):

  • effective_cache_size=15GB (可能没用,因为只执行一次查询)
  • shared_buffers=15GB
  • work_mem=3GB

该应用程序运行计算成本昂贵的任务(例如数据融合/数据注入)并消耗大约100 OS的内存,因此系统硬件有125 OS和16核(OS: Debian)。

我想知道为什么postgres没有在其执行计划中使用组合索引userid_ts_idx?由于索引中的时间戳列是按反向顺序排序的,因此我希望postgres使用它为查询的范围部分查找匹配的元组,因为它可以依次遍历索引,直到条件ts < '2017-09-02 00:00:00保持为真,并返回所有值,直到满足条件ts >= 2017-09-01 00:00:00为止。相反,postgres使用昂贵的Bitmap堆扫描,如果我正确理解,它会进行线性表扫描。我是配置错了db设置,还是有概念上的误解?

更新

遗憾的是,正如评论中所建议的那样,并没有带来任何改进。位图堆扫描已被Sequantial扫描取代,但性能仍然很差。以下是更新的执行计划:

代码语言:javascript
复制
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
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2017-10-10 06:43:36

如果要将时间戳转换为日期并按值列表进行筛选,则应该得到不同的计划。

代码语言:javascript
复制
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将继续使用该索引,即使日期值覆盖整个表(因此seq扫描会更好)。

演示

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/46654608

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档