PostgreSQL 基于 inherits 实现分表的示例代码
背景
生产环境有一张 sales_order_full 表数据量达到 3.3 亿. 存储了公司4个大区全量销售数据.并且数据量还在持续增长. 数据量过大对业务功能读写都造成极大的 IO 压力.为保证系统稳定,团队制定了短期的计划,对 sales_order_full 表按大区进行拆分.来减轻单表压力.
首先你需要了解 PostgreSQL 的 inherits 技术
在 PostgreSQL 中,INHERITANCE 是一种特性,允许一个表从另一个表继承属性(如列、约束等)。这意味着,如果一个表继承自另一个表,它将自动获得所有父表的列和约束。这对于简化数据库设计,特别是当你想要在不同的层次中共享相同结构的数据时非常有用。 (解释来自百度)
分析订单 Full 表
CREATE TABLE "public"."sales_order_full" (
"id" int8 NOT NULL DEFAULT nextval('sales_order_mixed_id_seq'::regclass),
"version_number varchar",
"order_id" varchar(298),
"country_cd varchar(29)",
"product_no" varchar(35),
"serial_no" varchar(273),
"qty" int4,
"fiscal_qtr",
"order_dt" time,
PRIMARY KEY ("order_id")
)
- ID: 自增索引序列.当前索引序列值已很大
- order_id: 订单ID,唯一主键
- country_cd: 通过country_cd 从 sales_geo_config 表中匹配所属大区 (历史原因导致).
拆分思路
- 重建 sales_order_full 表
- 放弃 sales_order_mixed_id_seq 重建 id 索引.
- full 增加区域字段.便于数据分表
- 初始化子表并初始化数据
- 代码逻辑调整
定义分表
| sales_order_full [主表] | 只做请求转发.不存储任何数据 |
| ales_order_full_cp | 存储 geo = CP 区域数据 |
| sales_order_full_hd | 存储 geo = HD 区域数据 |
| sales_order_full_cy | 存储 geo = CY 区域数据 |
| sales_order_full_sy | 存储 geo = SY 区域数据 |
| sales_order_full_unkonw | 兜底未匹配出区域的数据 |
前期工作
原 sales_order_full 表改名为 sales_order_full_all_data_bak
第一步:创建新 Full 表
CREATE TABLE "public"."sales_order_full" (
"id" bigserial, -- bigserial 会自动创建名为 sales_order_full_id_seq 自增索引
"version_number varchar",
"order_id" varchar(298),
"country_cd varchar(29)",
"product_no" varchar(35),
"serial_no" varchar(273),
"qty" int4,
"fiscal_qtr",
"geo", --所属大区
"order_dt" time,
PRIMARY KEY ("order_id")
)
第二步: 创建子表
因为分表较少.没有必要动态创建.统一进行分表初始化即可.
inherits 方式创建的子表. 索引需要重新设置.
子表中 id 字段会复用主表中的 ID 自增索引.
CHECK(geo = 'CP') 意思是对 geo 字段进行约束.只能存储 CP 大区数据.
-- 创建子表,并设置 full 表的父子关系
create table IF NOT EXISTS "sales_order_full_cp" (CHECK(geo = 'CP'))
inherits (sales_order_full);
-- 设置主键信息
ALTER TABLE "sales_order_full_cp" ADD CONSTRAINT "sales_order_full_cp_pkey"
PRIMARY KEY (order_id);
-- 设置对应索引
CREATE INDEX "sales_order_full_cp_mixed_idx" ON "public"."sales_order_full_cp"
USING btree (
"fiscal_qtr",
"order_id",
"country_cd"
);
第三步:数据流转子表
处理明确 Geo 的数据
insert into sales_order_full_cp (order_id,geo,country_cd,product_no,serial_no,qty,fiscal_qtr,order_dt,version_number) select -- 注意 geo 字段取自 sales_geo_config 表 a.order_id,b.geo,a.country_cd,a.product_no,a.serial_no,a.qty,a.fiscal_qtr,a.order_dt,version_number from sales_order_full_cp_all_data_bak a left join sales_geo_config b on a.country_cd = b.country where b.geo = 'CP';
处理 unkonw 因为这类数据没有明确Geo,所以要设置默认值 'UNKNOWN'
insert into sales_order_full_unknown
(order_id,geo,country_cd,product_no,serial_no,qty,fiscal_qtr,order_dt,versionnumber)
select
-- 注意 geo 直接默认
a.order_id,'UNKNOWN',a.country_cd,a.product_no,a.serial_no,a.qty,a.fiscal_qtr,a.order_dt,versionnumber
from
sales_order_full_cp_all_data_bak a
left join sales_geo_config b on a.country_cd = b.country
where b.geo is null or b.geo not in ('HD','CY','SY','CP');
第四步:数据对比并清理 sales_order_full_all_data_bak 表
注意这里得 select count(*) from sales_order_full;
再强调一遍: sales_order_full 表不存储任何数据,只做调用的中转。
通过 Explan 查看执行过程(简略):
-> Parallel Index Only Scan using sales_order_full_unknown -> Parallel Seq Scan on sales_order_full_cp -> Parallel Seq Scan on sales_order_full_cy -> Parallel Seq Scan on sales_order_sy -> Parallel Seq Scan on sales_order_full_hd
可以看到.数据来源全部来自5张子表.
select count(*) from sales_order_full; result: 330000000 select count(*) from sales_order_all_data_bak; result: 330000000 -- 结果相等 直接删,释放数据库空间 drop table sales_order_all_data_bak;
第五步:调整代码逻辑
- 对 sales_order_full 表查询操作,要求指定 geo 精确分表信息.
select * from sales_order_full where geo = 'HD' and xx = 'xx' 或 select * from sales_order_full_hd where xx = 'xx'
- 新增修改操作必须精确到具体子表操作.
insert into sales_order_full_cp (order_id,geo,country_cd,product_no,serial_no,qty,fiscal_qtr,order_dt,version_numer) select a.order_id,b.geo,a.country_cd,a.product_no,a.serial_no,a.qty,a.fiscal_qtr,a.order_dt,versionnumber from order_inbound inbound left join sales_geo_config b on inbound.country_cd = b.country where b.geo = 'CP' and inbound.version_number = '001'; update sales_order_full_cp set order_status = 'Closed' where order_id = 'xx';
疑问1:为什么不用触发器?
有同事疑问,为何不给 sales_order_full 主表创建触发器.这样数据新增就只操作主表.让主表进行转发处理岂不是更方便.
答案自然否定的.起码完全不合适我们. 系统订单全部是批处理的场景.大量数据通过触发器转发会大大降低性能,因为触发器会逐行检查处理.性能损耗无法接受.
疑问2:既然操作都细化到子表. sales_order_full 还有何用?
1.万恶的分页查询场景.假设要全区域分页查询.我们只需操作 sales_order_full 表. 由数据库帮我们完成 limit 和 offset . 不需要自己控制 5 张子表的分页.
2.对子表的结构修改.只需调整 sales_order_full 表.调整会自动同步到子表.
疑问3:为何不用成熟分表方案.如 Sharding-JDBC?
数据库自带的就超好用了.非常轻量.处理只依赖数据库 IO. 不占应用内存
到此这篇关于PostgreSQL 基于 inherits 实现分表的示例代码的文章就介绍到这了,更多相关PostgreSQL inherits分表内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
postgresql insert into select无法使用并行查询的解决
这篇文章主要介绍了postgresql insert into select无法使用并行查询的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧2021-01-01
SpringBoot连接使用PostgreSql数据库的方法
这篇文章主要介绍了SpringBoot连接使用PostgreSql数据库的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下2021-01-01
PostgreSQL数据库timestamp数据类型精度进位问题解析
PostgreSQL是一款功能强大的开源关系型数据库管理系统,起源于1986年的POSTGRES项目,它支持多种数据类型,包括数值类型、字符串类型、日期时间类型等,本文介绍PostgreSQL数据库timestamp数据类型精度进位问题,感兴趣的朋友一起看看吧2024-11-11


最新评论