Postgresql 建索引性能优化

事件背景

客户新上线了一套监控系统,可以监控到所有的执行慢的SQL,监控到有个批量任务导入大量数据后,进行索引创建。耗时需要几分钟,虽然不影响业务,但是需要整改。

这种问题的处理思路,都是大拆小,搞并发。

准备工作

在测试环境,创建一个大表进行测试,创建大量的假数据。

创建表

CREATE TABLE TABTEST (
    logid bigint,
    name varchar(255),
    log varchar(1024),
    logdate timestamp
);

随机字符串生成函数

CREATE OR REPLACE FUNCTION public.random_string(i integer)
  RETURNS text
  LANGUAGE sql
AS $function$
    select array_to_string(array(select substring('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' FROM (ceil(random()*62))::int FOR 1) FROM generate_series(1, $1)), '');
$function$
;

生成大量的数据

INSERT INTO TABTEST SELECT GENERATE_SERIES(0, 100000000, 1), random_string(50), random_string(100), now();

经测试发现这种方法创建数据太慢了,改成使用COPY的方式创建数据。
排查发现random_string效率太低,生成一条数据接近1ms

重新创建表

CREATE SEQUENCE tabtest_seq START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;

CREATE TABLE TABTEST (
    logid bigint default nextval('tabtest_seq'::regclass),
    name varchar(255),
    log varchar(1024),
    logdate timestamp default now()
);

写程序创建8600万条数据放在test.csv中
导入大量数据

COPY TABTEST(name, log) FROM '/tmp/test.csv' WITH csv;

测试基准数据

\timing 开启计时

CREATE INDEX idx_test ON public.TABTEST USING BTREE(NAME ASC NULLS LAST);

耗时532824.434 ms

尝试一:并发创建索引

set max_parallel_maintenance_workers to 8;
set max_parallel_workers to 8;
set max_worker_processes to 8; //需要重启
set maintenance_work_mem to '16GB';
alter table public.TABTEST set(parallel_workers=8);

耗时385838.893 ms,提升 38%的性能,非常不错,但是远远不够。
仍然会出发告警。

尝试二:表分区并发创建索引

重新创建表

CREATE TABLE TABTEST (
    logid bigint default nextval('tabtest_seq'::regclass),
    name varchar(255),
    log varchar(1024),
    logdate timestamp default now()
) partition by hash(name);

创建分区

CREATE TABLE TABTEST_00 PARTITION OF TABTEST FOR VALUES WITH (MODULUS 10, REMAINDER 0);
CREATE TABLE TABTEST_01 PARTITION OF TABTEST FOR VALUES WITH (MODULUS 10, REMAINDER 1);
CREATE TABLE TABTEST_02 PARTITION OF TABTEST FOR VALUES WITH (MODULUS 10, REMAINDER 2);
CREATE TABLE TABTEST_03 PARTITION OF TABTEST FOR VALUES WITH (MODULUS 10, REMAINDER 3);
CREATE TABLE TABTEST_04 PARTITION OF TABTEST FOR VALUES WITH (MODULUS 10, REMAINDER 4);
CREATE TABLE TABTEST_05 PARTITION OF TABTEST FOR VALUES WITH (MODULUS 10, REMAINDER 5);
CREATE TABLE TABTEST_06 PARTITION OF TABTEST FOR VALUES WITH (MODULUS 10, REMAINDER 6);
CREATE TABLE TABTEST_07 PARTITION OF TABTEST FOR VALUES WITH (MODULUS 10, REMAINDER 7);
CREATE TABLE TABTEST_08 PARTITION OF TABTEST FOR VALUES WITH (MODULUS 10, REMAINDER 8);
CREATE TABLE TABTEST_09 PARTITION OF TABTEST FOR VALUES WITH (MODULUS 10, REMAINDER 9);

并发创建INDEX,并记录每个分区索引创建的开始时间和结束时间;
耗时 = 最大结束时间 - 最小开始时间 = 137 s,速度提升接近4倍。

顺序创建INDEX,并记录每个分区索引创建的开始时间和结束时间;
耗时 = 每个索引的耗时相加 = 457358.14 ms,速度提升 16.5%

顺序创建INDEX,优化并发

set max_parallel_maintenance_workers to 8;
set max_parallel_workers to 8;
set max_worker_processes to 8; //需要重启
set maintenance_work_mem to '16GB';
alter table public.TABTEST_00 set(parallel_workers=8);
alter table public.TABTEST_01 set(parallel_workers=8);
alter table public.TABTEST_02 set(parallel_workers=8);
alter table public.TABTEST_03 set(parallel_workers=8);
alter table public.TABTEST_04 set(parallel_workers=8);
alter table public.TABTEST_05 set(parallel_workers=8);
alter table public.TABTEST_06 set(parallel_workers=8);
alter table public.TABTEST_07 set(parallel_workers=8);
alter table public.TABTEST_08 set(parallel_workers=8);
alter table public.TABTEST_09 set(parallel_workers=8);

耗时 = 每个索引的耗时相加 = 292027.642 ms, 速度提升接近两倍。

在开启了并发参数的情况下,如果再叠加并发分区INDEX创建,会不会有惊喜呢?
并发创建INDEX,并记录每个分区索引创建的开始时间和结束时间;
耗时 = 最大结束时间 - 最小开始时间 = 141 s,速度还不如默认并发参数下的表现。应该是资源发生争抢导致的,通过系统监控发现CPU已经打满了。

分区并发是目前能想到的最优化手段了。

警告

还需要结合查询的情况进行分析,分区会带来一点点的性能下降是否影响也需要考虑一下。

结论

分区时目前能避开监控报警的唯一手段了,另外还钻了监控报警的空子。
客户的监控是基于单条语句的,单个分区的最大创建时间为47s,控制在分钟以内了。

你可能感兴趣的:(Postgresql 建索引性能优化)