PostgreSQL 性能优化之 - 大锁与long sql/xact的蝴蝶效应

在使用数据库时,某些特定的因素结合在一起,就可能引发蝴蝶效应。
导致数据库性能的急剧下降。
本文要讲的是和以下几个因素有关的:

因素1
PG的锁排队机制,即使没有获得锁,只要在锁队列中就会造成锁竞争。

session A lock1 get
session B lock2 wait lock1 release
session C lock3 可能和lock1, lock2 冲突
session D lock4 可能和lock1,2,3 冲突

因素2
SQL响应变慢后,服务端程序通常会增加到数据库的连接来处理拥塞的请求

因素3
数据库的性能会随着连接数增加到一个值(通常是核数的3倍)后,性能开始下降

因素4
对象锁在事务结束时释放
例如在事务中查询表时,表的共享锁需要等到事务结束时释放。

以上4个因素加在一起,就可能引发一次应用级别的故障。

模拟
开启lock跟踪:

log_lock_waits = on
deadlock_timeout = 1s

创建测试表

postgres=# create table test(id int primary key, info text, crt_time timestamp);
CREATE TABLE
postgres=# insert into test select generate_series(1,10000000),md5(random()::text),clock_timestamp();
INSERT 0 10000000

测试脚本

vi test1.sql
\setrandom id 1 10000000
update test set info=info where id=:id;

.1. pgbench1 模拟数据更新A。使用10个链接(假设正常只需要10个)
正常的性能

pgbench -M prepared -n -r -P 1 -f ./test1.sql -c 10 -j 10 -T 10
progress: 2.0 s, 65994.3 tps, lat 0.149 ms stddev 0.038
progress: 3.0 s, 67706.5 tps, lat 0.145 ms stddev 0.051
progress: 4.0 s, 72865.0 tps, lat 0.135 ms stddev 0.048
progress: 5.0 s, 77664.2 tps, lat 0.126 ms stddev 0.032
progress: 6.0 s, 77138.9 tps, lat 0.127 ms stddev 0.037
progress: 7.0 s, 75941.3 tps, lat 0.129 ms stddev 0.061
progress: 8.0 s, 77328.8 tps, lat 0.127 ms stddev 0.036

开启长时间更新请求

pgbench -M prepared -n -r -P 1 -f ./test1.sql -c 10 -j 10 -T 10000

.2. 模拟一个查询长事务,查询表A
也可能是后台的whole vacuum prevent wrapper

postgres=# begin;
BEGIN
postgres=# select * from test limit 1;
 id |               info               |          crt_time
----+----------------------------------+----------------------------
  1 | e86e219d51c39d16f78d77cf697395ca | 2016-03-16 16:07:49.814487
(1 row)

暂不结束事务, 持有test表的shared lock.

.3. 模拟一个DDL请求A

postgres=# alter table test add column c1 int;

等待test shared lock锁释放

马上会堵塞正常的业务请求,tps降到0

progress: 53.0 s, 0.0 tps, lat -nan ms stddev -nan
progress: 54.0 s, 0.0 tps, lat -nan ms stddev -nan
progress: 55.0 s, 0.0 tps, lat -nan ms stddev -nan
progress: 56.0 s, 0.0 tps, lat -nan ms stddev -nan
progress: 57.0 s, 0.0 tps, lat -nan ms stddev -nan
progress: 58.0 s, 0.0 tps, lat -nan ms stddev -nan
progress: 59.0 s, 0.0 tps, lat -nan ms stddev -nan

.4. 这个时候,业务并不知道数据库堵塞了,会增加更多的连接来处理用户的请求。甚至可能把连接塞满。
pgbench2 模拟拥塞更新A,新建500链接

pgbench -M prepared -n -r -P 1 -f ./test1.sql -c 500 -j 500 -T 10000

新增的连接会全部处于这样的状态:

digoal  25434  22068  0 16:21 ?        00:00:00 postgres: postgres postgres [local] PARSE waiting
digoal  25437  22068  0 16:21 ?        00:00:00 postgres: postgres postgres [local] PARSE waiting

.5. 结束长事务或结束DDL请求后,锁释放。
锁释放,大量并发的连接开始处理拥塞的请求
此时性能下降了一半
pgbench2

progress: 10.3 s, 270.5 tps, lat 1396.862 ms stddev 3498.526
progress: 11.0 s, 34443.5 tps, lat 64.132 ms stddev 709.718
progress: 12.0 s, 34986.1 tps, lat 14.229 ms stddev 18.469
progress: 13.0 s, 36645.0 tps, lat 13.661 ms stddev 17.686
progress: 14.0 s, 34570.1 tps, lat 14.463 ms stddev 18.716
progress: 15.0 s, 36435.8 tps, lat 13.752 ms stddev 17.621
progress: 16.0 s, 35513.3 tps, lat 14.052 ms stddev 18.087
progress: 17.0 s, 35560.0 tps, lat 14.013 ms stddev 18.159

pgbench1

progress: 59.0 s, 688.7 tps, lat 340.857 ms stddev 2734.371
progress: 60.0 s, 733.0 tps, lat 13.659 ms stddev 18.501
progress: 61.0 s, 816.0 tps, lat 12.237 ms stddev 16.941
progress: 62.0 s, 811.0 tps, lat 12.328 ms stddev 16.715
progress: 63.0 s, 809.9 tps, lat 12.370 ms stddev 17.370
progress: 64.0 s, 750.1 tps, lat 13.338 ms stddev 17.745

将后建立的500个连接释放后,恢复正常的性能

progress: 66.0 s, 1937.8 tps, lat 5.044 ms stddev 12.975
progress: 67.0 s, 64995.8 tps, lat 0.157 ms stddev 0.757
progress: 68.0 s, 73996.3 tps, lat 0.133 ms stddev 0.042
progress: 69.0 s, 78099.4 tps, lat 0.125 ms stddev 0.038

日志
可以追踪到锁等待的源头,但是不能追踪到大锁。

2016-03-16 16:25:57.531 CST,"postgres","postgres",48877,"[local]",56e91894.beed,3,"ALTER TABLE waiting",2016-03-16 16:25:56 CST,13/28,580426398,LOG,00000,"process 48877 still waiting for AccessExclusiveLock on relation 61245 of database 13241 after 1000.048 ms","Process holding the lock: 48557. Wait queue: 48877, 46333, 46331, 46338, 46334, 46339, 46335, 46340, 46337, 46328, 46336.",,,,,"alter table test add column c1 int;",,"ProcSleep, proc.c:1323","psql"
2016-03-16 16:25:57.531 CST,"postgres","postgres",46333,"[local]",56e91871.b4fd,3,"BIND waiting",2016-03-16 16:25:21 CST,4/263058,0,LOG,00000,"process 46333 still waiting for RowExclusiveLock on relation 61245 of database 13241 after 1000.036 ms","Process holding the lock: 48557. Wait queue: 48877, 46333, 46331, 46338, 46334, 46339, 46335, 46340, 46337, 46328, 46336.",,,,,"update test set info=info where id=$1;",,"ProcSleep, proc.c:1323","pgbench"  

2016-03-16 16:26:10.191 CST,"postgres","postgres",49812,"[local]",56e918a1.c294,3,"PARSE waiting",2016-03-16 16:26:09 CST,14/29,0,LOG,00000,"process 49812 still waiting for RowExclusiveLock on relation 61245 of database 13241 after 1000.207 ms","Process holding the lock: 48557. Wait queue: 48877, 46333, 此处省略500+ PIDs, 50816, 50817.",,,,,"update test set info=info where id=$1;",8,"ProcSleep, proc.c:1323","pgbench"  

2016-03-16 16:26:19.367 CST,"postgres","postgres",48877,"[local]",56e91894.beed,4,"ALTER TABLE waiting",2016-03-16 16:25:56 CST,13/28,580426398,LOG,00000,"process 48877 acquired AccessExclusiveLock on relation 61245 of database 13241 after 22836.312 ms",,,,,,"alter table test add column c1 int;",,"ProcSleep, proc.c:1327","psql"
2016-03-16 16:26:19.368 CST,"postgres","postgres",48877,"[local]",56e91894.beed,5,"ALTER TABLE",2016-03-16 16:25:56 CST,13/28,580426398,ERROR,42701,"column ""c1"" of relation ""test"" already exists",,,,,,"alter table test add column c1 int;",,"check_for_column_name_collision, tablecmds.c:5069","psql"  

2016-03-16 16:26:19.379 CST,"postgres","postgres",49814,"[local]",56e918a1.c296,4,"PARSE waiting",2016-03-16 16:26:09 CST,15/2,0,LOG,00000,"process 49814 acquired RowExclusiveLock on relation 61245 of database 13241 after 10177.162 ms",,,,,,"update test set info=info where id=$1;",8,"ProcSleep, proc.c:1327","pgbench"

要追踪大锁,
可以使用以下SQL

with t_wait as
(select a.mode,a.locktype,a.database,a.relation,a.page,a.tuple,a.classid,
a.objid,a.objsubid,a.pid,a.virtualtransaction,a.virtualxid,a,
transactionid,b.query,b.xact_start,b.query_start,b.usename,b.datname
  from pg_locks a,pg_stat_activity b where a.pid=b.pid and not a.granted),
t_run as
(select a.mode,a.locktype,a.database,a.relation,a.page,a.tuple,
a.classid,a.objid,a.objsubid,a.pid,a.virtualtransaction,a.virtualxid,
a,transactionid,b.query,b.xact_start,b.query_start,
b.usename,b.datname from pg_locks a,pg_stat_activity b where
a.pid=b.pid and a.granted)
select r.locktype,r.mode r_mode,r.usename r_user,r.datname r_db,
r.relation::regclass,r.pid r_pid,
r.page r_page,r.tuple r_tuple,r.xact_start r_xact_start,
r.query_start r_query_start,
now()-r.query_start r_locktime,r.query r_query,w.mode w_mode,
w.pid w_pid,w.page w_page,
w.tuple w_tuple,w.xact_start w_xact_start,w.query_start w_query_start,
now()-w.query_start w_locktime,w.query w_query
from t_wait w,t_run r where
  r.locktype is not distinct from w.locktype and
  r.database is not distinct from w.database and
  r.relation is not distinct from w.relation and
  r.page is not distinct from w.page and
  r.tuple is not distinct from w.tuple and
  r.classid is not distinct from w.classid and
  r.objid is not distinct from w.objid and
  r.objsubid is not distinct from w.objsubid and
  r.transactionid is not distinct from w.transactionid and
  r.pid <> w.pid
  order by
  ((  case w.mode
    when 'INVALID' then 0
    when 'AccessShareLock' then 1
    when 'RowShareLock' then 2
    when 'RowExclusiveLock' then 3
    when 'ShareUpdateExclusiveLock' then 4
    when 'ShareLock' then 5
    when 'ShareRowExclusiveLock' then 6
    when 'ExclusiveLock' then 7
    when 'AccessExclusiveLock' then 8
    else 0
  end  ) +
  (  case r.mode
    when 'INVALID' then 0
    when 'AccessShareLock' then 1
    when 'RowShareLock' then 2
    when 'RowExclusiveLock' then 3
    when 'ShareUpdateExclusiveLock' then 4
    when 'ShareLock' then 5
    when 'ShareRowExclusiveLock' then 6
    when 'ExclusiveLock' then 7
    when 'AccessExclusiveLock' then 8
    else 0
  end  )) desc,r.xact_start;

优化措施或处理措施
.1. 养成大锁处理习惯, 配置锁等待超时
.2. 应用程序或中间件应该有自动释放空闲连接的功能
.3. auto_explain也不会记录锁等待的时间,所以不利于分析原因。只有从日志中才能分析。

时间: 2024-09-30 21:59:38

PostgreSQL 性能优化之 - 大锁与long sql/xact的蝴蝶效应的相关文章

PostgreSQL 性能优化方法 - 1

使用一个非常容易理解的案例,讲解PostgreSQL的优化手段. 本文成文自2012年初,有一些性能指标数据已经过时了,但是优化方法没有过时,请关注优化的方法. 现在的硬件和软件,一台主机达到的性能数据已经可以替代我当时测试的8台主机性能了. [软件环境] CentOS 5 x64 PostgreSQL 9.1.3 (还是以前测试的,所以用的老版本,你可以用最新的版本测试比如9.5) plproxy 2.3 pgbouncer 1.4.2 [测试模型] 设计一个包含INSERT, UPDATE,

postgreSql性能优化提升技巧指南

一.使用EXPLAIN:     PostgreSQL为每个查询都生成一个查询规划,因为选择正确的查询路径对性能的影响是极为关键的.PostgreSQL本身已经包含了一个规划器用于寻找最优规划,我们可以通过使用EXPLAIN命令来查看规划器为每个查询生成的查询规划.     PostgreSQL中生成的查询规划是由1到n个规划节点构成的规划树,其中最底层的节点为表扫描节点,用于从数据表中返回检索出的数据行.然而,不同的扫描节点类型代表着不同的表访问模式,如:顺序扫描.索引扫描,以及位图索引扫描等

PostgreSQL 性能优化方法 - 2

[调优阶段8] 1. 压力测试 pgbench -M prepared -r -c 1 -f /home/postgres/test/login0.sql -j 1 -n -T 180 -h 172.16.3.33 -p 1921 -U digoal digoal >./log.login0 & pgbench -M prepared -r -c 1 -f /home/postgres/test/login1.sql -j 1 -n -T 180 -h 172.16.3.33 -p 1921

Oracle SQL性能优化技巧大总结

(1) 选择最有效率的表名顺序(只在基于规则的优化器中有效): ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名,FROM子句中写在最后的表(基础表 driving table)将被最先处理,在FROM子句中包含多个表的情况下,你必须选择记录条数最少的表作为基础表.如果有3个以上的表连接查询, 那就需要选择交叉表(intersection table)作为基础表, 交叉表是指那个被其他表所引用的表. (2) WHERE子句中的连接顺序.: ORACLE采用自下而上的顺序解析WHER

PostgreSQL的性能优化

PostgreSQL 是全球最先进的开源数据库.作为学院派关系型数据库管理系统的鼻祖,它的优点主要集中在对 SQL 规范的完整实现以及丰富多样的数据类型支持(JSON 数据.IP 数据和几何数据等,大部分商业数据库都不支持).除了完美支持事务.子查询.多版本控制(MVCC).数据完整性检查等特性外,阿里云数据库RDS for  PostgreSQL 版还集成了高可用和备份恢复等重要功能,减轻用户的运维压力. PostgreSQL提供慢日志.SQL 运行报告.缺失索引等优化建议,用户可以根据优化建

SQL点滴22—性能优化没有那么神秘

原文:SQL点滴22-性能优化没有那么神秘 经常听说SQL Server最难的部分是性能优化,不禁让人感到优化这个工作很神秘,这种事情只有高手才能做.很早的时候我在网上看到一位高手写的博客,介绍了SQL优化的问题,从这些内容来看,优化并不都是一些很复杂的问题,掌握了基本的知识之后也可以尝试优化自己的SQL程序,甚至是其他相关的程序.优化是一些工作积累之后的经验总结和代码意识,只要平时注意积累,你也可以做优化的工作.这一篇随笔是转载,不过我强烈推荐给所有对数据库优化有兴趣的博友,读了这一篇之后下一

.Net+SQL Server企业应用性能优化笔记1——调研

在进行了一段时间的调研后,本周开始着手进行性能优化工作.现在在优化工作工作之前,我总结一下调研了的一些信息. 1.背景 客户这是一个03年的时候开发的系统了,所以使用的是.NET 1.1+SQL Server 2000,操作系统用的是Windows2003,使用了这么几年,只是对操作系统进行了升级(从当时的Windows2000升级到2003的)以及对系统进行维护,.Net环境和数据库并没有改变.由于系统中记录了几年的数据,有些表有几百万行的数据,当初没有建立索引和系统程序上考虑的不足造成目前系

Sql Server 查询性能优化之走出索引的误区分析_MsSql

据了解绝大多数开发人员对于索引的理解都是一知半解,局限于大多数日常工作没有机会.也什么没有必要去关心.了解索引,实在哪天某个查询太慢了找到查询条件建个索引就ok,哪天又有个查询慢了,再建立个索引就是,或者干脆把整个查询SQL直接发给DBA,让DBA直接帮忙优化了,所以造成的状况就是开发人员对于索引的理解.认识很局限,以下就把我个人对于索引的理解及浅薄认识和大家分享下,希望能解除一些大家的疑惑,一起走出索引的误区 误区1.在表上建立了索引,在查询时用到了索引的列,索引就一定会生效 首先明确下这样的

分析Sql Server查询性能优化之走出索引的误区

误区1.在表上建立了索引,在查询时用到了索引的列,索引就一定会生效 首先明确下这样的观点是错误的,SQL Server查询优化器是基于开销进行选择的优化器,通过一系列复杂判断来决定是否使用索引.使用什么类型索引.使用那个索引.SQL Server内部维护着索引列上的数据的统计,统计信息会随着索引列内容的变化而变化,索引的有效期完全取决于索引列上的统计信息,随着数据的变化关于索引的检索机制也随之变化.对于查询优化器来说始终保持查询开销最低始终是其的不二选择,如果一个非聚集索引的列上有大量的重复值,