我有一个已经优化的Postgres查询,但我们在峰值负载下达到了100%的CPU使用率,所以我想看看在优化数据库交互时是否还有更多工作要做。它已经在连接中使用了两个仅索引扫描,所以我的问题是在Postgres方面还有很多工作要做。
该数据库是运行9.4.1的亚马逊托管的Postgres RDS db.m3.2xlarge实例(8个vCPU和30 GB内存),下面的结果来自CPU使用率低和连接最少的时间段(大约15个)。峰值使用量大约为300个并发连接,这就是我们最大化CPU(在所有事情上杀死性能)的时候。
这是查询和EXPLAIN:
查询:
EXPLAIN (ANALYZE, BUFFERS)
SELECT m.valdate, p.index_name, m.market_data_closing, m.available_date
FROM md.market_data_closing m
JOIN md.primitive p on (m.primitive_id = p.index_id)
where p.index_name = ?
order by valdate desc
;
输出:
Sort (cost=183.80..186.22 rows=967 width=44) (actual time=44.590..54.788 rows=11133 loops=1)
Sort Key: m.valdate
Sort Method: quicksort Memory: 1254kB
Buffers: shared hit=181
-> Nested Loop (cost=0.85..135.85 rows=967 width=44) (actual time=0.041..32.853 rows=11133 loops=1)
Buffers: shared hit=181
-> Index Only Scan using primitive_index_name_index_id_idx on primitive p (cost=0.29..4.30 rows=1 width=25) (actual time=0.018..0.019 rows=1 loops=1)
Index Cond: (index_name = '?'::text)
Heap Fetches: 0
Buffers: shared hit=3
-> Index Only Scan using market_data_closing_primitive_id_valdate_available_date_mar_idx on market_data_closing m (cost=0.56..109.22 rows=2233 width=27) (actual time=0.016..12.059 rows=11133 loops=1)
Index Cond: (primitive_id = p.index_id)
Heap Fetches: 42
Buffers: shared hit=178
Planning time: 0.261 ms
Execution time: 64.957 ms
以下是表格大小:
作为参考,这是表和索引的基础规范:
CREATE TABLE md.primitive(
index_id serial NOT NULL,
index_name text NOT NULL UNIQUE,
index_description text not NULL,
index_source_code text NOT NULL DEFAULT 'MAN',
index_source_spec json NOT NULL DEFAULT '{}',
frequency text NULL,
primitive_type text NULL,
is_maintained boolean NOT NULL default true,
create_dt timestamp NOT NULL,
create_user text NOT NULL,
update_dt timestamp not NULL,
update_user text not NULL,
PRIMARY KEY
(
index_id
)
) ;
CREATE INDEX ON md.primitive
(
index_name ASC,
index_id ASC
);
CREATE TABLE md.market_data_closing(
valdate timestamp NOT NULL,
primitive_id int references md.primitive,
market_data_closing decimal(28, 10) not NULL,
available_date timestamp NULL,
pricing_source text not NULL,
create_dt timestamp NOT NULL,
create_user text NOT NULL,
update_dt timestamp not NULL,
update_user text not NULL,
PRIMARY KEY
(
valdate,
primitive_id
)
) ;
CREATE INDEX ON md.market_data_closing
(
primitive_id ASC,
valdate DESC,
available_date DESC,
market_data_closing ASC
);
还有什么可以做的?
似乎嵌套循环占用了大量的时间,而原始表只返回一行。您可以通过执行以下操作来尝试消除嵌套循环:
SELECT m.valdate, m.market_data_closing, m.available_date
FROM md.market_data_closing m
WHERE m.primitive_id = (SELECT p.index_id
FROM md.primitive p
WHERE p.index_name = ?
OFFSET 0 -- probably not needed, try it)
ORDER BY valdate DESC;
这不会返回p.index_name,但可以通过将其选为const来轻松修复。
对于下一代阅读:问题似乎与索引有关
md.market_data_closing(
...
PRIMARY KEY
(
valdate,
primitive_id
)
这似乎是一个不正确的索引。应该:
md.market_data_closing(
...
PRIMARY KEY
(
primitive_id,
valdate
)
解释原因。这种查询:
...
JOIN md.primitive p on (m.primitive_id = p.index_id)
...
仅在primitive_id是第一个字段时才有效。也
order by validate
如果验证次之,则会更有效。为什么?
因为索引是B树结构。
(
valdate,
primitive_id
)
结果是
valdate1
primitive_id1
primitive_id2
primitive_id3
valdate2
primitive_id1
primitive_id2
使用这个树你不能有效地搜索primitive_id1但是
(
primitive_id,
valdate
)
结果是
primitive_id1
valdate1
valdate2
valdate3
primitive_id2
valdate1
valdate2
这对于通过primitive_id查找是有效的。此问题还有另一种解决方案:如果您不想更改索引,则在valdate上添加严格相等的条件。说'valdate = some_date',这将使您的索引生效。