[TOC]
没有规矩,不成方圆。
PostgreSQL的功能非常强大,但是要把PostgreSQL用好,需要后端、运维、DBA的协力配合。
本文针对PostgreSQL数据库原理与特性,整理了一份开发/运维规约,希望可以减少大家在使用PostgreSQL数据库过程中遇到的困惑:你好我也好,大家都好。
本文第一版主要针对 PostgreSQL 9.4 - PostgreSQL 10 版本 ,当前最新版本针对 PostgreSQL 15/16 进行更新与调整。
计算机科学只存在两个难题:缓存失效和命名。
通用命名规则(Generic)
snake_case 风格。select pg_get_keywords(); 获取保留关键字列表。$ ,禁止使用中文,不要以 pg 开头。集群命名规则 (Cluster)
[a-z][a-z0-9-]*。pg-<biz>-<tld>。数据库类型 / 业务名称 / 业务线或环境biz 为最能代表业务特征的英文词语,应当仅由小写字母与数字组成,不得包含连字符 -。biz 名应当为 <biz>delay,例如 pg-testdelay。biz 尾部添加数字:例如从 pg-user1 可以分支出 pg-user2,pg-user3 等biz命名中应当包含 shard,并缀以分片号,例如 pg-testshard1,pg-testshard2,……<tld> 为顶层业务线,也可用于区分不同环境:例如 -tt,-dev,-uat,-prod 等。无此需要可以省略。服务命名规则(Service)
pg-test-primary,pg-test-replica。primary 后缀命名,只读服务统一以 replica 后缀命名,这两个为必选服务。offline 后缀命名,直连主库/ETL写入以 default 后缀命名,为选配服务。standby 后缀命名,延迟从库服务以 delayed 后缀命名,少量核心库可提供此服务。实例命名规则(Instance)
- 拼接而成,例如: pg-test-1,pg-test-2。ins 标签,附加到该实例的所有数据上。数据库命名规则(Database)
<tld>_<biz> 的形式构建,<tld> 为顶层业务线,也可用于区分不同环境,不用可以省略。<biz>为具体业务名称,例如 pg-test-tt 集群可以使用库名 tt_test 或 test。这一点不强制,即允许创建不同于集群 <biz> 名称的其他数据库。<biz> 部分必须以shard结尾,但不应当包含分片号,例如 pg-testshard1,pg-testshard2 都用 testshard 即可。-连接。例如:<biz>-chat-shard,<biz>-payment等,总共不超过三段。角色命名规范(Role/User)
dbsu 有且仅有一个:postgres,用于流复制的用户命名为replicator。dbuser_monitor,用于日常管理的超级用户为:dbuser_dba。dbuser_<biz> 作为用户名,例如 dbuser_test。来自不同服务的访问应当使用独立的业务用户区分访问。dbp_<name>,其中 name 为 LDAP 中的标准用户名。dbrole_readonly,dbrole_readwrite,dbrole_admin,dbrole_offline。模式命名规则(Schema)
<prefix> 作为模式名,尽可能简短,默认设置为search_path 首位元素。<prefix> 不得使用 public,monitor ,不得与任何 PostgreSQL 扩展使用的模式名冲突,例如: timescaledb,citus,repack,graphql,net,cron,…… 不宜使用特殊名称:dba,trash。rel_<partition_total_num>_<partition_index>。中间为总分片数,目前固定使用 8192 ,后缀是分片号,从0开始计数。如 rel_8192_0,…… ,rel_8192_11,等等。<prefix> 之外的模式名需要由研发解释其必要性。关系命名规则(Relation)
v_作为命名前缀,物化视图使用mv_作为命名前缀,临时表以tmp_作为命名前缀。tbl_2023,月级分区 tbl_202304,天级分区 tbl_20230405,小时级分区 tbl_2023040518 ,默认分区以 _default 结尾。索引命名规则(Index)
_pkey 结尾,唯一索引以 _key 结尾,普通索引以 _idx 结尾,用于EXCLUDED约束的索引以_excl结尾。tbl_md5_title_idx,tbl_ts_ge_2023_idx,但不可超出长度限制。字段命名规则(Attribute)
oid, xmin, xmax,cmin,cmax,ctid 。id,或以id作为后缀。created_time,最后修改时间惯用名为 updated_timeis_,has_ 等作为前缀。extra 作为列名。枚举项命名 (Enum)
camelCase,但也允许其他风格。函数命名规则(Function)
select,insert,delete,update,upsert,create ,……。_by_ids,_by_user_ids的后缀在函数名中体现。BIGINT/INTEGER/SMALLINT 等整型进行函数签名重载,调用时可能产生歧义。$1,$2,…)。_,例如_user_id。注释规范(Comment)
COMMENT),注释使用英文,言简意赅,一行为宜。Suum cuique
建表注意事项
EXCLUDE约束是泛化的唯一约束,可以在低频更新场景下用于保证数据完整性。分区表注意事项
宽表注意事项
主键注意事项
PRIMARY KEY(a,b,...) 单列指定。UUID 与长度受限的文本类型,使用其他类型需要显式说明与评估。BIGINT,谨慎使用 INTEGER,不允许使用 SMALLINT。GENERATED ALWAYS AS IDENTITY 生成唯一主键;SERIAL,BIGSERIAL 仅当需要兼容 PG 10 以下版本时允许使用。UUID 类型作为主键,建议用 UUID v1/v7;谨慎使用 UUIDv4 作为主键,随机 UUID 的局部性较差且有碰撞概率。VARCHAR(64),使用更长的字符串时应当进行说明与评估。INSERT/UPDATE 时原则上禁止修改主键列的值,INSERT RETURNING 可用于返回自动生成的主键值。外键注意事项
SET NULL, SET DEFAULT, CASCADE,慎用级联操作。空值/默认值注意事项
NOT NULL约束。DEFAULT 默认值。数值类型注意事项
INTEGER。容量拿不准的数值列使用BIGINT。SMALLINT,性能与存储提升甚小,但会有很多额外的问题。INTMAX但没超过UINTMAX的值需要升格存储。不要存储超过INT64MAX的值到BIGINT列中,会溢出为负数。REAL 表示4字节浮点数,FLOAT 表示8字节浮点数。浮点数仅可用于末尾精度无所谓的场景,例如地理坐标。切记不要对浮点数使用等值判断,零值除外。NUMERIC,如果可行,请用 NUMERIC(p) 与NUMERIC(p,s) 设置有效数字位数以及小数部分的有效位数。例如摄氏气温(37.0)可以用 NUMERIC(3,1) 类型来存储3位有效数字与1位小数。MONEY。文本类型注意事项
char(n), varchar(n), text。默认情况下,可以使用 text 类型 ,不限制字符串长度,但受字段最大长度1GB限制。varchar(n) 类型来设置一个最大字符串长度,这会引入极微小的额外检查开销,但能规避一些脏数据与极端情况。char(n),该类型为了与SQL标准兼容,存在不合直觉的行为表现(补齐空格与截断),且并没有存储和性能优势。时间类型注意事项
TIMESTAMPTZ,不带时区的 TIMESTAMP 。TIMESTAMPTZ,如果使用 TIMESTAMP 存储,必须使用0时区标准时。now() AT TIME ZONE 'UTC' ,不能直接截断时区 now()::TIMESTAMP。2006-01-02 15:04:05,避免DMY与MDY问题。Asia/Hong_Kong +8 时区,因为上海时区缩写 CST 有歧义。枚举类型注意事项
ALTER TYPE <enum_name> 用于修改枚举。UUID类型注意事项
uuid-ossp ,pg_uuidv7 等,有此需求请在配置时指明。JSON类型注意事项
JSONB 类型与相关函数,而非文本版本的 JSON。text 类型中不允许出现 \u0000 零字符,与 JSON 数值类型对应的 numeric 中不允许出现 NaN 与 infinity。布尔值只接受小写的 true 与 false 字面值。null对象和 SQL 标准中的空值 NULL 并非同一个概念。数组类型注意事项
pgvector 扩展提供的专用数据类型。GIS类型注意事项
触发器注意事项
updated_time 为当前时间戳,或者将表的增删改动作记录到另一张日志表中,或者维持两张表在业务上的一致性。存储过程/函数注意事项
IMMUTABLE, STABLE, VOLATILE。添加属性标签,如:RETURNS NULL ON NULL INPUT,PARALLEL SAFE,ROWS 1 等。CREATE OR REPLACE FUNCTION nspname.myfunc(arg1_ TEXT, arg2_ INTEGER) RETURNS VOID LANGUAGE SQL STABLE PARALLEL SAFE ROWS 1 RETURNS NULL ON NULL INPUT AS $function$ SELECT 1; $function$;
使用合理的Locale选项
en_US.UTF8,没有特殊理由不得更改。collate 规则必须为 C ,避免字符串索引问题。使用合理的字符编码与本地化配置
UTF8 字符编码,严格禁止使用其他任何字符编码。C 作为 LC_COLLATE 默认排序规则,有特殊需求必须在DDL/查询子句中显式指定来实现。LC_CTYPE 默认使用 en_US.UTF8,一些扩展依赖字符集信息方可正常工作,如 pg_trgm 。索引相关注意事项
明确B树索引空值的顺序
NULLS FIRST还是NULLS LAST。DESC排序的默认规则是NULLS FIRST,即空值会出现在排序的最前面,通常这不是期望行为。CREATE INDEX ON tbl (id DESC NULLS LAST);禁止在大字段上建立索引
varchar(n) 类型。充分利用函数索引
reverse 函数索引。充分利用部分索引
The limits of my language mean the limits of my world.
—Ludwig Wittgenstein
使用服务接入
读写分离
快慢分离
使用连接池
为查询语句配置主动超时
关注复制延迟
重试失败的事务
掉线重连
SELECT 1作为心跳包查询,检测连接的有消息,并定期保活。在线服务应用代码禁止执行DDL
SELECT语句显式指定列名
SELECT *,或在RETURNING子句中使用*。请使用具体的字段列表,不要返回用不到的字段。当表结构发生变动时(例如,新值列),使用列通配符的查询很可能会发生列数不匹配的错误。id 升级为 BIGINT 后,id 的列顺序会到最后一列。此问题只能在维护迁移时择机修复,研发应当克制调整列顺序的强迫症,并在 SELECT 语句中显式指定列的顺序禁止在线查询全表扫描
!=, <>的否定式操作符会导致全表扫描,必须避免。禁止在事务中长时间等待
IDEL IN Transaction将被强制杀死。BEGIN之后没有配对的ROLLBACK或COMMIT。使用 count 计数时的注意事项
count(*)是统计行数的标准语法,与空值无关。count(col)统计的是col列中的非空记录数。该列中的NULL值不会被计入。count(distinct col) 对col列除重计数,同样忽视空值,即只统计非空不同值的个数。count((col1, col2))对多列计数,即使待计数的列全为空也会被计数,(NULL,NULL)有效。a(distinct (col1, col2))对多列除重计数,即使待计数列全为空也会被计数,(NULL,NULL)有效。使用聚合函数的注意事项
count之外的所有聚合函数都会忽略空值输入,因此当输入值全部为空时,结果是NULL。但count(col)在这种情况下会返回 0,是一个例外。coalesce 来设置缺省值。谨慎处理空值
IS NULL进行等值判断,零值使用常规的=运算符进行等值判断。unknown,需要注意unknown参与布尔运算的逻辑:
and:TRUE or UNKNOWN会因为逻辑短路返回TRUE。or:FALSE and UNKNOWN会因为逻辑短路返回FALSEUNKNOWN,结果都是UNKNOWNNULL=NULL返回结果是NULL而不是TRUE/FALSE。 进行比较,保证比较结果非空。注意序列号空缺
Serial类型时,INSERT,UPSERT等操作都会消耗序列号,该消耗不会随事务失败而回滚。INTEGER 作为主键,且表存在频繁插入冲突时,需要关注整型溢出的问题。使用游标后必须及时关闭
重复查询使用准备语句
选择合适的事务隔离等级
r h 09判断结果存在性不要使用count
SELECT 1 FROM tbl WHERE xxx LIMIT 1判断是否存满足条件的列,要比Count快。SELECT exists(SELECT * FROM tbl WHERE xxx LIMIT 1)将存在性结果转换为布尔值。使用RETURNING子句一次性取回修改后的结果
RETURNING 子句可以在 INSERT,UPDATE,DELETE 语句后使用,有效减少数据库交互次数。使用UPSERT简化逻辑
UPSERT替代。利用咨询锁应对热点并发。
优化IN操作符
EXISTS 子句代替IN操作符,性能更佳。=ANY(ARRAY[1,2,3,4]) 代替 IN (1,2,3,4),效果更佳。不建议使用左模糊搜索
WHERE col LIKE '%xxx'无法充分利用B树索引,如有需要,可用reverse表达式函数索引。使用数组代替临时表
=ANY(ARRAY[1,2,3]) 要比临时表JOIN好。使用 Pigsty 搭建 PostgreSQL 集群与基础设施
pigsty.yml 配置文件通常包含了高度敏感的重要机密信息,应当使用 git 进行版本化管理,并严格控制访问权限。files/pki 内生成的 CA 私钥与其他证书应当妥善保管,定期将备份至安全区域存储归档,并严格控制访问权限。监控系统是必选项
根据需求合理规划集群架构
oltp 模板,分析类数据库使用 olap 模板,财务库使用 crit 模板,小微虚拟机(四核内)使用 tiny 模板。使用 Patroni 与 Etcd 配置集群高可用
etcd 使用专用虚拟机集群,3 ~ 5 个节点,严格打散分布在不同机柜上。使用 pgBackRest 与 MinIO 配置集群PITR
核心业务数据库配置注意事项
财务数据库配置注意事项
crit 模板,启用同步提交确保数据零丢失,视情况启用 Watchdog。使用合理的字符编码与本地化配置
UTF8 字符编码,严格禁止使用其他任何字符编码。C 作为 LC_COLLATE 默认排序规则,有特殊需求必须在DDL/查询子句中显式指定来实现。LC_CTYPE 默认使用 en_US.UTF8,一些扩展依赖字符集信息方可正常工作,如 pg_trgm 。业务数据库管理注意事项
业务用户管理注意事项
pg_offline_query 的从库。扩展插件管理注意事项
yum/apt 安装对应大版本的扩展插件二进制软件包。shared_preload_libraries ,如果需要应当安排滚动重启。shared_preload_libraries 优先级顺序, citus, timescaledb,pgml 通常要放在最前面。pg_stat_statements 与 auto_explain 是必选插件,必须在所有集群中启用。dbsu 进行,在业务数据库中 CREATE EXTENSION 执行创建。数据库XID与年龄注意事项
VACUUM FREEZE。数据库表与索引膨胀注意事项
pg_repack 在线处理表/索引膨胀问题。数据库重启注意事项
CHECKPOINT 两次,强制脏页刷盘,可加速重启过程。pg_ctl reload 重载配置确认配置文件正常可用。pg_ctl restart 或 patronictl 同时重启整个集群。kill -9 关闭任何数据库进程。复制延迟注意事项
新从库数据预热
pg_prewarm 将热数据加载至内存。数据库发布流程
数据工单格式
xx 库执行 yy 动作。数据库变更发布注意事项
dbuser_dba 远程执行,确保默认权限正常工作。SET ROLE dbrole_admin 后再执行发布,确保默认权限。psql 命令行工具,连接到集群主库执行,使用 \i 执行脚本或 \e 手工分批执行。删除表注意事项
DROP 应当首先重命名,冷却 1~3 天确认没有访问后再移除。trash Schema 中,通过 ALTER TABLE SET SCHEMA 修改模式名。创建与删除索引注意事项
CREATE INDEX CONCURRENTLY 并发创建索引,使用 DROP INDEX CONCURRENTLY 并发移除索引。INVALID 的索引,修改索引后,使用 analyze 重新收集表上的统计数据。maintenance_work_mem 为更大的值加速索引创建。审慎地进行模式变更
VOLATILE 的函数,避免全表重写。ANALYZE 刷新统计信息。控制数据写入的批次规模
UPDATE 后,执行 VACUUM 回收死元组占用的空间。数据加载注意事项
COPY加载数据,如有需要可以并行执行。autovacuum,按需禁用触发器,并在加载完后再建立约束与索引。maintenance_work_mem,增大max_wal_size。vacuum verbose analyze table。数据库迁移、大版本升级注意事项
pg_dump | psql 逻辑导出导入的方式停机升级。数据误删/误更新处理流程
pageinspect 与 pg_dirtyread 从坏表中抢救数据。数据腐坏处理流程
auto_vacuum ,定位错误根因,替换故障磁盘并补充新从库。pg_filedump 从表二进制文件中恢复数据。dd 生成仿造提交记录。数据库连接打满注意事项
pg_cancel_backend 或 pg_terminate_backend。pg_terminate_backend 中止所有普通后端进程,从每秒一次(psql \watch 1)开始。并从监控系统确认连接情况,如果继续堆积,则不断提高杀连接查询的执行频次,例如每 0.1 秒一次,直到不再堆积为止。本文作者:Kevin@灼华
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!