数据库三十六计 - PostgreSQL 三十六计(下)

5 minute read

背景

PostgreSQL 三十六计 - 下

25. 数据库端编程,处理复杂业务逻辑。

在传统企业、电商、运营商等涉及用户交互、或者多个系统交互的业务场景中,通常一个事务涉及到很复杂的业务逻辑,需要保证数据的一致性,同时还需要与数据库多次交互。

比如银行开户,涉及的业务系统多,逻辑复杂。在传统企业中,通常也使用商业数据库的过程函数,实现此类复杂的逻辑。

PostgreSQL的数据库过程函数支持的语言非常丰富,比如plpgsql(可与Oracle pl/sql功能比肩),另外还支持语言的扩展,编程语言可以热插拔,例如支持python,perl,java,c,r 等等作为数据库的过程函数语言。

对于开发人员来说,几乎可以在PostgreSQL数据库中处理任何业务逻辑。使用数据库端编程,可以在保证一致性的前提下,处理复杂的业务逻辑,减少数据库与程序的交互次数,降低整个事务的RT。

https://www.postgresql.org/docs/10/static/plpgsql.html

https://www.postgresql.org/docs/10/static/pltcl.html

https://www.postgresql.org/docs/10/static/plperl.html

https://www.postgresql.org/docs/10/static/plpython.html

26. 善用ECPG,C嵌入式开发

在一些对性能要求非常高的场景,同时考虑开发效率,C嵌入式开发是一个非常好的选择。

比如在很多金融行业的开发商,ECPG和Oracle Pro*C一样具有同等地位。

https://www.postgresql.org/docs/10/static/ecpg.html

27. 数据库水平拆分、跨平台数据融合

PostgreSQL 从 2011年的9.1版本引入FDW开始,发展到现在已经支持几乎所有的外部数据源读写操作,例如mysql,oracle,pgsql,redis,mongo,hive,jdbc,odbc,file,sqlserver,es,S3,……。

https://wiki.postgresql.org/wiki/Fdw

开放的接口,允许用户自己添加外部数据源的支持。

9.6针对postgres_fdw(即PostgreSQL外部数据源)再次增强,开始支持对sort, where, join的下推,支持remote cancel query, 用户使用FDW可以对应用透明的实现数据库的sharding,单元化需求。

内核层支持sharding,这种分片技术相比中间件分片技术的好处:

1. 支持跨库JOIN

2. 支持绑定变量

3. 支持master(coordinator)节点水平扩展

4. 支持segment(datanode)节点水平扩展

5. 支持函数和存储过程

6. 支持sort, where, join的下推,支持remote cancel query,10.x支持聚合算子的下推。

7. 支持单一SQL,在shard节点并行RUN QUERY。

ps: 目前还不支持分布式事务(需要用户干预2PC) ,10.x的版本会增加内核层面的分布式事务控制。

《PostgreSQL 最佳实践 - 水平分库(基于plproxy)》

《PostgreSQL 9.6 sharding based on FDW & pg_pathman》

《PostgreSQL 9.6 sharding + 单元化 (based on postgres_fdw) 最佳实践 - 通用水平分库场景设计与实践》

《阿里云ApsaraDB RDS for PostgreSQL 最佳实践 - 4 水平分库 之 节点扩展》

《阿里云ApsaraDB RDS for PostgreSQL 最佳实践 - 3 水平分库 vs 单机 性能》

《阿里云ApsaraDB RDS for PostgreSQL 最佳实践 - 2 教你RDS PG的水平分库》

28. 开发规约 - 命名

1. 长度不要超过63个字符,如果要超过的话,需要在编译PostgreSQL前修改头文件

src/include/pg_config_manual.h  
  
/*  
 * Maximum length for identifiers (e.g. table names, column names,  
 * function names).  Names actually are limited to one less byte than this,  
 * because the length must include a trailing zero byte.  
 *  
 * Changing this requires an initdb.  
 */  
#define NAMEDATALEN 64  

2. 不要使用关键字作为对象名。

[《PostgreSQL Oracle兼容性 之 - parser SQL保留 关键字(keywrods)大全》](../201710/20171024_06.md)

3. 不要使用小写字母、数字和下划线以外的字符作为对象名。

否则需要使用双引号。

create table "表aA-f3" (id int);  

4. 不建议使用多字节字符作为注释。

29. 开发规约 - 设计

1. 同类属性,将来可能涉及JOIN,FK,务必对齐数据类型,包括数据长度。

2. BTREE索引字段,长度不建议超过2000字节,否则请使用hash索引或表达式索引。

create index idx on tbl using hash (long_col);  
  
create index idx on tbl (hashtext(long_col));  

3. 频繁更新的表、索引,建议设置合适的fillfactor。

create table ttt (id int) with (fillfactor=80);  

4. 定期删除历史数据的场景,建议使用分区表或继承表的方式,TRUNCATE或DROP或断开继承的方式删除分区或继承的数据。

5. 全球化业务,建议使用UTF-8字符集。

6. 使用能描述清楚数据的类型。能不用字符串的时候,尽量不要用字符串。

7. 不建议使用无序的UUID,也不建议在无序的UUID上建立索引,包括PK。

8. 根据不同的数据类型,数据的查询需求,使用合适的索引方法(Btree, GiST, GIN, SP-GiST, HASH, rum, BRIN, bloom)。

《PostgreSQL 9种索引的原理和应用场景》

9. 建议不需要的大对象,要清理,否则会造成空间泄露。

vacuumlo removes unreferenced large objects from databases.  
  
Usage:  
  vacuumlo [OPTION]... DBNAME...  
  
Options:  
  -l LIMIT       commit after removing each LIMIT large objects  
  -n             don't remove large objects, just show what would be done  
  -v             write a lot of progress messages  
  -V, --version  output version information, then exit  
  -?, --help     show this help, then exit  
  
Connection options:  
  -h HOSTNAME    database server host or socket directory  
  -p PORT        database server port  
  -U USERNAME    user name to connect as  
  -w             never prompt for password  
  -W             force password prompt  

10. 不建议使用public schema(不同业务共享的对象可以使用public schema),应该为每个应用分配对应的schema,schema_name最好与username一致。

create role user1;  
  
create schema user1 authorized user1;  

11. 用户应该对频繁访问的大表(例如超过16GB的表,或者超过1亿记录的表,视IOPS能力)进行分区,从而提升查询的效率、更新的效率、备份与恢复的效率、建索引的效率等等,(PostgreSQL 10支持多核创建索引,可以适当将这个限制放大)。

https://www.postgresql.org/docs/10/static/ddl-partitioning.html

12. 多个业务共用一个PG集群时,建议为每个业务创建一个数据库。 如果业务之间有数据交集,建议使用同一个数据库,使用schema将其隔离。

《PostgreSQL 逻辑结构 和 权限体系 介绍》

pic

30. 开发规约 - QUERY

1. 返回大量结果集的时候,建议使用游标的方式流式返回。

begin;  
  
declare cur1 cursor for select * from tbl where xxx order by xxx;  
  
fetch 1000 from cur1;  
  
close cur1;  
  
end;  

2. 翻页时,不建议使用order by offset的方式,因为越到末尾,消耗越大。建议使用游标的方式翻页。

或者使用OFFSET偏移条件。

select * from tbl where xxx and xx>last_offseted_val order by xxx limit xxx;  

3. 对于经常使用表达式作为查询条件的语句,应使用表达式或函数索引加速查询。

create index idx on tbl (udf(x,xxx));  
  
select * from tbl where udf(x,xxx) ...;  

4. 中文分词的查询,建议使用PostgreSQL的分词插件zhparser或jieba,用户还可以通过接口自定义词组。

create extension zhparser;  

《使用阿里云PostgreSQL zhparser中文分词时不可不知的几个参数》

5. 规则表达式查询,文本近似度查询,前后模糊查询。建议对字段使用trgm的gin、gist索引。中文的支持确保数据库的lc_ctype不等于C。

create index idx on tbl using gin (txt gin_trgm_ops);  

6. 不要使用count(列名)或count(常量)来替代count(),count()就是SQL92定义的标准统计行数的语法,跟数据库无关,跟NULL和非NULL无关。

7. 任何地方都不要使用 select * from t ,用具体的字段列表代替*,不要返回用不到的任何字段。另外表结构发生变化也容易出现问题。

31. 管理规约 - 安全与审计

1. 所有的DDL均应该被审计。

postgres=# alter system set log_statement TO ddl;  
ALTER SYSTEM  
  
postgres=# select pg_reload_conf();  
 pg_reload_conf   
----------------  
 t  
(1 row)  

2. 数据库的非应用程序用户(例如DBA,分析师,开发账号),所有的操作均应该被审计。

postgres=# alter role user_name set log_statement to ddl;  

3. 所有的登陆和退出操作必须被审计。

vi postgresql.conf  
  
log_connections = on  
log_disconnections = on  

4. 关键表(例如账务表、密码表等)的所有操作(包括DML)应该被审计。

《USE hstore store table’s trace record》

5. 为数据库访问账号设置复杂密码。

https://www.postgresql.org/docs/10/static/chkpass.html

《PostgreSQL 密码安全指南》

6. 业务系统,开发测试账号,不要使用数据库超级用户。

Command:     CREATE ROLE  
Description: define a new database role  
Syntax:  
CREATE ROLE name [ [ WITH ] option [ ... ] ]  
  
where option can be:  
  
      SUPERUSER | NOSUPERUSER  
    | CREATEDB | NOCREATEDB  
    | CREATEROLE | NOCREATEROLE  
    | INHERIT | NOINHERIT  
    | LOGIN | NOLOGIN  
    | REPLICATION | NOREPLICATION  
    | BYPASSRLS | NOBYPASSRLS  
    | CONNECTION LIMIT connlimit  
    | [ ENCRYPTED ] PASSWORD 'password'  
    | VALID UNTIL 'timestamp'  
    | IN ROLE role_name [, ...]  
    | IN GROUP role_name [, ...]  
    | ROLE role_name [, ...]  
    | ADMIN role_name [, ...]  
    | USER role_name [, ...]  
    | SYSID uid  

7. 应该为每个业务分配不同的数据库账号,禁止多个业务共用一个数据库账号。

8. 敏感数据应该加密存储,建议在服务端加解密,如果加密后的数据有等值查询以外的查询需求,建议使用数据库的透明加密技术。

《固若金汤 - PostgreSQL pgcrypto加密插件》

https://www.postgresql.org/docs/10/static/pgcrypto.html

9. 对安全要求较高的业务,网络传输应该被加密,使用数据库的SSL连接。

《PostgreSQL 如何实现网络压缩传输或加密传输(openssl)》

10. 不建议将数据库的监听端口暴露在公网,防止破解密码,DDoS攻击。

《PostgreSQL 连接攻击(类似DDoS)》

11. 有公网访问需求时,建议开启SSL连接,前置防火墙并使用来访白名单,数据库PG_HBA.CONF也要配置白名单。

12. 不要泄露用户密码的MD5值,目前这么做有危险,(10.0 SASL上线后安全性会有提高)。

《PostgreSQL 10.0 preview 安全增强 - SASL认证方法 之 scram-sha-256 安全认证机制》

13. 使用auth_delay插件,可以防止暴力破解。

https://www.postgresql.org/docs/10/static/auth-delay.html

32. 管理规约 - 诊断

1. 统计信息活动视图(表、索引、库、STANDBY、SLOT、流复制、后台任务、bgwriter、归档、事务、活动会话等),均以pg_stat开头。

《PostgreSQL 统计信息pg_statistic格式及导入导出dump_stat - 兼容Oracle》

《PostgreSQL pg_stat_ pg_statio_ 统计信息(scan,read,fetch,hit)源码解读》

2. auto_explain插件,可以帮助用户分析慢SQL的执行计划。

《PostgreSQL 函数调试、诊断、优化 & auto_explain》

3. pg_stat_statements插件,可以帮助用户找出资源开销的TOP SQL,(调用次数、IO耗时、CPU耗时、内存消耗)。优化数据库,应该从开销的TOP 1 SQL开始。

《PostgreSQL 如何查找TOP SQL (例如IO消耗最高的SQL)》

4. 建议配置慢SQL的时间阈值,超过阈值的SQL将被记录到日志文件中。

vi postgresql.conf  
  
log_min_duration_statement = '1s'  

5. 建议将日志输出格式配置为CSV格式。

vi postgresql.conf  
  
log_destination = 'csvlog'  

6. explain (analyze,verbose,timing,costs,buffers) 可以执行SQL,并输出详细的执行计划,帮助分析SQL的瓶颈,优化SQL。

7. explain analyze会真实的执行SQL,因此如果要用在DML语句上,请使用begin; explain analyze; rollback;的模式。

8. auto_explain插件,还可以帮助用户分析函数内部执行的SQL,以及他们的执行计划。

10. 数据库隐藏了一些开发参数,DEBUG参数,可以帮助用户进行跟深入的诊断。

《PostgreSQL Developer Options (debug, trace, system table mod and so on…) 详解》

11. perf可以从内核层面,帮助用户分析数据库软件本身的瓶颈。

《PostgreSQL 源码性能诊断(perf profiling)指南》

33. 管理规约 - 优化

1. 大批量数据入库的优化,如果有大批量的数据入库,建议使用copy语法批量写入(减少解析和交互),或者 insert into table values (),(),…(); 的方式。 提高写入速度。

2. 不要使用delete 全表,性能很差,请使用truncate代替,(truncate是DDL语句,注意加锁等待超时)。

set lock_timeout='1s';  
  
truncate table only tbl;  -- 不清理子表,只清理当前表  

3. 两阶段提交的事务或者普通事务,要及时提交或回滚,否则可能导致数据库膨胀,年龄无法降低等问题。

postgres=# select * from pg_prepared_xacts ;  
 transaction | gid | prepared | owner | database   
-------------+-----+----------+-------+----------  
(0 rows)  

4. 应用程序一定要开启autocommit,同时避免应用程序自动begin事务,并且不进行任何操作的情况发生,某些框架可能会有这样的问题。

5. 高并发的应用场合,务必使用绑定变量(prepared statement),防止数据库硬解析消耗过多的CPU资源。

6. 秒杀场景,一定要使用 advisory_lock先对记录的唯一ID进行锁定,拿到AD锁再去对数据进行更新操作。 拿不到锁时,可以尝试重试拿锁。

update tbl set x=? where id=? and pg_try_advisory_xact_lock(?);  

7. 在函数中,或程序中,不要使用count(*)判断是否有数据,很慢。 建议的方法是select 1 from table where … limit 1; 然后使用FOUND变量的值判断前面这条QUERY有没有结果返回来判断是否有数据。

create or replace function .....  
declare  
  ...  
begin  
  ...  
perform 1 from tbl where ... limit;  
if FOUND then  
  ...  
else  
  ...  
end if;  
...  

8. 分页评估,不需要精确分页数时,请使用快速评估分页数的方法(explain的评估)。

《妙用explain Plan Rows快速估算行 - 分页数估算》

9. 避免频繁创建和删除临时表,以减少系统表资源的消耗,因为创建临时表会产生元数据,频繁创建,元数据可能会出现碎片。

10. 可以预估SQL执行时间的操作,建议设置语句级别的超时,可以防止雪崩,也可以防止长时间持锁。

set statement_timeout = '10ms';  
....  

11. TRUNCATE TABLE 在功能上与不带 WHERE 子句的 DELETE 语句相同:二者均删除表中的全部行。但 TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少,但是TRUNCATE是DDL,锁粒度很大,故不建议在开发代码中使用DDL语句,除非加了lock_timeout锁超时的会话参数或事务参数。

12. PostgreSQL支持DDL事务,支持回滚DDL,建议将DDL封装在事务中执行,必要时可以回滚,但是需要注意事务的长度,避免长时间堵塞DDL对象的读操作。

begin;  
...  
DDL;  
...  
end;  

13. 如果用户需要在插入数据和,删除数据前,或者修改数据后马上拿到插入或被删除或修改后的数据,建议使用insert into .. returning ..; delete .. returning ..或update .. returning ..; 语法。减少数据库交互次数。

insert into .. returning ..;   
  
delete .. returning ..;  
  
update .. returning ..;  

14. 自增字段建议使用序列,序列分为2字节,4字节,8字节几种(serial2,serial4,serial8)。按实际情况选择。 禁止使用触发器的方式产生序列值。

15. 树形查询应该使用递归查询,尽量减少数据库的交互或JOIN。

with recursive ....  
;  

16. 批量导入、迁移数据时。推荐的步骤:

建表(不要建索引,约束,主键);  
  
关闭autovacuum;  
  
导入;  
  
改大maintenance_work_mem;  
  
创建索引、约束、主键;  
  
analyze 表;  
  
打开autovacuum。  

17. 在函数中处理批量数据时,中间结果集尽量使用数组变量来代替临时表。如果临时表有非常庞大的数据时,才考虑使用临时表。

18. GIN索引的写优化,因为GIN的索引列通常是多值列,所以一条记录可能影响GIN索引的多个页,为了加快数据插入和更新删除的速度,建议打开fastupdate,同时设置合适的gin_pending_list_limit(单位KB)。autovacuum,vacuum,或者cache满时会自动合并,为了提高查询效率,可以将cache合并后进行查询。

《PostgreSQL 10 GIN索引 锁优化》

19. BRIN索引优化,根据数据的相关性,以及用户需求的查询的范围,设置合适的pages_per_range=n

20. b-tree索引优化,不建议对频繁访问的数据上使用非常离散的数据,例如UUID作为索引,索引页会频繁的分裂,重锁,重IO和CPU开销都比较高。

《PostgreSQL 优化CASE - 无序UUID性能问题诊断》

34. 管理规约 - 备份与恢复

1. 备份分为逻辑备份和物理备份,逻辑备份支持跨版本恢复,物理备份不支持跨版本恢复。

2. 逻辑备份不支持时间点恢复,逻辑备份为库级一致性备份。

3. 逻辑备份需要持有表的共享锁,与DDL会冲突,因此如果业务系统有可能执行DDL时,不建议使用逻辑备份。

4. 物理备份需要开启归档,备份时需要备份数据文件以及归档。

5. 物理备份不会堵塞任何SQL操作。

6. 小库,没有时间点恢复需求时,可以使用逻辑备份。

7. 大库,有时间点恢复需求时,请使用物理备份。

8. 物理备份的恢复速度取决于需要APPLY多少归档日志,有恢复时间SLA要求的场景,请评估好全量备份与归档备份的时间间隔。

9. 对于归档生成速度很快的数据库实例,建议使用快照的方式备份全量数据,或者使用数据库的块级别增量的方式备份。加快恢复的速度。

35. 管理规约 - 日常维护

1. 长事务,两阶段事务。长事务,长时间未关闭的两阶段事务,会导致数据库膨胀,无法冻结,年龄无法下降的问题。务必监控,必要时杀死(例如不正常跑几天的事务的情况)。

2. TOP SQL。通过关注TOP SQL,逐个优化。

3. VACUUM freeze 风暴。数据库通过vacuum freeze降年龄,如果大表同时爆发freeze,会导致大量写REDO,数据文件,IO暴增。必须预测和freeze smooth化。

《PostgreSQL的”天气预报” - 如何预测Freeze IO风暴》

《PostgreSQL 大表自动 freeze 优化思路》

4. 年龄。必须关注数据库的年龄,不能大于20亿。(虽然数据库会自动降年龄)。

select relkind, relname, age(relfrozenxid), pg_size_pretty(pg_total_relation_size(oid)) from pg_class where relkind in ('r','m','p') order by age(relfrozenxid) desc limit 100;  

5. 膨胀。必须关注表、索引的膨胀。超出设置的阈值时,应该找原因,同时vacuum full(DDL)或者使用pg_repack处理膨胀。

《PostgreSQL 收缩膨胀表或索引 - pg_squeeze or pg_repack》

6. sort(cluster)。如果频繁的访问范围数据,应该对该列执行cluster进行heap sort。减少离散IO和IO放大的问题。

cluster tbl using index_name;  

7. 瓶颈分析。关注LONG SQL,TOP SQL,perf。找出瓶颈,分析原因。

8. 读写分离。有必要时,可以使用物理流复制,逻辑流复制的方式建立备库,使用中间件或业务程度多数据源的方式,实现读写分离。同时关注备库的健康和延迟。

9. 垂直拆分。不同的业务,不相干的业务,当发展到单库瓶颈时,建议垂直拆分。

10. 水平分库。同一个业务,不相干的数据,当发展到单库瓶颈时,建议水平拆分。

11. 任务。关注数据库任务的执行状态,成功与否,错误原因。

《PostgreSQL Oracle 兼容性之 - DBMS_JOBS - Daily Maintenance - Timing Tasks(pgagent)》

12. HA健康。关注HA的健康,心跳,备库的延迟等。

13. 监控。监控各项用户或业务关心的指标。

14. 死锁。关注日志中输出的死锁信息,找出业务逻辑问题。

15. QUERY响应时间变化。关注重点QUERY(TOP SQL)的响应时间的变化,找出问题。

16. 表空间增速。关注表空间增速,提前做好扩容,迁移对象或表空间的准备。

17. 数据库增速。关注数据库的增速,提前做好扩容,清理、迁移数据的准备。

18. 对象增速。关注对象增速,提前做好扩容,分区的准备。

19. 分区表。关注分区表的边界是否快到达,提前创建分区。

20. 日志文件增速。日志文件(指错误日志),及时压缩归档和清理。

21. 临时文件。数据库某些查询会使用临时文件,关注临时文件的大小,找到异常QUERY(例如递归死循环),通过加大work_mem或者其他方式减少临时文件的使用。使用参数控制单个QUERY的临时文件的上限。

22. 入侵检测。分析日志,找到SQL注入,找到密码暴力破解的安全问题。

23. 日志文件分析,错误类型统计。从日志中,分析出错误日志,找到业务逻辑的问题,数据库本身的问题等。

24. 不使用的索引。通过pg_stat*统计信息,找到不使用的索引,删除。

25. 不合理的全表扫描。通过auto_explain,慢SQL,TOP SQL,找出给系统带来负担的不合理全表扫描SQL,优化之。

36. 开箱即用,用为上计

Flag Counter

digoal’s 大量PostgreSQL文章入口