设计开发规范

最近更新时间: 2024-10-17 17:10:00

设计准则

命名准则

数据库对象名命名准则

  • 数据库对象: 模式名, 表名,视图名, 字段名,索引名等名称:

  • 使用小写字母、数字、下划线的组合。

  • 禁用双引号即"包围,除非必须包含大写字母。

  • 注意TDSQL PostgreSQL 版的对象名称长度不能超过63个字符。

  • 不要以pg_开头或者pgxc_(避免与系统表,系统视图名称混淆),禁用以数字开头。

  • 禁止使用 SQL 关键字例如 type, order 等,所有关键字见TDSQL PostgreSQL 版关键字列表。

  • 尽量用英文单词,这样使用者根据英文单词也可以大概知道数据库对象的用途,不用再去查找文档;

  • 由于数据库对象名不能够超过63个字符,所以当超过63个字符时,英文单词应该缩写。

表名命名规则

同一业务的表名前缀保持一致:

保险业务相关可以写成:policy_basic,policy_xxxx。

视图命名规则

同一业务的表名前缀保持一致,视图带上对象类型为view如:

保险业务相关可以写成:policy_basic_view。

物化视图命名规则

同一业务的物化视图名前缀保持一致,物化视图的对象类型为mv如:

保险业务相关可以写成:policy_basic_mv。

序列命名规则

序列命名为【表名_字段名_seq】:如表policy_basic的id字段使用的序列

policy_basic_id_seq

主键命名规则

主键命名规则为【表名_字段名_pkey】,如果是多个字段组合成主键,则每个字段使用下划画隔开,如

policy_basic_id_pkey

唯一索引命名规则

唯一索引命名规则为【表名_字段名_uidx】,如果是多个字段组合成的唯一索引,则每个字段使用下划画隔开,如

policy_basic_id_uidx

普通索引命名规则

普通索引命名规则为【表名_字段名_idx】,如果是多个字段组合成的索引,则每个字段使用下划画隔开,如

policy_basic_id_idx

临时表命名规则

临时表命名规则为【表名_tmp】,如

policy_basic_tmp

转储表命名规则

转储表命名规则为【表名_his】,如

policy_basic_his

字段命名规则

字段名称必须用字母开头,采用有特征含义的单词或缩写,不能用双引号包含。如

inserttime,updatetime

使用准则

shard(分片表)数据表设计准则

这里的数据表包括普通表,普通分区表,冷热分区表。

  • 所有数据表都必需有主键。

  • 业务表都必需有插入时间截和最后更新时间截字段,并且在业务发生时保存对应时间截。

  • 单个表大小建议不要超过32G,有效的控制表大小方法,增加集群节点数可以降低单个表的大小,使用分区表可以降低单数据表的大小。

  • 对表名增加COMMENT中文注释,便于后续新人了解业务及维护,其它对象也需要遵守这个规则。

分区表使用准则

分区表带来的好处就是表大小得到有效控制,索引的高度低,更新性能更好,坏处就是全表扫描更慢,系统元数据更大,DDL操作开销更大,使用分区表准则如下。

  • 带有时间截字段(如插入时间)的业务数据表。

  • 对该表进行查询时,业务大多数是限制在一定时间范围内进行。

  • 对于高并发的查询业务,需要指定到某个分区范围执行查询才能发挥分区表的带来的好处。

  • 删除记录和更新记录时必须限定在某个分区表内,否则更新效率低。

冷热分区表使用准则

冷热分区表使用除了具有分区表的特性外,还有这一点要特别注意,运维成本大一些,冷热数据搬迁需要占用磁盘IO,网络带宽,热group的数据无法与冷group的数据进行跨DN库join。

  • 冷热分区表继承分区表的使用情景。

  • 历史数据查询少。

  • 历史数据存储容量大。

  • 热数据存储介质成本大,冷GROUP可以使用便宜的SAS盘,解决存储成本的问题。

  • 禁止热group和冷group join操作。

满足上面的场景的话就可以考虑使用冷热分区表。

全局表使用准则

  • 全局表是所有节点都有全量数据,对于大数据量的数据表不适合。

  • 全局表更新性能较低,控制不好容易产生死锁,对于经常更新的业务不适合使用全局表。

  • 数据经常要跨库JOIN的小数据量表可以考虑使用全局表。

序列使用准则

  • 无特别要求,禁止在系统中使用序列,主要是序列需要与GTM频繁的通信,增加了GTM负载,降低了系统TPS值

  • 使用 TDSQL PostgreSQL 版自带的UUID函数或应用程序的UUID代替。

外键使用准则

  • 无特别要求,禁止使用外键,外键对应程序和运维带来极高要求,同时也增加了数据库性能开销。

  • 可以使用事务来替代外键达到数据一致性要求。

触发器使用准则

  • 无特别要求,禁止使用触发器,触发器需要把数据拉回接入层CN计算,数据路由复杂,性能低,开销大。

  • 触发器不利于应用程序和数据库功能解偶,排错复杂。

存储过程使用准则

无特别要求,禁止在存储过程业务中使用

  • 存储过程使用增加数据库服务器的额外负担。

  • 存储过程也不利于应用程序和数据库功能解偶,业务还是放到业务代码中。

物化视图使用准则

无特殊要求,禁止使用物化视图。

  • 物化视图生成数据存放于CN节点,如果需要使用物化视图,需要确保CN节点有足够的空间和良好的IO能力。

  • 物化视图与其它表JOIN时,数据需要拉回协调节点CN进行计算,不适合于大量数据JOIN业务使用。

索引使用准则

  • 能使用Btree 索引的就不要使用GIN索引。

  • 有唯一约束要求的字段必需使用UNIQUE来约束。

  • 索引越多,更新越慢。

  • 索引也需要占用物理空间。

  • 在线建立索引请使用CONCURRENTLY方式。

  • 建议对固定条件的(一般有特定业务含义)且选择比好(数据占比低)的query,创建带where的索引;

        select * from test where status=1 and col=?; -- 其中status=1为固定的条件
    
        create index test_col_idx on test (col) where status=1;
  • 建议对经常使用表达式作为查询条件的query,可以使用表达式或函数索引加速query;

        select * from test where exp(xxx);
    
        create index test_xxx_idx on test ( exp(xxx) );
  • 建议不要建过多index,一般不要超过6个,核心表(保订单)可适当增加index个数。

  • 通过查询系统视图pg_stat_all_indexes可以评估索引使用情况 。

字段设计准则

  • 能用数值类型的,就不用字符类型。

  • 能用varchar(N)就不用char(N),以利于节省存储空间,而且varchar拉长时不需要更新全表数据。

  • 能用varchar(N) 就不用text,varchar。

  • 使用default NULL,而不用default '',以节省存储空间。

  • 使用NUMERIC(precision, scale)来存储货币金额和其它要求精确计算的数值,而不建议使用real, double precision

  • 逻辑复制表禁止bytea大对象。

用户及权限设计准则

  • 不准许在业务操作中使用DBA角色用户。

  • 每个独立的应用都需要有独立的数据库用户。

开发准则

GROUP拆分原则

TDSQL PostgreSQL 版可以把集群中的部分数据节点定义成一个GROUP,然后在建立数据表时指定数据存放到这个GROUP中,实现不同业务表的物理资源使用隔离。如可以按不同的地区来进行拆分,这样拆分的好处

  • 各个地区的数据完全隔离,互不影响。

  • 各个地区可以独立扩容,包括该地区的冷热节点。

  • 各个地区可以独立做灰度升级,互不影响。

  • 某个数据节点DN故障倒换只会影响到该个地区的业务。

  • 每个地区上业务时只需要评估该地区的资源要求。

  • 对于非分布键的查询,更新,删除不会占用到其它地区服务器资源,而且访问的节点更少,性能及可靠性更高。

数据库拆分原则

TDSQL PostgreSQL 版的逻辑隔离有两个级别,分别是database(数据库)和schema(模式)级别,请使用使用schema级别,这样拆分后的好处

  • 用户访问不同schema下的资源需要授权后才能访问,所以schema级别来说已经足够安全。

  • schema拆分只是访问权限上的隔离,放开权限后,同一个数据源就可以访问不同schema下的数据表。

  • 不同schema下的数据表可以在接入层CN进行JOIN查询,而database级别是无法这样操作。

  • 从 TDSQL PostgreSQL 版的设计上来看,不同数据库连接增加了协调节点CN到数据节点DN这间连接池的维护工作,即随着数据库的增加,要维护的连接数会更多,占用资源更大

  • 参考其它DB的使用习惯来看,oracle使用的是一用户对应一个schema。

数据库设计原则

数据库编码

数据库编码统一使用UTF8

  • 建立UTF8编码数据库方法

      create database postgres ENCODING 'utf8';
  • 查询现在数据库编码方法

     postgres=# select datname,pg_encoding_to_char(encoding) from pg_database ;
    
    

datname | pg_encoding_to_char

-----------+---------------------

template1 | UTF8

template0 | UTF8

postgres | UTF8

(3 rows)

**注意:数据库编码建立后就无法再修改。**




#### 数据库默认排序分组规则
数据库排序分组规则,使用zh_CN.utf8或者C,但推荐使用zh_CN.utf8,主要是因为zh_CN.utf8支持模糊查询和全文搜索,还有汉字排序时使用拼音首字母。

- 指定数据库排序分组规则

   ``` bash
    create database postgres ENCODING 'utf8' lc_collate 'zh_CN.utf8' LC_CTYPE

    'zh_CN.utf8';
  • 查询数据库排序分组规则

     postgres=# select datname,datcollate,datctype from pg_database;
    
     datname | datcollate | datctype
    
      -----------+------------+-----------
    
      template1 | zh_CN.utf8 | zh_CN.utf8
    
      template0 | zh_CN.utf8 | zh_CN.utf8
    
      postgres | zh_CN.utf8 | zh_CN.utf8
    
       (3 rows)

schema设计技巧

在 TDSQL PostgreSQL 版中schema与用户不存在必然的联系,但建议为每个用户建立一个相同名称的schema,并且将该schema分配给其同名的用户,如

create schema picc_gd AUTHORIZATION picc_gd;

这样做好处就是使用picc_gd这个用户连接上来后,访问数据表时最先搜索的路径为picc_gd这个schema,这处简单的设计就能实现同一套程序匹配多套库的功能。

数据库用户设计原则

  • 绝对不准许在业务中使用DBA角色用户。

  • 不同应用请使用不同的用户来隔离数据表的访问。

  • 用户停止使用后要禁用

建立普通用户的方法

create role picc_gd with login password 'picc_gd@123';

禁用用户方法

alter role picc_gd with nolgin; 

分布键的选择原则

  • 分布键只能选择一个字段。

  • 如果有主键,则选择主键做分布键。

  • 如果主键是复合字段组合,则选择字段值选择性多的字段做分布键。

  • 也可以把复合字段拼接成一个新的字段来做分布键。

  • 没有主键的可以使用UUID来做分布键。

  • 总之一定要让数据尽可能的分布得足够散。

索引的设计原则

btree索引

创建方法
 create table policy_basis(id int not null,cardid varchar(20) not null,primary

key(id));

 create index policy_basis_cardid_idx on policy_base using btree(cardid);

什么情况下会使用到索引

  • 单表=,>,>=,<=,between x and x,in操作,如

     Select * from policy_basis where cardid=xxx;
    
      Select * from policy_basis where cardid>xxx;
    
      Select * from policy_basi where cardid>=xxx;
    
      Select * from policy_basis where cardid>xxx and cardid<xxx; 
    
      Select * from policy_basis where cardid between xx and xxx; 
    
      Select * from policy_basis where cardid in(’xx’,’xx’);
  • like左匹配模糊查询用法

要使用like左匹配模糊查询,需要指定排序方法为 collate "C"

   create table t_like (id varchar(32),cardid varchar(20),primary key(id) );

   create index t_like_cardid_idx on t_like using btree(cardid collate "C");

然后这样的语句就能走索引

select * from t_like where cardid like '99999%';

但其它操作符需要这样写才能使用索引

 select * from t_like where cardid COLLATE "C" = '99999';

 select * from t_like where cardid COLLATE "C" > '99999';

  • order by + limit x 之类返回部分数据,如

     create table t_order_by(id int not null,cardid varchar(20) not null,primary
    
     key(id));
    
      insert into t_order_by select t,t from generate_series(1,1000000) as t; 
    
      select * from t_order_by order by id limit 10;
    
      select * from t_order_by order by cardid limit 10;

    上面的查询不管是分布键还是非分布键都要对排序的字段建立索引

create index t_order_by_id_idx on t_order_by(id);

 create index t_order_by_cardid_idx on t_order_by(cardid);

如果是全表排序,则无需要建立索引。

  • join连接查询

    “分布键与分布键”,“分布键与非分布键”,“非分布键与非分布键”,只要是join连接扫描的数据量占比量少,建立索引都是能大大提高查询速度

      ccreate table t_join_main(id int not null,cardid int,primary key(id));
    
      create table t_join_detail(id int not null,cardid int,phone varchar(20),primary
    
      key(id));
    
      insert into t_join_main select t,t from generate_series(1,1000000) as t;
    
      insert into t_join_detail select t,t from generate_series(1,1000000) as t;
    
      select * from t_join_main,t_join_detail where t_join_main.id=t_join_detail.id
    
      and t_join_main.id=1;
    
      select * from t_join_main,t_join_detail where
    
      t_join_main.id=t_join_detail.cardid and t_join_main.id=1; 
    
      select * from t_join_main,t_join_detail where
    
      t_join_main.cardid=t_join_detail.cardid and t_join_detail.cardid=2;

    上面几个join查询对应的字段都要建立索引

      create index t_join_detail_cardid_idx on t_join_detail(cardid);
    
      create index t_join_main_cardid_idx on t_join_main(cardid);

数组gin索引

创建方法
create table t_array(id int not null, cardid varchar(32)[],primary key(id));

insert into t_array select t,('{'||md5(t::text)||'}')::text[] from
generate_series(1,1000000) as t;

insert into t_array values(1000001,'{1,2,3}');
insert into t_array values(1000002,'{1,2,3,4,5}');
create index t_array_cardid_idx on t_array using gin(cardid);
使用方法
select * from t_array where cardid @>
('{c4ca4238a0b923820dcc509a6f75849b}')::text[];

select * from t_array where cardid @> ('{1,2}')::text[];

select * from t_array where cardid @> ('{1,2,5}')::text[];

jsonb使用索引

创建方法
create table t_jsonb(id int not null,f_jsonb jsonb,primary key(id));

create index t_jsonb_f_jsonb_idx on t_jsonb using gin(f_jsonb);
使用方法
select * from t_jsonb where f_jsonb @> '{"phone":"81838898"}';

pg_trgm全模糊走索引

创建方法
create extension pg_trgm;

create table t_trgm(id int not null,cardid varchar(500),primary key(id));

create index t_trgm_cardid_idx on t_trgm using gin(cardid gin_trgm_ops);
使用方法
select * from t_trgm where cardid like '%440521%';
使用限制
  • 数据库排序规则需要使用lc_collate 'zh_CN.utf8';

  • 查询字符不能少于3个

  • 存储的内容不能大于8k

任意字段都可使用索引

创建方法
create table gin_mul(f1 int, f2 int, f3 timestamp, f4 text, f5 numeric, f6

text);

create extension btree_gin;

create index gin_mul_gin_idx on gin_mul using gin(f1,f2,f3,f4,f5,f6);
使用方法

这样建立索引后,这个表的任意字段就能组合使用索引查询,如

select * from gin_mul where f1=xx;

select * from gin_mul where f3=xx;

select * from gin_mul where f1=xx and f2=xx;

select * from gin_mul where f1=xx and f2=xx and f5=xxx;
使用注意

btree_gin索引的容量一般是数据表2倍左右,索引的维护成本非常大,适合于更新少,需要任意字段组合查询使用场景。

如何检查查询是否使用了索引

postgres=# explain select * from t1 where f1=1;

QUERY PLAN 

--------------------------------------------------------------------------

Remote Fast Query Execution (cost=0.00..0.00 rows=0 width=0) 

Node/s: dn001 

-> Index Scan using t1_f1_idx on t1 (cost=0.42..4.44 rows=1 width=8)

Index Cond: (f1 = 1)

(4 rows) 

postgres=# 

postgres=# explain select * from t_array where mc @>

('{'||md5('1')||'}')::text[];

QUERY PLAN

-------------------------------------------------------------------------------------------

Remote Fast Query Execution (cost=0.00..0.00 rows=0 width=0)

Node/s: dn001, dn002

-> Bitmap Heap Scan on t_array (cost=31.40..3174.64 rows=2503 width=61)

Recheck Cond: (mc @> ('{c4ca4238a0b923820dcc509a6f75849b}'::cstring)::text[])

-> Bitmap Index Scan on t_array_mc_idx (cost=0.00..30.78 rows=2503 width=0) 

Index Cond: (mc @> ('{c4ca4238a0b923820dcc509a6f75849b}'::cstring)::text[])

(6 rows)



postgres=# explain select * from t_join_1,t_join_2 where

t_join_1.f1=t_join_2.f2 and t_join_1.f1=1;

QUERY PLAN

--------------------------------------------------------------------------------------------------

Nested Loop (cost=0.25..8.29 rows=1 width=16)

-> Remote Subquery Scan on all (dn001) (cost=100.12..104.16 rows=1 width=8) 

-> Index Scan using t_join_1_f1_idx on t_join_1 (cost=0.12..4.14 rows=1

width=8)

Index Cond: (f1 = 1)

-> Materialize (cost=100.12..104.16 rows=1 width=8) 

-> Remote Subquery Scan on all (dn001,dn002) (cost=100.12..104.16 rows=1

width=8) 

-> Index Scan using t_join_2_f2_idx on t_join_2 (cost=0.12..4.14 rows=1

width=8)

Index Cond: (f2 = 1) 

(8 rows)

postgres=# explain select * from t_join_1,t_join_2 where

t_join_1.f1=t_join_2.f1 and t_join_1.f1=1;

QUERY PLAN

---------------------------------------------------------------------------------------------

Remote Fast Query Execution (cost=0.00..0.00 rows=0 width=0)

Node/s: dn001, dn002

-> Nested Loop (cost=14.51..1359.15 rows=989 width=16) 

-> Index Scan using t_join_1_f1_idx on t_join_1 (cost=0.42..4.44 rows=1

width=8)

Index Cond: (f1 = 1) 

-> Bitmap Heap Scan on t_join_2 (cost=14.09..1344.82 rows=989 width=8)

Recheck Cond: (f1 = 1)

-> Bitmap Index Scan on t_join_2_f1_idx (cost=0.00..13.84 rows=989 width=0)

Index Cond: (f1 = 1)

(9 rows)

增删改查使用原则

insert

一次插入多条记录
insert into tbase(id,nickname) values(1,'hello TBase'),(2,'TBase hello');

备注:一次性提交效率更高

插入记录存在时更新
insert into tbase values(1,'pgxz') ON CONFLICT (id) DO UPDATE SET nickname =

'tbase';

插入记录,记录存在时变成更新,使用ON CONFLICT

2.8.1.3 插入记录后,查询返回新增的记录
insert into tbase(nickname) values(1,'TBase') returning *; 

使用returning相当insert+select操作

delete

分布键做为删除条件效率高,扩展性好

不使用分布键 ,则删除语句发往所有节点。

删除分区表数据

删除分区表数据除了带分布键做为删除条件外,还要带上分区键,否则会扫描所有分区表。

删除也要考虑使用索引

带索引的字段做为过滤条件,往往执行效率更高。

使用returning需要返回数据
postgres=# delete from tbase where id=2 returning *;

id | nickname 

----+----------- 

2 | TBase 

(1 row)
两表关联删除只支持分布键关联
delete from tbase using t_appoint_col where tbase.id=t_appoint_col.id;

这里 t_appoint_col.id和tbase.id 必需都是分布键

删除所有数据请使用truncate
truncate table tbase;

truncate

效率高,而且能直接释放空间,但对于做主主复制的系统,需要两个集群各自操作一次。另外还要考虑数据同步到kafka的问题。

update

分布键不支持更新
update tbase set id=8 where id=1; 

ERROR: Distribute column or partition column can't be updated in current version 
分区表的分区键不支持更新
update t_time_range set inserttime='2017-09-02';

ERROR: Distributed column or partition column "inserttime" can't be updated in

current version 
更新并返回记录请使用returning
update tbase set nickname = nickname where id = (random()*2)::integer returning

*;

id | nickname 

----+----------- 

2 | TBase 

(1 row)
两表关联更新只支持分布键关联
update tbase set nickname ='Good TBase' from t_appoint_col where

t_appoint_col.id=tbase.id;

这里 t_appoint_col.id和tbase.id 必需都是分布键

分布键做为更新条件效率高,扩展性好

不使用分布键 ,则更新语句发往所有节点。

分区表数据更新

更新分区表数据除了带分布键做为更新条件外,还要带上分区键,否则会扫描所有分区表。

更新也要考虑使用索引

带索引的字段做为过滤条件,往往执行效率更高。

select

分布键查询效率高,扩展性好

select * from tbase where nickname=’tbase’ ; #扩展性差

select * from tbase where nickname=’tbase’ and id=1; #效率高,具有良好扩展性
分区表查询需要带上分区时间

select * from t_time_range where

id=1;#搜索所有分区,效率低,60个分区表,无数据,查询一次12ms

select * from t_time_range where id=1 and inserttime>=’2019-01-01’ and

inserttime<’2019-02-01’;#搜索单一分区,效率高,查询一次2ms

not in 中包含了null,结果全为真
select * from tbase where id not in (3,5,null);

这样的查询返回为空,一条记录都查询不出来

尽量分页提取数据
select * from policy_basic order by id limit 10 offset 0;

select * from policy_basic order by id limit 10 offset 9;

offset 0表示第一条开始,另外 offset x只是不提出数据,但还要查询出来。

大数据抽取请使用游标

游标抽取数据效率更高。

分布式事务使用原则

tbase支持全局acid级别分布式事务,但应用设计时尽可能减少,分布式事务在最后提交时开销更大。

字段类型使用原则

数字类型

| 名字 | 存储尺寸 | 描述 | 范围 |

|------------------|--------------|--------------------|-----------------------------------------------|

| smallint | 2字节 | 小范围整数 | -32768 to +32767 |

| integer | 4字节 | 整数的典型选择 | -2147483648 to +2147483647 |

| bigint | 8字节 | 大范围整数 | -9223372036854775808 to +9223372036854775807 |

| numeric | 可变 | 用户指定精度,精确 | 最高小数点前131072位,以及小数点后16383位 |

| double precision | 8字节 | 可变精度,不精确 | 15位十进制精度 |

  • 整数smallint,integer,bigint请按最大可能数进行选择,这样可以在存入超出范围的数字时由数据库做最后的检查保证。

  • 小数字可以减少不必需的存储空间。

  • 小数字减少查询时扫描的数据块。

  • numeric用于需要精度准确的业务,如存储金额。

  • double precision用于精度要求不高的业务,其性能要好于numeric。

字符类型

| 名字 | 描述 |

|--------------------------------------|----------------|

| character varying(n), varchar(n) | 有限制的变长 |

| character(n), char(n) | 定长,空格填充 |

| text | 1G |

  • 如果字段将来存在变更长度的可能,则禁止使用char。

  • varchar类型要指定长度使用,不指定长度的varchar与text一样可以保持1G数据。

  • 禁止使用text来存储副件图片之类的数据。

二进制数据类型

| 名字 | 存储尺寸 | 描述 |

|----------|----------------------------|------------------------------|

| bytea | 1或4字节外加真正的二进制串 | 变长二进制串,最大存储1G数据 |

TDSQL PostgreSQL 版支持二进制数据存储,但访问效率远低于对象存储系统,无特别要求禁止使用。

日期类型

| 名字 | 存储尺寸 | 描述 | 范围 |

|--------------|--------------|----------------|-------------|

| timestamp | 8字节 | 包括日期和时间 | 精确到1微秒 |

| timestamp(0) | 8字节 | 包括日期和时间 | 精确到1秒 |

| date | 4字节 | 只包括日期 | |

| time | 8字节 | 一天中的时间 | 精确到1微秒 |

| Time(0) | 8字节 | 一天中的时间 | 精确到1秒 |

  • 如果存入的数据只想精确到秒,则使用timestamp(0),time(0)

  • 注意oracle转换过来的应用,oracle的date是精确到秒,对应的tbase类型为timestamp(0)

null值拼接字段串需要处理

不处理返回为null值

postgres=# select id,nickname||null from tbase limit 1;

id | ?column?

----+----------

1 | 

(1 row)

postgres=# select id,nickname||coalesce(null,'') from tbase limit 1; 

id | ?column? 

----+------------- 

1 | hello TBase 

(1 row)

count函数使用

建议使用count(1) 或count(*) 来统计行数,而不建议使用count(col)

来统计行数,因为NULL值不会计入;

注意:

  • count(多列列名)时,多列列名必须使用括号,例如count( (col1,col2,col3) );

  • 多列的count,即使所有列都为NULL,该行也被计数,所以效果与count(*) 一致;

postgres=# select * from tbase ;
id | nickname
----+---------------
1 | hello TBase
2 | TBase
5 |
3 | TBase
4 | TBase default
(5 rows)
postgres=# select count(1) from tbase;
count
-------
5
(1 row)
postgres=# select count(*) from tbase;
count
------- 
5
(1 row) 
postgres=# select count(nickname) from tbase; 

count
-------
4
(1 row) 
postgres=# select count((id,nickname)) from tbase; 
count 
-------
5
(1 row)

只取所需字段

建议非必须时避免select *,只取所需字段,以减少包括不限于网络带宽消耗

过滤不必要更新

建议update 时尽量做 <> 判断,比如update table_a set column_b = c where column_b

<> c;

postgres=# update tbase_main set mc='TBase' ;
UPDATE 1
postgres=# select xmin,* from tbase_main;
xmin | id | mc
------+----+-------
2562 | 1 | TBase
(1 row)
postgres=# update tbase_main set mc='TBase' ;
UPDATE 1
postgres=# update tbase_main set mc='TBase' where mc!='TBase';
UPDATE 0

上面的效果是一样的,但带条件的更新不会产生一个新的版本记录,不需要系统执行vacuum回收垃圾数据。

大数据量更新尽量折分

建议将单个事务的多条SQL操作,分解、拆分,或者不放在一个事务里,让每个事务的粒度尽可能小,尽量少锁定资源,避免死锁的产生;

#sesseion1把所有数据都更新而不提交,一下子锁了2000千万条记录

postgres=# begin;
BEGIN
postgres=# update tbase_main set mc='TBase_1.3';
UPDATE 200000000
#sesseion2 等待
postgres=# update tbase_main set mc='TBase_1.4' where id=1;
#sesseion3 等待
postgres=# update tbase_main set mc='TBase_1.5' where id=2;
如果#sesseion1分布批更新的话,如下所示
postgres=# begin;
BEGIN
postgres=# update tbase_main set mc='TBase_1.3' where id>0 and id <=100000;
UPDATE 100000
postgres=#COMMIT;
postgres=# begin;
BEGIN
postgres=# update tbase_main set mc='TBase_1.3' where id>100000 and id
<=200000;
UPDATE 100000
postgres=#COMMIT;
则session2和session3中就能部分提前完成,这样可以避免大量的锁等待和出现大量的session占用系统资源,在做全表更新时请使用这种方法来执行

禁止在业务代码中使用DDL

TDSQL PostgreSQL 版在扩容,冷热数据搬迁时DDL都会禁止执行。

跨库访问规范

所有不按分布键的查询都是属于跨库访问,需要按下面的规则来限制

  • 如果查询需要将数据拉回协调节点(CN)计算,只要涉及计算数据量比较少,这样在协调节点(CN)上面的聚集也是高性能的。

  • 针对于单表聚集统计之类的查询,由于可以实现查询下推,只是汇集最后的结果,这样的查询只要DN的处理能力足够,使用上也没什么问题。

  • 如果需要拉回大量数据在协调节点(CN)进行计算,就需要考虑使用提前计算好的中间表,宽表,或者按业务维度分布的数据表,减少协调节点(CN)和数据节点(DN)之间的数据量传输,如果做不到,需要禁止这样的查询使用。

高可用数据源接入方式

TDSQL PostgreSQL 版的每一个cn都可以接入,看到的信息都是全局一致的,可以使用下面的方法实现高可用

  • 应用程序配置多个IP+PORT数据源,轮循选择接入。

  • 前置CLB,F5或者LVS,应该程序直接访问前置的VIP

防SQL注入

为了防止SQL注入,需要把传入的变量值做处理,处理方法如下,如

select * from tbase where nickname='$1';

上面的$1 参数如果传入成这样 123';delete from tbase ;select ' 就会变成这样的语句

select * from tbase where nickname='123';delete from tbase ;select ''; 

这样就会把数据表tbase的数据给删除掉

所以在程序中需要对传入的参数进行处理(使用扩展协议不需要处理这个问题),把参数值中的单引号'变成两个'',最终变成这样

select * from tbase where nickname='123'';delete from tbase ;select ''';

这样就变成只查询一个字符串,如下所示

postgres=# select * from tbase where nickname='123'';delete from tbase ;select
''';
id | nickname
----+----------
(0 rows)
postgres=# select '123'';delete from tbase ;select ''';
?column?
----------------------------------
123';delete from tbase ;select '
(1 row)

应用程序升级规范

概述

执行工具

TDSQL PostgreSQL 版之客户端工具psql

连接环境

如无特别约定,连接到 TDSQL PostgreSQL 版集群之任意一个CN都可以

操作方法

DDL更新

操作前环境配置

DDL需要锁表,所以执行DDL操作时都要配置拿到表锁的最大时长,特别是在线服务项目,下在配置某个节点在3秒内如果无法获取表锁就rollback

psql -h 172.16.0.29 -p 11000 -d postgres -U tbase
psql (PostgreSQL 10.0 TBase V2)
Type "help" for help.
postgres=# set lock_timeout to 3000;
SET
postgres=#
postgres=# alter table tbase add column phone varchar(12);
ALTER TABLE
postgres=#
更新超时提示
postgres=# alter table tbase add column phone varchar(12);

ERROR: canceling statement due to lock timeout
下面情况下禁止执行DDL

添加新节点,节点扩容,冷热数据搬迁,节点异常

主主复制表DDL变更准则

对于主主复制表DDL变更,两个集群DDL需要保持一致的操作顺序,每次更新后务必检查主主复制进程是否能正常同步数据。

DML更新

INSERT插入数据

直接在线执行即可。

UPDATE或者DELETE

如果是全表的UPDATE,DELETE,则需要使用分批更新法,如

UPDATE 表名 set 字段名=xxxx WHERE id>1 and id<=100 and 字段名!=xxxx ;

UPDATE 表名 set 字段名=xxxx WHERE id>100 and id<=200 and 字段名!=xxxx ;

上面语句好处就是减少行锁数,尽量做到对业务影响最小化

原子性批量更新
Begin;
Insert ....
Update...
...
Commit;

把所有更新语句合在一个事务中执行来保证更新的原子性,但要注意更新过程持有的锁时间会更长。

数据管理

数据复制

TDSQL PostgreSQL 版目前提供了三种不同级别的复制方案

流复制

流复制用于保证数据节点DN节点高可用和异地容灾使用。同步的级别分别同步和异步,同时支持配置多个备机同步。

部署建议

  1. 建议配置最少一主一备同步,由于同步在备机异常时会卡住主节点写入,所以需要准备一个备机用于同步备机异常时做为后备接入,即一个IDC为1主2备。

  2. 如果有同城IDC,要求网络的延迟在3ms以内,则同城的另一个IDC可以部署一个同步备机。

  3. 异地IDC备节点只能使用异步同步方式。

以上数据同步复制只支持同集群同构同版本。

逻辑复制

对于不同 TDSQL PostgreSQL 版集群之间数据复制, TDSQL PostgreSQL 版提供了表级逻辑复制能力,逻辑复制可以实现A集群的A表在数据发生变化时同步至B集群,使用逻辑复制注意点。

  • 同步的数据表需要有主键。

  • 逻辑复制在数据约束冲突时会导致复制服务停止。

  • 逻辑复制不支持强同步复制。

  • 修改字段名,删除字段的DDL操作需要确保无增量数据需要同步,否则会导致增量数据无法同步。

  • 逻辑复制的数据表做DDL维护时两边需要保持一致。

  • 逻辑复制只支持INSERT,DELETE,UPDATE操作的数据变更,对于COPY,TRUNCATE是无法进行逻辑解释。

逻辑解释至kafka

TDSQL PostgreSQL 版支持将增加的数据逻辑解释同步到kafka集群,其它需要同步数据的服务再通过订阅kafka的方式来实现数据同步。

冷热数据分离

TDSQL PostgreSQL 版支持集群内使用异构机型,机型之间配置差异化,满足高性能的同时,节约业务存储成本和冷热数据访问隔离。 TDSQL PostgreSQL 版目前支持按月度划分数据分区,从而在业务规划时,可以将频繁访问月份的数据存储在高IO性能机型(例如SSD磁盘),将非频繁访问数据存储在大存储容量机型(例如SAS磁盘)。

冷热分区表使用注意事项:

冷热分区表需要在建立数据表时就定义好,普通表无法修改成冷热分区表。