Postgres JSONB timestamp query very slow compared to timestamp column query
我有一个包含170万条记录的Postgres 9.4.4数据库,其中以下信息存储在名为
1 2 3 4 5 | DATA: { "lastUpdated":"2016-12-26T12:09:43.901Z", "lastUpdatedTimestamp":"1482754183" } } |
实际的JSONB列存储了更多信息,但我省略了无关数据。由于这是遗留信息,因此无法更改数据格式。
我正在尝试有效地获取
1 2 | EXPLAIN analyze SELECT COUNT(*) FROM"accounts" WHERE data->>'lastUpdated' >= '2015-12-01T10:10:10Z'; |
这需要22秒:
1 2 3 4 5 6 | Aggregate (cost=843795.05..843795.06 ROWS=1 width=0) (actual TIME=22292.584..22292.584 ROWS=1 loops=1) -> Seq Scan ON accounts (cost=0.00..842317.05 ROWS=591201 width=0) (actual TIME=1.410..22142.046 ROWS=1773603 loops=1) FILTER: ((DATA ->> 'lastUpdated'::text) >= '2015-12-01T10:10:10Z'::text) Planning TIME: 1.234 ms Execution TIME: 22292.671 ms |
我尝试添加以下文本索引:
1 | CREATE INDEX accounts_last_updated ON accounts ((data->>'lastUpdated')); |
但查询仍然相当缓慢,超过17秒:
1 2 3 4 5 6 7 8 9 10 | Aggregate (cost=815548.64..815548.65 ROWS=1 width=0) (actual TIME=17172.844..17172.845 ROWS=1 loops=1) -> Bitmap Heap Scan ON accounts (cost=18942.24..814070.64 ROWS=591201 width=0) (actual TIME=1605.454..17036.081 ROWS=1773603 loops=1) Recheck Cond: ((DATA ->> 'lastUpdated'::text) >= '2015-12-01T10:10:10Z'::text) Heap Blocks: exact=28955 lossy=397518 -> Bitmap INDEX Scan ON accounts_last_updated (cost=0.00..18794.44 ROWS=591201 width=0) (actual TIME=1596.645..1596.645 ROWS=1773603 loops=1) INDEX Cond: ((DATA ->> 'lastUpdated'::text) >= '2015-12-01T10:10:10Z'::text) Planning TIME: 1.373 ms Execution TIME: 17172.974 ms |
我也尝试按照PostgreSQL上的JSON创建时间戳索引中的说明,尝试创建以下函数和索引:
1 2 3 4 5 6 7 | CREATE OR REPLACE FUNCTION text_to_timestamp(text) RETURNS TIMESTAMP AS $$SELECT to_timestamp($1, 'YYYY-MM-DD HH24:MI:SS.MS')::TIMESTAMP; $$ LANGUAGE SQL IMMUTABLE; CREATE INDEX accounts_last_updated ON accounts (text_to_timestamp(data->>'lastUpdated')); |
但是这并没有给我带来任何改进,实际上它更慢,查询时间超过24秒,而无索引版本需要22秒:
1 2 3 4 5 6 7 8 9 | EXPLAIN analyze SELECT COUNT(*) FROM"accounts" WHERE text_to_timestamp(data->>'lastUpdated') >= '2015-12-01T10:10:10Z'; Aggregate (cost=1287195.80..1287195.81 ROWS=1 width=0) (actual TIME=24143.150..24143.150 ROWS=1 loops=1) -> Seq Scan ON accounts (cost=0.00..1285717.79 ROWS=591201 width=0) (actual TIME=4.044..23971.723 ROWS=1773603 loops=1) FILTER: (text_to_timestamp((DATA ->> 'lastUpdated'::text)) >= '2015-12-01 10:10:10'::TIMESTAMP WITHOUT TIME zone) Planning TIME: 1.107 ms Execution TIME: 24143.183 ms |
在最后一个绝望的行为中,我决定添加另一个时间戳列并将其更新为包含与
1 2 3 | ALTER TABLE accounts ADD COLUMN updated_at TIMESTAMP; UPDATE accounts SET updated_at = text_to_timestamp(data->>'lastUpdated'); CREATE INDEX accounts_updated_at ON accounts(updated_at); |
这给了我迄今为止最好的表现:
1 2 3 4 5 6 7 8 9 | EXPLAIN analyze SELECT COUNT(*) FROM"accounts" WHERE updated_at >= '2015-12-01T10:10:10Z'; Aggregate (cost=54936.49..54936.50 ROWS=1 width=0) (actual TIME=676.955..676.955 ROWS=1 loops=1) -> INDEX ONLY Scan USING accounts_updated_at ON accounts (cost=0.43..50502.48 ROWS=1773603 width=0) (actual TIME=0.026..552.442 ROWS=1773603 loops=1) INDEX Cond: (updated_at >= '2015-12-01 10:10:10'::TIMESTAMP WITHOUT TIME zone) Heap Fetches: 0 Planning TIME: 4.643 ms Execution TIME: 678.962 ms |
但是,我非常希望避免添加另一个列,以提高一个查询的速度。
这给我留下了以下问题:是否有任何方法可以提高我的JSONB查询的性能,因此它可以像单个列查询一样高效(我使用
在你的第一次和第二次尝试中,大多数执行时间花费在索引重新检查或过滤上,这必须读取每个json字段索引命中,读取json是昂贵的。 如果索引命中几百行,查询将很快,但如果索引达到数千或数十万行 - 过滤/重新检查json字段将花费一些时间。 在第二次尝试中,另外使用另一个功能会使情况更糟。
JSON字段适用于存储数据,但不打算用于分析查询,如摘要,统计及其不良做法,以便在条件的情况下使用json对象,至少作为主要过滤条件,如您的情况。
最后一次沮丧你的行为是正确的方法:)
要提高查询性能,必须添加一个或多个具有键值的列,这些列将在where条件中使用最多。