我们正在从时间序列数据库(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());
答案 0 :(得分:0)
您是否真的计划了year 2038 问题?为什么不像标准UNIX那样只使用int?
答案 1 :(得分:0)
您的时间序列数据库,如果它像我检查过的竞争对手那样工作,则会按照“时间”列的顺序自动将数据存储在类似堆的结构中。 Postgres 不。因此,您正在进行O(n)搜索[n =表中的行数]:必须读取整个表以查找与您的时间过滤器匹配的行。时间戳上的主键(创建唯一索引),或者,如果时间戳不是唯一的,则常规索引将为您提供单个记录的二进制O(log n)搜索,并提高检索小于约5%的所有查询的性能桌子。 Postgres将估计索引扫描或全表扫描更好的交叉点。
您可能还希望CLUSTER
(PG 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;