PostgreSQL性能优化综合

前端之家收集整理的这篇文章主要介绍了PostgreSQL性能优化综合前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
【软件环境】

CentOS 5 x64
Postgresql 9.1.3
plproxy2.3
pgbouncer1.42


【测试模型】
设计一个包含INSERT,UPDATE,SELECT语句的业务模型用于本优化案例.
业务逻辑 :
【测试表】

create table user_info
(userid int,
engname textcnname textoccupation textbirthday datesignname textemail textqq numericcrt_time timestamp without time zonemod_time timestamp without time zone
);
create table user_session
logintime timestamp(0) without time zonelogin_count bigint defaultlogouttime timestamponline_interval interval interval '0'
create table user_login_rec
login_time timestamp without time zoneip inet
create table user_logout_rec
logout_time timestamp without time zone);

【初始化数据】
insert into user_info useridengnamecnnameoccupationbirthdaysignnameemailqqcrt_timemod_time)
select generate_series120000000),
'digoal.zhou''德哥''DBA''1970-01-01'
E'公益是一辈子的事,I\'m Digoal.Zhou,Just do it!''digoal@126.com'276732431clock_timestamp(),0)">NULL;
user_session );
set work_mem='2048MB';
maintenance_work_mem;
alter table user_info add constraint pk_user_info primary key );
alter table user_session add constraint pk_user_session primary key 【业务函数

-- 模拟用户登录函数
create or replace function f_user_login
i_userid OUT o_userid OUT o_engname textOUT o_cnname textOUT o_occupation textOUT o_birthday dateOUT o_signname textOUT o_email textOUT o_qq numeric
as $BODY$
declare
begin
select useridqq
into o_userido_engnameo_cnnameo_occupationo_birthdayo_signnameo_emailo_qq
from user_info where userid=i_userid user_login_rec login_timeip values nowinet_client_addr());
update user_session logintimelogin_count+1returnend$BODY$
language plpgsql;
f_user_logout
OUT o_result int
user_logout_rec logout_time logouttimeonline_interval+(()-logintimeo_result :=exception
when others then
;

【搭建测试模型】
1. 搭建环境,安装Postgresql9.1.3数据库软件并初始化数据库(略).
2. 调整数据库postgresql.conf参数.打开日志,sql统计,跟踪,以及性能参数,便于优化过程中取证.

监听IPv4的所有IP.
listen_addresses '0.0.0.0'
最大允许1000个连接.
max_connections 1000
为超级用户保留3个可用连接.
superuser_reserved_connections 3
默认的unix socket文件放在/tmp修改$PGDATA以确保安全.
unix_socket_directory '/pgdata/digoal/1921/data02/pg_root'
默认的访问权限是0777修改0700更安全.
unix_socket_permissions 0700
Linux下面默认是2小时. tcpkeepalives包发送间隔以及重试次数,0)">如果客户端没有响应,0)">将主动释放对应的SOCKETtcp_keepalives_idle 60
tcp_keepalives_interval 10
_keepalives_count 6
大的shared_buffers需要大的checkpoint_segmentsSystem V共享内存资源.
这个值不需要设的太大,0)">因为Postgresql还依赖操作系统的cache来提高读性能,0)">另外,0)">写操作频繁的数据库这个设太大反而会增加checkpoint压力.
shared_buffers 512MB
这个值越大,0)"> VACUUM CREATE INDEX的操作越快,0)">当然大到一定程度瓶颈就不在内存了,0)">可能是cpu例如创建索引.
这个值是一个操作的内存使用上限,0)">而不是一次性分配出去的.并且需要注意如果开启了autovacuum最大可能有autovacuum_max_workers*maintenance_work_mem的内存被系统消耗掉.
maintenance_work_mem 一般设置为比系统限制的略少,0)">ulimit -a : stack size kbytess10240
max_stack_depth 8MB
手动执行vacuum操作时,0)">默认是没有停顿执行到底的,0)">为了防止VACUUM操作消耗太多数据库服务器硬件资源,0)">这个值是指在消耗多少资源后停顿多少时间,以便其他的操作可以使用更多的硬件资源.
vacuum_cost_delay 10ms
#vacuum_cost_page_hit = 1 # 0-10000 credits
#vacuum_cost_page_miss = 10 # 0-10000 credits
#vacuum_cost_page_dirty = 20 # 0-10000 credits
#vacuum_cost_limit = 200 # 1-10000 credits
默认bgwriter进程执行一次后会停顿200ms再被唤醒执行下一次操作,0)">当数据库的写操作很频繁的时候,0)">可能太长,0)">导致其他进程需要花费过多的时间来进行的操作.
bgwriter_delay 10ms
如果需要做数据库WAL日志备份的话至少需要设置成archive级别,0)">如果需要做hot_standby那么需要设置成由于这个值修改需要重启数据库,0)">所以先设置成比较好.当然意味着记录得更详细,0)">如果没有打算做设置得越低性能越好.
wal_level hot_standby
wal buffers默认是-根据的设置自动调整*%.最大限制是XLOGsegment_sizewal_buffers 16384kB
多少个xlog file产生后开始操作,0)">允许shared_buffer中的被频繁访问的脏数据存储得更久.一定程度上可以提高数据库性能.但是太大的话会导致在数据库发生的时候需要处理更多的脏数据带来长时间的IO开销.太小的话会导致产生更多的文件(因为full page writesonCHECKPOINT后的第一次块的改变要写全块,0)"> checkpoint越频繁,0)">越多的数据更新要写全块导致产生更多).
checkpoint_segments 64
这个和效果是一样的,0)">只是触发的条件是时间条件.
checkpoint_timeout 5min
归档参数的修改也需要重启数据库,0)">所以就先打开吧.
archive_mode on
这个是归档调用的命令,0)">我这里用date代替,0)">所以归档的时候调用的是输出时间而不是拷贝wal文件.
archive_command '/bin/date'
如果要做hot standby这个必须大于并且修改之后要重启数据库所以先设置为32.
_wal_senders 32
这是个standby 数据库参数,0)">为了方便角色切换,0)">我一般是所有的数据库都把他设置为on 的.
hot_standby 这个参数是说数据库随机PAGE访问的开销占seq_page_cost的多少倍 seq_page_cost默认是1.其他的开销都是的倍数.这些都用于基于成本的执行计划选择.
random_page_cost 2.0
和上一个参数一样,0)">用于基于成本的执行计划选择.不是说会用多少它只是个度量值.表示系统有多少内存可以作为操作系统的.越大的话,0)">数据库越倾向使用index这种适合random访问的执行计划.
effective_cache_size 12000MB
下面是日志输出的配置.
log_destination 'csvlog'
logging_collector on
log_directory '/var/applog/pg_log/digoal/1921'
log_truncate_on_rotation log_rotation_age 1d
log_rotation_size 10MB
这个参数调整的是记录执行时间超过秒的sql到日志中,0)">一般用于跟踪哪些执行时间长.
log_min_duration_statement 1000ms
记录每一次到日志中.
log_checkpoints 记录锁等待超过秒的操作,0)">一般用于排查业务逻辑上的问题.
log_lock_waits deadlock_timeout 1s
记录DDL语句,0)">一般用于跟踪数据库中的危险操作.
log_statement 'ddl'
这个原本是1024表示跟踪的的地方截断,0)">超过将无法显示2048会消耗更多的内存(基本可以忽略),0)">不过可以显示更长的
track_activity_query_size 2048
就是打开的,0)"> log_autovacuum_min_duration 记录所有的操作.
autovacuum log_autovacuum_min_duration 0
这个模块用于记录数据库中的最近的以及这些统计信息,0)">如执行了多少次,0)">总共耗时是多少.一般用于发现业务上最频繁调用是什么,0)">有针对性的进行优化.
shared_preload_libraries 'pg_stat_statements'
custom_variable_classes pg_stat_statementsmax 1000
pg_stat_statementstrack all
其他参数值默认.
这些参数的详细解释如有疑问请参考官方文档.

3. 新建数据库用户digoal,库digoal. 并使用前面的测试模型新建表以及函数,初始化数据.
下面的测试过程中只测登陆部分,未测试退出部分,因为登陆过程已经包含了INSERT,SELECT. 基本上可以反映整个调优过程了.
【调优阶段1】
使用pgbench进行压力测试,发现瓶颈并合理优化.
1. pgbench用到的登陆脚本
cat login.sql

\setrandom userid 20000000
selectqq from user_info =:;
(:());
;

2.pgbench用到的退出脚本
cat logout.sql
insert
  3. 压力测试 

pgbench M simple r c 8f /homepostgrestestloginsql j n T 180h 172.163.33p 1921U digoal digoal

4. 压力测试结果
transaction type Custom query
scaling factor1
query mode simple
number of clients8
number of threadsduration s
number of transactions actually processed62675
tps348.084647including connections establishing)
tps 348.100337excluding connections establishingstatement latencies in milliseconds:
0.004577 \setrandom userid 20000000
12.963789 5.540750 insert 4.457834 update user_session 5. 瓶颈分析与优化
压力测试中查看数据库服务器的iostat -x
avgcpuuser nice system iowait steal idle
0.69 0.000.2524.1174.95
Device rrqms wrqms rs ws rsecs wsecs avgrqsz avgqusz await svctm util
ccissc0d0 6.001.5060.0040.000.016.671.00
ccissc0d0p1 c0d0p2 0.00
c0d0p3 c0d1 c0d2 638.5010.00217.50160.006444.0029.03152.58707.894.40100.10
c0d3 c0d4 c0d5 dm-0 866.506932.008.09446.26510.491.142340.00

操作系统的平均IO请求等待700多毫秒,Postgresql数据文件所处的块设备使用率100%. 存在严重的IO性能瓶颈.
使用pgfincore降低读的物理IO请求.
pgfincore的相关文章可参考如下,
《use posix_fadvise pre-cache frequency data》
《a powerful upgrade from pgfincore 1.0》
《TOAST table with pgfincore》
pgfincore所起的作用类似EnterpriseDB的InfiniteCache或者熟悉Oracle的朋友可能更易于接受的KEEP BUFFER POOL.
载入os cache
digoal=> reltoastrelid pg_class relname'user_info'reltoastrelid
---------------
16424
row)
relname oid=16424 relname
----------------
pg_toast_16421
\c digoal postgres
seYou are now connected to database "digoal" user "postgres".
=# select * from pgfadvise_willneed('pg_toast.pg_toast_16421');
relpath | os_page_size | rel_os_pages | os_pages_free
----------------------------------------------+--------------+--------------+---------------
pg_tblspc/16385/PG_9.1_201105231/16386/16424 | 4096 | 0 | 243865
(1 row)
# select * from pgfadvise_willneed('digoal.user_info');
relpath | os_page_size | rel_os_pages | os_pages_free
------------------------------------------------+--------------+--------------+---------------
pg_tblspc/16385/PG_9.1_201105231/16386/16421 | 4096 | 262144 | 243834
pg_tblspc/16385/PG_9.1_201105231/16386/16421.1 | 4096 | 262144 | 243834
pg_tblspc/16385/PG_9.1_201105231/16386/16421.2 | 4096 | 244944 | 243834
(3 rows)
# select * from pgfadvise_willneed('digoal.user_session');
pg_tblspc/16385/PG_9.1_201105231/16386/16431 | 4096 | 262144 | 243834
pg_tblspc/16385/PG_9.1_201105231/16386/16431.1 | 4096 | 33640 | 243834
(2 rows)
# select reltoastrelid from pg_class where relname='user_session';
reltoastrelid
---------------
0
# select * from pgfadvise_willneed('digoal.pk_user_session');
pg_tblspc/16385/PG_9.1_201105231/16386/16438 | 4096 | 109680 | 243865
# select * from pgfadvise_willneed('digoal.pk_user_info');
pg_tblspc/16385/PG_9.1_201105231/16386/16436 | 4096 | 109680 | 235567
(1 row)

【调优阶段2】
1. 压力测试
U digoal digoal

2. 测试结果
 query
   
   
1
simple
8
s
264895
1471.517096)
1471.585818:
0.00422620000000
0.4598242.4577972.501684;

3. 瓶颈分析与优化
SELECT语句的延时已经通过上一个优化阶段下降到了0.45毫秒,INSERT和UPDATE语句的平均耗时也从原来的5.5和4.45下降到了2.5.原因是select的请求在内存中命中了,因此update和insert不需要和select争抢物理io请求,处理效率自然有一定的提高.
但是INSERT和UPDATE的语句延时还有2.5毫秒存在很大的可优化空间.
开启Postgresql的异步提交日志.
synchronous_commit  off
   
   
wal_writer_delay 10ms

与Oracle的异步日志差别请参考 :
《Postgresql and Oracle's async commit》
http://blog.163.com/digoal@126/blog/static/16387704020121229223072/
【调优阶段3】
685344
3751.3779193751.5689480.0034740.4187160.5116011.188277;

3. 瓶颈分析与优化
客户端连接使用simple协议,存在一定的可优化空间.
修改协议为extended,查看性能提升多少.
【调优阶段4】
M extended 2. 测试结果
extended
970981
5394.0153685394.2154770.0033450.3816750.2963000.792592;

3. 瓶颈分析与优化
客户端连接使用extended协议,存在一定的可优化空间.
修改协议为prepared,查看性能提升多少.
参见 :
《Postgresql prepared statement: SPI_prepare,prepare|execute COMMAND,PL/pgsql STYLE: custom & generic plan cache》
http://blog.163.com/digoal@126/blog/static/1638770402012112452432251/
【调优阶段5】
M prepared prepared
1044186
5800.5893305800.9024910.0034650.3196650.2669310.777822 3. 瓶颈分析与优化
压力测试的脚本中使用的是普通的sql语句,未使用初始化时用到的登陆函数退出函数. 使用普通sql显然比使用函数多了交互的次数以及每次发送的数据包的大小.
使用函数看看性能能提升多少.
【调优阶段6】
1. 登陆脚本
SELECT f_user_login);

2. 退出脚本
f_user_logout 4. 测试结果
1616746
8981.5962908981.9958000.0040120.881060 SELECT f_user_login 到这个时候看起来好像没什么好优化的了,其实不然.
我们知道在整个登陆过程中用到了SELECT,INSERT.
其中UPDATE的表有一个PK索引,每次更新需要修改数据表的同时还需要更新索引. 所以理论上这个更新操作表越小性能越高.
我们可以通过拆表来提升性能. 如下 :
拆表 :
create table user_info_0 like user_info including all);
   
   
create table user_info_1 create table user_info_2 create table user_info_3 create table user_info_4 );
create table user_session_0 like user_session including allcreate table user_session_1 create table user_session_2 create table user_session_3 create table user_session_4 );
插入初始化数据 :
 user_info_0 )
   
   
4000000'1970-01-01'
;
user_info_1 40000018000000 user_info_2 800000112000000 user_info_3 1200000116000000 user_info_4 16000001 user_session_0 user_session_1 user_session_2 user_session_3 user_session_4 );
;
alter table user_info_0 add primary key alter table user_info_1 add primary key alter table user_info_2 add primary key alter table user_info_3 add primary key alter table user_info_4 add primary key alter table user_session_0 add primary key alter table user_session_1 add primary key alter table user_session_2 add primary key alter table user_session_3 add primary key alter table user_session_4 add primary key );


同样通过pgfincore把他们加载到内存中,这里不详细描述.
新建登陆和退出函数
 f_user_login_0
   
   
OUT o_qq numeric
$BODY$
declare
begin
qq
o_qq
from user_info_0 update user_session_0 $BODY$
f_user_login_1
from user_info_1 update user_session_1 f_user_login_2
from user_info_2 update user_session_2 f_user_login_3
from user_info_3 update user_session_3 f_user_login_4
from user_info_4 update user_session_4 f_user_logout_0
int
exception
others then
f_user_logout_1
f_user_logout_2
f_user_logout_3
f_user_logout_4
【调优阶段7】
cat login*.sql
4000000
   
   
f_user_login_040000018000000
f_user_login_1800000112000000
f_user_login_21200000116000000
f_user_login_316000001 f_user_login_4
cat logout*.sql
 f_user_logout_0 f_user_logout_1 f_user_logout_2 f_user_logout_3 f_user_logout_4login0U digoal digoal >./loglogin0 &
  
  
login1login1 login2login2 login3login3 login4login4 &

cat log.log*
 prepared
   
   
233348
1281.8180971281.8371090.0034924000000
0.771932 SELECT f_user_login_0233466
1282.5147741282.5735000.0035468000000
0.771399 SELECT f_user_login_12
475466
2612.2007832612.2815260.00360512000000
0.757312 SELECT f_user_login_2468904
2576.3804432576.4884850.00358716000000
0.767869 SELECT f_user_login_3439381
2414.3470862414.4256000.0044310.817879 SELECT f_user_login_4);
总计
10167.261183)

到这里我们还没有关注过表空间,其实这些表拆分后它们还在同一个表空间里面. 把它们放在不同的表空间可以扩展它们整体的IO吞吐能力.

# \db+
List of tablespaces
Name | Owner | Location | Access privileges | Description
------------+----------+---------------------------------------------+---------------------+-------------
digoal | postgres | /pgdata/digoal/1921/data02/pg_tbs/digoal | postgres=C/postgres+|
| | | digoal=C/postgres |
digoal_01 | postgres | /pgdata/digoal/1921/data03/pg_tbs/digoal_01 | postgres=C/postgres+|
digoal_02 | postgres | /pgdata/digoal/1921/data04/pg_tbs/digoal_02 | postgres=C/postgres+|
digoal_03 | postgres | /pgdata/digoal/1921/data05/pg_tbs/digoal_03 | postgres=C/postgres+|
digoal_04 | postgres | /pgdata/digoal/1921/data06/pg_tbs/digoal_04 | postgres=C/postgres+|
| | | digoal=C/postgres |
digoal=> alter table user_info_0 set tablespace digoal_04;
ALTER TABLE
digoal=> alter table user_info_2 set tablespace digoal_01;
digoal=> alter table user_info_3 set tablespace digoal_02;
digoal=> alter table user_info_4 set tablespace digoal_03;
alter index user_info_0_pkey tablespace digoal_04;
ALTER INDEX
digoal alter index user_info_2_pkey tablespace digoal_01 alter index user_info_3_pkey tablespace digoal_02 alter index user_info_4_pkey tablespace digoal_03;
alter table user_session_0 TABLE
alter table user_session_2 alter table user_session_3 alter table user_session_4 alter index user_session_0_pkey alter index user_session_2_pkey alter index user_session_3_pkey alter index user_session_4_pkey ;


重新把它们加载到内存.
下节 :
http://blog.163.com/digoal@126/blog/static/163877040201221333411196/

【附】
pgbench simple|extended|prepare 部分源码 :
1. pgbench.c

if (commands[st->state]->type == sql_COMMAND)
{
const Command*command = commands];
int r;
querymode QUERY_SIMPLE charsql sql xstrdupcommandargv[0]);
assignVariables sql);
debug fprintfstderr "client %d sending %s\n" stid);
r PQsendQuerycon free}
else QUERY_EXTENDEDsql command*paramsMAX_ARGS];
getQueryParamsPQsendQueryParamsargc - 1 NULL NULL QUERY_PREPARED nameMAX_PREPARE_NAME(!prepareduse_file])
j;
forj ;j]!= j++)
PGresult res;
continue preparedStatementNamename res PQprepare name commands],0)">(PQresultStatus) PGRES_COMMAND_OK fprintf"%s"PQerrorMessage));
PQclear sttrue}
preparedStatementNamePQsendQueryPrepared }

2.src/interfaces/libpq/fe-exec.c 原文链接:https://www.f2er.com/postgresql/196117.html

猜你在找的Postgre SQL相关文章