【发布时间】:2017-08-15 13:44:51
【问题描述】:
我有一个 Postgres 9.4.4 数据库,其中包含 170 万条记录,以下信息存储在名为 data 的 JSONB 列中,位于名为 accounts 的表中:
data: {
"lastUpdated": "2016-12-26T12:09:43.901Z",
"lastUpdatedTimestamp": "1482754183"
}
}
实际的 JSONB 列存储了更多信息,但我省略了不相关的数据。无法更改数据格式,因为这是旧信息。
我正在尝试有效地获取 lastUpdated 值大于或等于某个参考时间的所有记录的计数(我将在以下示例中使用 2015-12-01T10:10:10Z):
explain analyze SELECT count(*) FROM "accounts"
WHERE data->>'lastUpdated' >= '2015-12-01T10:10:10Z';
这需要 22 秒:
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
我尝试添加以下文本索引:
CREATE INDEX accounts_last_updated ON accounts ((data->>'lastUpdated'));
但是查询还是比较慢,超过17秒:
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
我也尝试按照Create timestamp index from JSON on PostgreSQL 中的说明创建以下函数和索引:
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 秒:
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
在最后的绝望中,我决定添加另一个时间戳列并更新它以包含与 data->>'lastUpdated' 相同的值:
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);
这给了我迄今为止最好的表现:
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 查询的性能,以便它可以像单个列查询一样高效(我使用的最后一个查询 @987654336 @ 而不是 data->>'lastUpdated')?就目前而言,我使用data->>'lastUpdated' 查询JSONB 数据需要17 秒到24 秒,而查询updated_at 列只需要678 毫秒。 JSONB 查询会慢得多是没有意义的。我希望通过使用text_to_timestamp 函数可以提高性能,但事实并非如此(或者我做错了什么)。
【问题讨论】:
-
为了让查询使用 JSON 列上的索引,您的查询必须使用与索引完全相同的表达式,因此您应该使用
where text_to_timestamp(data->>'lastUpdated') > ...) -
我确实使用了与您建议的相同的查询,我只是错误地没有将它包含在我的原始问题中。我已更新问题以包含我在分析命令中使用的确切查询,该命令产生 24143.183 毫秒的执行时间。还有什么我想念的吗?
标签: postgresql jsonb