当UTC毫秒存储为bigint时,postgresql中的查询时间较慢

时间:2013-03-13 15:32:31

标签: performance postgresql time utc

我们正在从时间序列数据库(ECHO历史数据库)迁移到开源数据库,主要是因为价格因素。我们的选择是PostgreSQL,因为没有开源时间序列数据库。我们过去在ECHO中存储的只是时间和价值对。 现在这是问题所在。我在postgre中创建的表包含2列。首先是“bigint”类型以UTC毫秒(13位数字)存储时间,第二个是数据类型设置为“real”类型的值。我已经填满了大约360万行(在30天的时间范围内传播)数据,当我查询一个小的时间范围(比如1天)时,查询需要4秒但是在ECHO的相同时间范围内响应时间是150毫秒! 这是一个巨大的差异。时间紧迫似乎是缓慢但不确定的原因。您能否建议如何改进查询时间。 我还读到了使用数据类型“timestamp”和“timestamptz”,看起来我们需要将日期和时间存储为常规格式而不是UTC秒。这有助于加快查询时间吗?

这是我的表定义:

            Table "public. MFC2 Flow_LCL "
Column  |  Type  | Modifiers | Storage | Stats target | Description  
----------+--------+-----------+---------+--------------+-------------

 the_time | bigint |           | plain   |              |
 value    | real   |           | plain   |              |

Indexes:
"MFC2 Flow_LCL _time_idx" btree (the_time)

Has OIDs: no

目前我以UTC毫秒(使用bigint)存储时间。这里的挑战是可能存在重复的时间值对。

这是我正在使用的查询(通过简单的API调用,它将传递表名,开始和结束时间)

PGresult *res;

int rec_count;
std::string sSQL;

sSQL.append("SELECT * FROM ");
sSQL.append(" \" ");
sSQL.append(table);
sSQL.append(" \" ");
sSQL.append(" WHERE");
sSQL.append(" time >= ");
CString sTime;
sTime.Format("%I64d",startTime);
sSQL.append(sTime);
sSQL.append(" AND time <= ");
CString eTime;
eTime.Format("%I64d",endTime);
sSQL.append(eTime);
sSQL.append(" ORDER BY time ");

res = PQexec(conn, sSQL.c_str());

3 个答案:

答案 0 :(得分:0)

您是否真的计划了year 2038 问题?为什么不像标准UNIX那样只使用int?

答案 1 :(得分:0)

您的时间序列数据库,如果它像我检查过的竞争对手那样工作,则会按照“时间”列的顺序自动将数据存储在类似堆的结构中。 Postgres 。因此,您正在进行O(n)搜索[n =表中的行数]:必须读取整个表以查找与您的时间过滤器匹配的行。时间戳上的主键(创建唯一索引),或者,如果时间戳不是唯一的,则常规索引将为您提供单个记录的二进制O(log n)搜索,并提高检索小于约5%的所有查询的性能桌子。 Postgres将估计索引扫描或全表扫描更好的交叉点。

您可能还希望CLUSTERPG Docs)该索引上的表格。

另外,请遵循上面的建议,不要将time或其他SQL保留字用作列名。即使它是合法的,它也会遇到麻烦。

[作为评论会更好,但对此来说太长了。]

答案 2 :(得分:0)

SET search_path=tmp;

  -- -------------------------------------------
  -- create table and populate it with 10M rows
  -- -------------------------------------------
DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;

SET search_path=tmp;

CREATE TABLE old_echo
        ( the_time timestamp NOT NULL PRIMARY KEY
        , payload DOUBLE PRECISION NOT NULL
        );

INSERT INTO old_echo (the_time, payload)
SELECT now() - (gs * interval '1 msec')
        , random()
FROM generate_series(1,10000000) gs
        ;

-- DELETE FROM old_echo WHERE random() < 0.8;

VACUUM ANALYZE old_echo;

SELECT MIN(the_time) AS first
        , MAX(the_time) AS last
        , (MAX(the_time) - MIN(the_time))::interval AS width
FROM old_echo
        ;

EXPLAIN ANALYZE
SELECT *
FROM old_echo  oe
JOIN (
        SELECT MIN(the_time) AS first
        , MAX(the_time) AS last
        , (MAX(the_time) - MIN(the_time))::interval AS width
        , ((MAX(the_time) - MIN(the_time))/2)::interval AS half
        FROM old_echo
        ) mima ON 1=1
WHERE oe.the_time >= mima.first + mima.half
AND  oe.the_time < mima.first + mima.half + '1 sec':: interval
        ;

结果:

                                                                               QUERY PLAN                                                                                
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Nested Loop  (cost=0.06..59433.67 rows=1111124 width=64) (actual time=0.101..1.307 rows=1000 loops=1)
   ->  Result  (cost=0.06..0.07 rows=1 width=0) (actual time=0.049..0.050 rows=1 loops=1)
         InitPlan 1 (returns $0)
           ->  Limit  (cost=0.00..0.03 rows=1 width=8) (actual time=0.022..0.022 rows=1 loops=1)
                 ->  Index Scan using old_echo_pkey on old_echo  (cost=0.00..284873.62 rows=10000115 width=8) (actual time=0.021..0.021 rows=1 loops=1)
                       Index Cond: (the_time IS NOT NULL)
         InitPlan 2 (returns $1)
           ->  Limit  (cost=0.00..0.03 rows=1 width=8) (actual time=0.009..0.010 rows=1 loops=1)
                 ->  Index Scan Backward using old_echo_pkey on old_echo  (cost=0.00..284873.62 rows=10000115 width=8) (actual time=0.009..0.009 rows=1 loops=1)
                       Index Cond: (the_time IS NOT NULL)
   ->  Index Scan using old_echo_pkey on old_echo oe  (cost=0.01..34433.30 rows=1111124 width=16) (actual time=0.042..0.764 rows=1000 loops=1)
         Index Cond: ((the_time >= (($0) + ((($1 - $0) / 2::double precision)))) AND (the_time < ((($0) + ((($1 - $0) / 2::double precision))) + '00:00:01'::interval)))
 Total runtime: 1.504 ms
(13 rows)

更新:由于时间戳似乎是非唯一的(顺便说一句:在这种情况下,重复意味着什么?)我添加了一个额外的键列。一个丑陋的黑客,但它在这里工作。查询时间为11毫秒,10M -80%行。 (行数达到210/222067):

CREATE TABLE old_echo
        ( the_time timestamp NOT NULL
        , the_seq SERIAL NOT NULL -- to catch the duplicate keys
        , payload DOUBLE PRECISION NOT NULL
        ,       PRIMARY KEY(the_time, the_seq)
        );

    -- Adding the random will cause some timestamps to be non-unique.
    -- (and others to be non-existent)
INSERT INTO old_echo (the_time, payload)
SELECT now() - ((gs+random()*1000::integer) * interval '1 msec')
        , random()
FROM generate_series(1,10000000) gs
        ;

DELETE FROM old_echo WHERE random() < 0.8;