我在Windows 7 64位操作系统上使用PostgreSQL 9.4.8 32位。
我在3个2T的磁盘上使用RAID 5。 CPU是Xeon E3-1225v3,带有8G RAM。
在表格中,我插入了超过3000万条目(我想要达到5000万条)。
在此表上执行选择计数(*)需要超过19分钟。再次执行此查询会将其减少到14分钟,但仍然很慢。索引似乎没有做任何事情。
我的postgresql.conf在文件末尾设置如下:
max_connections = 100
shared_buffers = 512MB
effective_cache_size = 6GB
work_mem = 13107kB
maintenance_work_mem = 512MB
checkpoint_segments = 32
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 100
random_page_cost = 1.2
以下是此表的架构:
CREATE TABLE recorder.records
(
recorder_id smallint NOT NULL DEFAULT 200,
rec_start timestamp with time zone NOT NULL,
rec_end timestamp with time zone NOT NULL,
deleted boolean NOT NULL DEFAULT false,
channel_number smallint NOT NULL,
channel_name text,
from_id text,
from_name text,
to_id text,
to_name text,
type character varying(32),
hash character varying(128),
codec character varying(16),
id uuid NOT NULL,
status smallint,
duration interval,
CONSTRAINT records_pkey PRIMARY KEY (id)
)
WITH (
OIDS=FALSE
)
CREATE INDEX "idxRecordChanName"
ON recorder.records
USING btree
(channel_name COLLATE pg_catalog."default");
CREATE INDEX "idxRecordChanNumber"
ON recorder.records
USING btree
(channel_number);
CREATE INDEX "idxRecordEnd"
ON recorder.records
USING btree
(rec_end);
CREATE INDEX "idxRecordFromId"
ON recorder.records
USING btree
(from_id COLLATE pg_catalog."default");
CREATE INDEX "idxRecordStart"
ON recorder.records
USING btree
(rec_start);
CREATE INDEX "idxRecordToId"
ON recorder.records
USING btree
(to_id COLLATE pg_catalog."default");
CREATE INDEX "idxRecordsStart"
ON recorder.records
USING btree
(rec_start);
CREATE TRIGGER trig_update_duration
AFTER INSERT
ON recorder.records
FOR EACH ROW
EXECUTE PROCEDURE recorder.fct_update_duration();
我的查询是这样的:
select count(*) from recorder.records as rec where rec.rec_start < '2016-01-01' and channel_number != 42;
解释此查询的分析:
Aggregate (cost=1250451.14..1250451.15 rows=1 width=0) (actual time=956017.494..956017.494 rows=1 loops=1)
-> Seq Scan on records rec (cost=0.00..1195534.66 rows=21966592 width=0) (actual time=34.581..950947.593 rows=23903295 loops=1)
Filter: ((rec_start < '2016-01-01 00:00:00-06'::timestamp with time zone) AND (channel_number <> 42))
Rows Removed by Filter: 7377886
Planning time: 0.348 ms
Execution time: 956017.586 ms
现在一样,但是通过禁用seqscan:
Aggregate (cost=1456272.87..1456272.88 rows=1 width=0) (actual time=929963.288..929963.288 rows=1 loops=1)
-> Bitmap Heap Scan on records rec (cost=284158.85..1401356.39 rows=21966592 width=0) (actual time=118685.228..925629.113 rows=23903295 loops=1)
Recheck Cond: (rec_start < '2016-01-01 00:00:00-06'::timestamp with time zone)
Rows Removed by Index Recheck: 2798893
Filter: (channel_number <> 42)
Rows Removed by Filter: 612740
Heap Blocks: exact=134863 lossy=526743
-> Bitmap Index Scan on "idxRecordStart" (cost=0.00..278667.20 rows=22542169 width=0) (actual time=118628.930..118628.930 rows=24516035 loops=1)
Index Cond: (rec_start < '2016-01-01 00:00:00-06'::timestamp with time zone)
Planning time: 0.279 ms
Execution time: 929965.547 ms
如何更快地进行此类查询?
已添加: 我使用rec_start和channel_number创建了一个索引,经过57分钟的真空分析后,查询现在在3分钟内完成:
CREATE INDEX "plopLindex"
ON recorder.records
USING btree
(rec_start, channel_number);
解释相同查询的缓冲区:
explain (analyse, buffers, verbose) select count(*) from recorder.records as rec where rec.rec_start < '2016-01-01' and channel_number != 42;
Aggregate (cost=875328.61..875328.62 rows=1 width=0) (actual time=199610.874..199610.874 rows=1 loops=1)
Output: count(*)
Buffers: shared hit=69490 read=550462 dirtied=75118 written=51880"
-> Index Only Scan using "plopLindex" on recorder.records rec (cost=0.56..814734.15 rows=24237783 width=0) (actual time=66.115..197609.019 rows=23903295 loops=1)
Output: rec_start, channel_number
Index Cond: (rec.rec_start < '2016-01-01 00:00:00-06'::timestamp with time zone)
Filter: (rec.channel_number <> 42)
Rows Removed by Filter: 612740
Heap Fetches: 5364345
Buffers: shared hit=69490 read=550462 dirtied=75118 written=51880
Planning time: 12.416 ms
Execution time: 199610.988 ms
然后第二次执行此查询(无需解释):11secs!很大的进步。
答案 0 :(得分:2)
查看你的行数,这对我来说听起来不正常,并且在其他RDBMS上也是如此。
你有太多的行来快速获得结果,因为你有一个WHERE
子句,快速获取行数的唯一解决方案是创建特定的表来跟踪它,填充了TRIGGER
INSERT
或批量作业。
TRIGGER
解决方案100%准确但更加密集,批处理解决方案近似但更灵活,您提高批处理作业频率越多,您的统计数据就越准确;
在您的情况下,我会选择第二个解决方案并创建一个或多个聚合表。
例如,您可以使用批处理作业来计算按日期和渠道分组的所有行
针对此特定需求的聚合表的示例是
CREATE TABLE agr_table (AGR_TYPE CHAR(50), AGR_DATE DATE, AGR_CHAN SMALLINT, AGR_CNT INT)
您的批处理工作可以:
DELETE FROM agr_table WHERE AGR_TYPE='group_by_date_and_channel';
INSERT INTO agr_table
SELECT 'group_by_date_and_channel', rec_start, channel_number, count(*) as cnt
FROM recorder.records
GROUP BY rec_start, channel_number
;
然后你可以通过以下方式快速检索统计数据:
SELECT SUM(cnt)
FROM agr_table
WHERE AGR_DATE < '2016-01-01' and AGR_CHAN != 42
这当然是一个非常简单的例子。您应该根据需要快速检索的统计数据来设计您的agregation表。
答案 1 :(得分:1)
是的,您已经创建了正确的索引来覆盖您的查询参数。托马斯G还建议你一个很好的解决方法。我完全同意。
但是我还想与你分享另一件事:第二次运行仅需11秒(与第一次运行相比3分钟),这听起来你正面临一个&#34;缓存问题&#34;。
当你运行第一次执行时,postgres将磁盘表页从磁盘移植到RAM,当你进行第二次运行时,它所需要的一切都在内存中,并且只运行了11秒。
我过去常常遇到完全相同的问题而且我的#34;最好的&#34;解决方案只是给postgres更多shared_buffers
。我不依赖于操作系统的文件缓存。我保留了大部分内存,我可以使用postgres。但是,在Windows中做一个简单的改变就是痛苦。您有操作系统限制和Windows&#34;废物&#34;太多的记忆力来自我运行。真遗憾。
相信我......你不必改变你的硬件添加更多内存(无论哪种方式,添加更多内存总是好事!)。最有效的改变是改变您的操作系统。如果你有一个专门的&#34;服务器,为什么浪费如此珍贵的内存与视频/声音/驱动程序/服务/ AV /等...那些你不会(或不会被使用过)的东西?
转到Linux操作系统(也许是Ubuntu服务器?)并在完全相同的硬件上获得更多性能。
将kernel.shmmax更改为更大的值:
sysctl -w kernel.shmmax=14294967296
echo kernel.shmmax = 14294967296 >>/etc/sysctl.conf
然后你可以将postgresql.conf更改为:
shared_buffers = 6GB
effective_cache_size = 7GB
work_mem = 128MB
你会感觉到正确的方式。