PostgreSQL规范

简介:PostgreSQL是一个功能非常强大的、源代码开放(自由软件)的客户/服务器关系型数据库管理系统

命名规范

|
|--库名、表名限制命名长度,建议表名及字段名字符总长度小于等于63
|
|--对象名务必只使用小写字母,下划线,数字。不要以pg开头,不要以数字开头,不要使用保留字
|
|--query中的别名不要使用 "小写字母,下划线,数字" 以外的字符,例如中文
|
|--主键索引应以 pk_ 开头, 唯一索引要以 uk_ 开头,普通索引要以 idx_ 打头
|
|--临时表以 tmp_ 开头,子表以规则结尾,例如按年分区的主表如果为tbl, 则子表为tbl_2016,tbl_2017,...
|
|--库名最好以部门名字开头 + 功能(或应用名称),如 xxx_yyy,xxx_zzz,便于辨识, 。。。
|
|--不建议使用public schema,应该为每个应用分配对应的schema,schema_name最好与user name一致
|
|--comment不要使用中文,因为编码可能不一样,如果存进去和读取时的编码不一致,导致可读性不强
|
|--pg_dump时也必须与comment时的编码一致,否则可能乱码

设计规范

|
|--多表中的相同列,必须保证列名一致,数据类型一致
|
|--索引字段不建议超过2000字节,如果有超过2000字节的字段需要建索引,建议使用函数索引(例如哈希值索引),或者使用分词索引
|
|--使用外键时,如果你使用的PG版本没有自动建立fk的索引,则必须要对foreign key手工建立索引,否则可能影响references列的更新或删除性能
|
|--使用外键时,一定要设置fk的action,例如cascade,set null,set default
|
|--对于频繁更新的表,建议建表时指定表的fillfactor=85,每页预留15%的空间给HOT更新使用
|
|--表结构中字段定义的数据类型与应用程序中的定义保持一致,表之间字段校对规则一致,避免报错或无法使用索引的情况发生
|
|--如何保证分区表的主键序列全局唯一。 使用多个序列,每个序列的步调不一样,或者每个序列的范围不一样即可
|                                    |		
|                                    |---create sequence seq_tab1 increment by 10000 start with 1;
|                                    |---create sequence seq_tab2 increment by 10000 start with 2;
|                                    |---create sequence seq_tab3 increment by 10000 start with 3;
|
|--建议有定期历史数据删除需求的业务,表按时间分区,删除时不要使用DELETE操作,而是DROP或者TRUNCATE对应的表。
|
|--为了全球化的需求,所有的字符存储与表示,均以UTF-8编码,那么字符计数方法注意|---select length('阿里巴巴');      //4
|																			  |---select octet_length('阿里巴巴');//12
|                             
|--对于值与堆表的存储顺序线性相关的数据,如果通常的查询为范围查询,建议使用BRIN索引:create index idx on tbl using brin(id);     
|
|--设计时应尽可能选择合适的数据类型,能用数字的坚决不用字符串,能用树类型的,坚决不用字符串
|
|--应该尽量避免全表扫描(除了大数据量扫描的数据分析),PostgreSQL支持几乎所有数据类型的索引
|
|--对于网络复杂并且RT要求很高的场景,如果业务逻辑冗长,应该尽量减少数据库和程序之间的交互次数,尽量使用数据库存储过程(如plpgsql),或内置的函数
|
|--应用应该尽量避免使用数据库触发器,这会使得数据处理逻辑复杂,不便于调试
|
|--如果应用经常要访问较大结果集的数据(例如100),可能造成大量的离散扫描。 建议想办法将数据聚合成1,例如经常要按ID访问这个ID的数据,建议可以定期按ID聚合这些数据
|
|--流式的实时统计,为了防止并行事务导致的统计空洞,建议业务层按分表并行插入,单一分表串行插入
|
|--范围查询,应该尽量使用范围类型,以及GIST索引,提高范围检索的查询性能
|
|--未使用的大对象,一定要同时删除数据部分,否则大对象数据会一直存在数据库中,与内存泄露类似;vacuumlo可以用来清理未被引用的大对象数据
|
|--对于固定条件的查询,可以使用部分索引,减少索引的大小,同时提升查询效率:create index idx on tbl (col) where id=1; 
|
|--对于经常使用表达式作为查询条件的语句,可以使用表达式或函数索引加速查询:create index idx on tbl ( exp )
|
|--如果需要调试较为复杂的逻辑时,不建议写成函数进行调试,可以使用plpgsql的online code
|
|--当业务有中文分词的查询需求时,建议使用PostgreSQL的分词插件zhparser或jieba,用户还可以通过接口自定义词组。建议在分词字段使用gin索引,提升分词匹配的性能
|
|--当用户有规则表达式查询,或者文本近似度查询的需求时,建议对字段使用trgm的gin索引,提升近似度匹配或规则表达式匹配的查询效率,同时覆盖了前后模糊的查询需求。如果没有创建trgm gin索引,则不推荐使用前后模糊查询例如like %xxxx%
|
|--当用户有prefix或者 suffix的模糊查询需求时,可以使用索引,或反转索引达到提速的需求。
|
|--用户应该对频繁访问的大表(通常指超过8GB的表,或者超过1000万记录的表)进行分区,从而提升查询的效率、更新的效率、备份与恢复的效率、建索引的效率等等,(PostgreSQL支持多核创建索引后,可以适当将这个限制放大)
|
|--用户在设计表结构时,建议规划好,避免经常需要添加字段,或者修改字段类型或长度。 某些操作可能触发表的重写,例如加字段并设置默认值,修改字段的类型。如果用户确实不好规划结构,建议使用jsonb数据类型存储用户数据。

QUERY 规范

|
|-不要使用count(列名)count(常量)来替代count(),count()就是SQL92定义的标准统计行数的语法,跟数据库无关,跟NULL和非NULL无关
| 说明:count(*)会统计NULL值(真实行数),count(列名)不会统计。
|
|-count(多列列名),多列列名必须使用括号,例如count( (col1,col2,col3) )。注意多列的count,即使所有列都为NULL,该行也被计数,所以效果与count(*)一致
|
|-count(distinct col) 计算该列的非NULL不重复数量,NULL不被计数
|
|-count(distinct (col1,col2,...) ) 计算多列的唯一值时,NULL会被计数,同时NULL与NULL会被认为是想同的
|
|-count(col)"是NULL的col列" 返回为0,sum(col)则为NULL
| 因此注意sum(col)的NPE问题,如果你的期望是当SUM返回NULL时要得到0,可以这样实现//SELECT coalesce( SUM(g)), 0, SUM(g) ) FROM table;  
|
|-NULL是UNKNOWN的意思,也就是不知道是什么。 因此NULL与任意值的逻辑判断都返回NULL
|
|-除非是ETL程序,否则应该尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理
|
|-任何地方都不要使用 select * from t ,用具体的字段列表代替,不要返回用不到的任何字段。另外表结构发生变化也容易出现问题

管理规范

|
|-数据订正时,删除和修改记录时,要先select,避免出现误删除,确认无误才能提交执行
|
|-DDL操作必须设置锁等待,可以防止堵塞所有其他与该DDL锁对象相关的QUERY
|            |
|            |-- begin;  
|            |-- 	set local lock_timeout = '10s';  
|            |-- 	-- DDL operate;  
|            |-- end;
|            |
|-用户可以使用explain analyze查看实际的执行计划,但是如果需要查看的执行计划涉及数据的变更,必须在事务中执行explain analyze,然后回滚。
|            |
|            |-- begin;  
|            |-- 	explain analyze query; 
|            |-- rollback;
|
|-如何并行创建索引,不堵塞表的DML,创建索引时加CONCURRENTLY关键字,就可以并行创建,不会堵塞DML操作,否则会堵塞DML操作
|								   create index CONCURRENTLY idx on tbl(id); 
|
|-为数据库访问账号设置复杂密码
|
|-业务系统,开发测试账号,不要使用数据库超级用户。非常危险。
|
|-多个业务共用一个PG集群时,建议为每个业务创建一个数据库。 如果业务之间有数据交集,或者事务相关的处理,强烈建议在程序层处理数据的交互。
|
|-应该为每个业务分配不同的数据库账号,禁止多个业务共用一个数据库账号
|
|-在发生主备切换后,新的主库在开放给应用程序使用前,建议使用pg_prewarm预热之前的主库shared buffer里的热数据
|
|-快速的装载数据的方法,关闭autovacuum, 删除索引,数据导入后,对表进行analyze同时创建索引
|
|-如何加快创建索引的速度,调大maintenance_work_mem,可以提升创建索引的速度,但是需要考虑实际的可用内存。
|                         |
|                         |-- begin;  
|                         |-- 	set local maintenance_work_mem='2GB';  
|                         |-- 	create index idx on tbl(id);  
|                         |-- end; 
|
|-防止长连接,占用过多的relcache, syscache
|
|-大批量数据入库的优化,如果有大批量的数据入库,建议使用copy语法,或者 insert into table values (),(),...(); 的方式。 提高写入速度

稳定性与性能规范

|
|-在代码中写分页查询逻辑时,若count为0应直接返回,避免执行后面的分页语句
|
|-游标使用后要及时关闭
|
|-两阶段提交的事务,要及时提交或回滚,否则可能导致数据库膨胀
|
|-不要使用delete 全表,性能很差,请使用truncate代替(truncate是DDL语句)
|
|-应用程序一定要开启autocommit,同时避免应用程序自动begin事务,并且不进行任何操作的情况发生,某些框架可能会有这样的问题
|
|-高并发的应用场合,务必使用绑定变量(prepared statement),防止数据库硬解析消耗过多的CPU资源
|
|-不要使用hash index,目前hash index不写REDO,在备库只有结构,没有数据,并且数据库crash后无法恢复
| 同时不建议使用unlogged table ,道理同上,但是如果你的数据不需要持久化,则可以考虑使用unlogged table来提升数据的写入和修改性能
|
|-秒杀场景,一定要使用 advisory_lock先对记录的唯一ID进行锁定,拿到AD锁再去对数据进行更新操作。 拿不到锁时,可以尝试重试拿锁
|
|-在函数中,或程序中,不要使用count(*)判断是否有数据,很慢。 建议的方法是limit 1;//select 1 from tbl where xxx limit 1; 
|
|-对于高并发的应用场景,务必使用程序的连接池,否则性能会很低下。 
| 如果程序没有连接池,建议在应用层和数据库之间架设连接池,例如使用pgbouncer或者pgpool-II作为连接池
|
|-当业务有近邻查询的需求时,务必对字段建立GIST或SP-GIST索引,加速近邻查询的需求。 
|                           |
|                           |-- create index idx on tbl using gist(col);  
|                           |-- select * from tbl order by col <-> '(0,100)'; 
|
|-避免频繁创建和删除临时表,以减少系统表资源的消耗,因为创建临时表会产生元数据,频繁创建,元数据可能会出现碎片
|
|-必须选择合适的事务隔离级别,不要使用越级的隔离级别,例如READ COMMITTED可以满足时,就不要使用repeatable read和serializable隔离级别
|
|-高峰期对大表添加包含默认值的字段,会导致表的rewrite,建议只添加不包含默认值的字段,业务逻辑层面后期处理默认值
|
|-分页评估,不需要精确分页数时,请使用快速评估分页数的方法。
|
|-分页优化,建议通过游标返回分页结果,避免越后面的页返回越慢的情况
|
|-可以预估SQL执行时间的操作,建议设置语句级别的超时,可以防止雪崩,也可以防止长时间持锁
|
|-PostgreSQL支持DDL事务,支持回滚DDL,建议将DDL封装在事务中执行,必要时可以回滚,但是需要注意事务的长度,避免长时间堵塞DDL对象的读操作
|
|-如果用户需要在插入数据和,删除数据前,或者修改数据后马上拿到插入或被删除或修改后的数据,
| 建议使用insert into .. returning ..; delete .. returning ..或update .. returning ..; 语法。减少数据库交互次数
|
|-自增字段建议使用序列,序列分为2字节,4字节,8字节几种(serial2,serial4,serial8)。按实际情况选择。 禁止使用触发器产生序列值。
| 
|-果对全表的很多字段有任意字段匹配的查询需求,建议使用行级别全文索引,或行转数组的数组级别索引。
|
|-中文分词的token mapping一定要设置,否则对应的token没有词典进行处理。 
|
|-树形查询应该使用递归查询,尽量减少数据库的交互或JOIN
|
|-对于有UV查询需求的场景(例如count(distinct xx) where time between xx and xx),
| 如果要求非常快的响应速度,但是对精确度要求不高时,建议可以使用PostgreSQL的估值数据类型HLL
|
|-如果用户经常需要访问一张大表的某些数据,为了提升效率可以使用索引,但是如果这个数据还需要被用于更复杂的与其他表的JOIN操作,则可以使用物化视图来提升性能
|      |
|      |--CREATE MATERIALIZED VIEW mv_tbl as select xx,xx,xx from tbl where xxx with data; 
|      |--REFRESH MATERIALIZED VIEW CONCURRENTLY mv_tbl with data;  //增量刷新物化视图
|      
|-不建议对宽表频繁的更新,原因是PG目前的引擎是多版本的,更新后会产生新的版本,如果对宽表的某几个少量的字段频繁更新,其实是存在写放大的
|
|-使用窗口查询减少数据库和应用的交互次数
|
|-应该尽量在业务层面避免死锁的产生,例如一个用户的数据,尽量在一个线程内处理,而不要跨线程(即跨数据库会话处理)
|
|-OLTP系统不要频繁的使用聚合操作,聚合操作消耗较大的CPU与IO资源。例如实时的COUNT操作,如果并发很高,可能导致CPU资源撑爆
|
|-线上表结构的变更包括添加字段,索引操作在业务低峰期进行。
|
|-OLTP系统,在高峰期或高并发期间 拒绝 长SQL,大事务,大批量。
|
|-查询条件要和索引匹配,例如查询条件是表达式时,索引也要是表达式索引,查询条件为列时,索引就是列索引。
|
|-如何判断两个值是不是不一样(并且将NULL视为一样的值),使用col1 IS DISTINCT FROM col2 
|
|-如果在UDF或online code逻辑中有数据的处理需求时,建议使用游标进行处理。 
|
|-应尽量避免在 where 子句中使用!=<>操作符,否则将引擎放弃使用索引而进行全表扫描。 
|
|-对于经常变更,或者新增,删除记录的表,应该尽量加快这种表的统计信息采样频率,获得较实时的采样,输出较好的执行计划。
|
|-PostgreSQL 对or的查询条件,会使用bitmap or进行索引的过滤,所以不需要改SQL语句,可以直接使用。 
|
|-很多时候用 exists 代替 in 是一个好的选择
|
|-尽量使用数组变量来代替临时表。如果临时表有非常庞大的数据时,才考虑使用临时表。
|
|-对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。 
|
|-GIN索引的写优化,因为GIN的索引列通常是多值列,所以一条记录可能影响GIN索引的多个页,
|
| 为了加快数据插入和更新删除的速度,建议打开fastupdate,同时设置合适的gin_pending_list_limit(单位KB)|
| 这么做的原理是,当变更GIN索引时,先记录在PENDING列表,而不是立即合并GIN索引。从而提升性能。
|
|-b-tree索引优化,不建议对频繁访问的数据上使用非常离散的数据,例如UUID作为索引,索引页会频繁的分裂,重锁,重IO和CPU开销都比较高
|
|-BRIN索引优化,根据数据的相关性,以及用户需求的查询的范围,设置合适的pages_per_range=n。
|
|-如果你是阿里云RDS PGSQL的用户,推荐你参考一下规范,阿里云RDS PGSQL提供了很多有趣的特性帮助用户解决社区版本不能解决的问题。

你可能感兴趣的:(关系型数据库)