如果表中有超过3000万个条目,则选择计数需要19分钟才能执行

时间:2016-07-07 09:20:01

标签: sql performance postgresql query-optimization

我在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!很大的进步。

2 个答案:

答案 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表。

我建议您仔细阅读Postgres Slow CountingPostgres Count Estimate

答案 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

你会感觉到正确的方式。