一、为什么要给数据库表做分区设计

很多做业务开发的朋友可能都有过这种经历:刚上线的系统查询速度飞快,随着数据越攒越多,比如电商的订单表、物流的轨迹表、物联网的设备上报表,过个半年一年,查个历史数据要等好几秒,甚至直接超时。其实核心原因就是表的数据量太大了,数据库每次查询都得扫全表,就像你要从一本1000页的书里找一句话,得一页一页翻,当然慢。

分区表设计的本质,就是把这本“厚书”拆成好几本薄的分册,比如按时间、按地区、按用户范围来拆。查询的时候只需要找对应的分册就行,不用翻全本;删数据的时候也只需要把没用的分册整个扔掉,不用一条条删,既快又省资源。

二、GaussDB分区表的核心类型及示例

GaussDB是华为推出的数据库,支持好几种分区方式,我们选最常用的两种来详细说,每个示例都带完整的代码和注释,大家可以直接照着改着用。

2.1 范围分区:按时间拆分最常用

范围分区是最适合时间序列数据的,比如订单、日志、上报数据,我们可以按年、按月、按天来拆分。比如做一个电商的订单表,按创建时间(create_time)按月拆分,每个月的订单单独存在一个分区里。

技术栈:GaussDB(兼容MySQL语法,示例代码直接在GaussDB里运行)

-- 创建订单表,按create_time的月份做范围分区
CREATE TABLE orders (
    order_id INT COMMENT '订单ID',
    user_id INT COMMENT '下单用户ID',
    order_amount DECIMAL(10,2) COMMENT '订单金额',
    create_time DATETIME COMMENT '订单创建时间',
    status VARCHAR(20) COMMENT '订单状态'
)
-- 定义分区规则:按create_time的月份拆分,每个月一个分区
PARTITION BY RANGE (MONTH(create_time)) (
    -- 2024年1月的订单分区
    PARTITION p_202401 VALUES LESS THAN (2) COMMENT '2024年1月订单',
    -- 2024年2月的订单分区
    PARTITION p_202402 VALUES LESS THAN (3) COMMENT '2024年2月订单',
    -- 2024年3月的订单分区
    PARTITION p_202403 VALUES LESS THAN (4) COMMENT '2024年3月订单',
    -- 4月及以后的订单放这个默认分区,之后可以再拆
    PARTITION p_future VALUES LESS THAN MAXVALUE COMMENT '未来月份订单'
);

-- 插入测试数据:2024年1月的订单
INSERT INTO orders VALUES(1, 1001, 99.9, '2024-01-15 10:00:00', '已完成');
-- 插入测试数据:2024年2月的订单
INSERT INTO orders VALUES(2, 1002, 199.9, '2024-02-20 14:00:00', '已完成');
-- 插入测试数据:2024年3月的订单
INSERT INTO orders VALUES(3, 1003, 299.9, '2024-03-10 16:00:00', '已完成');

-- 验证分区:查2024年1月的订单,只会扫p_202401分区,速度很快
SELECT * FROM orders WHERE create_time BETWEEN '2024-01-01 00:00:00' AND '2024-01-31 23:59:59';

上面的代码运行后,你会发现查1月订单的时候,数据库只会扫描p_202401这个分区,不会碰其他分区,速度比全表查快很多。

2.2 列表分区:按固定值拆分

如果你的数据可以按固定的枚举值拆分,比如按地区、按订单状态,就适合用列表分区。比如做一个全国的物流轨迹表,按省份拆分,每个省份的轨迹单独存在一个分区里。

技术栈:GaussDB(兼容MySQL语法)

-- 创建物流轨迹表,按省份做列表分区
CREATE TABLE logistics_trace (
    trace_id INT COMMENT '轨迹ID',
    order_id INT COMMENT '关联订单ID',
    province VARCHAR(20) COMMENT '所在省份',
    location VARCHAR(100) COMMENT '具体位置',
    trace_time DATETIME COMMENT '轨迹产生时间'
)
-- 定义分区规则:按province的固定值拆分
PARTITION BY LIST (province) (
    -- 广东省的轨迹分区
    PARTITION p_guangdong VALUES IN ('广东') COMMENT '广东省物流轨迹',
    -- 浙江省的轨迹分区
    PARTITION p_zhejiang VALUES IN ('浙江') COMMENT '浙江省物流轨迹',
    -- 江苏省的轨迹分区
    PARTITION p_jiangsu VALUES IN ('江苏') COMMENT '江苏省物流轨迹',
    -- 其他省份的轨迹放默认分区
    PARTITION p_other VALUES IN (DEFAULT) COMMENT '其他省份物流轨迹'
);

-- 插入测试数据:广东省的轨迹
INSERT INTO logistics_trace VALUES(1, 1001, '广东', '广州市天河区', '2024-03-01 09:00:00');
-- 插入测试数据:浙江省的轨迹
INSERT INTO logistics_trace VALUES(2, 1002, '浙江', '杭州市西湖区', '2024-03-01 10:00:00');
-- 插入测试数据:四川省的轨迹(会进p_other分区)
INSERT INTO logistics_trace VALUES(3, 1003, '四川', '成都市锦江区', '2024-03-01 11:00:00');

-- 验证分区:查广东省的轨迹,只会扫p_guangdong分区
SELECT * FROM logistics_trace WHERE province = '广东';

列表分区的好处是,比如你要做广东地区的业务统计,只会扫描广东的分区,不用扫全国的全表,速度提升非常明显。

三、分区表的应用场景

不是所有表都适合做分区,选对场景才能发挥分区的优势,常见的适合分区的场景有这几类: 第一类是时间序列数据,比如订单、日志、物联网设备上报数据、游戏的玩家行为数据,这类数据有明显的时间属性,查询的时候经常按时间范围筛选,比如查上个月的订单、昨天的日志,分区后查询速度提升非常大。 第二类是按业务属性拆分的数据,比如按地区拆分的物流、按用户等级拆分的会员数据、按产品线拆分的商品数据,这类数据的业务属性是固定的,查询的时候经常按业务属性筛选,比如查北京地区的物流、钻石会员的订单,分区后可以精准扫描对应分区。 第三类是需要定期清理历史数据的场景,比如很多公司要求保留半年的订单数据,超过半年的要删除,用分区表的话,只需要把超过半年的分区整个删除就行,不用一条条删,比如要删除2023年10月的订单,只需要执行ALTER TABLE orders DROP PARTITION p_202310;,几秒就能完成,要是全表删的话,几十万条数据可能要跑十几分钟。

四、分区表的优缺点

4.1 优点

首先是查询速度快,这个是最核心的,分区后查询只扫对应分区,不用扫全表,数据量越大,速度提升越明显,比如一个1亿条数据的表,分区成100个小分区,查询速度可能提升几十倍。 其次是数据维护简单,清理历史数据只需要删分区,不用执行慢的DELETE语句;备份恢复也可以按分区来,比如只备份最近3个月的分区,不用备份全表,节省备份时间和存储空间。 还有是可以提升并发性能,不同的分区可以放在不同的存储设备上,比如把常用的最近一个月的分区放在高速的SSD上,把不常用的历史分区放在便宜的机械硬盘上,既提升了常用数据的查询速度,又降低了存储成本。

4.2 缺点

首先是分区设计有门槛,要是分区规则设计不好,反而会降低性能,比如一个表总共才10万条数据,硬要拆成10个分区,每个分区才1万条,查询的时候还要先找分区,反而比全表查慢。 其次是分区表的语法和普通表有区别,比如删除分区、修改分区的语法和普通表不一样,开发和运维人员需要学习新的操作方法。 还有是分区数量不能太多,GaussDB对分区数量有上限,比如普通表最多支持1024个分区,要是拆得太细,超过上限就会出问题,比如按天拆分一个表,要是拆10年,就会有3650个分区,超过上限。

五、分区表的注意事项

第一是分区规则要提前规划,选对分区键是核心,分区键就是你拆分分区的字段,比如时间序列数据选create_time当分区键,地区数据选province当分区键,分区键必须是查询条件里经常用到的字段,要是分区键选的不对,比如订单表按用户ID当分区键,但是你经常按时间查订单,那分区就发挥不了作用,查询还是要扫全表。 第二是分区粒度要合适,分区不能太粗也不能太细,比如一个月100万条订单,按天拆的话每个分区才3万条,太细;按年拆的话每个分区1200万条,太粗,按月拆就合适,每个分区100万条左右,查询速度快,维护也方便。 第三是要注意分区表的索引设计,分区表也可以建索引,但是索引是全局的还是本地的要选对,全局索引是针对整个表的,本地索引是针对每个分区的,比如你要查某个用户的所有订单,适合建全局索引;要是经常查某个月某个用户的订单,适合建本地索引。 第四是要避免跨分区查询,跨分区查询就是查询条件跨了多个分区,比如查2024年1-2月的订单,会扫p_202401和p_202402两个分区,要是跨的分区太多,比如查2023年全年的订单,会扫12个分区,速度就会变慢,所以业务设计的时候要尽量避免跨多个分区的查询。

六、文章总结

分区表设计是解决海量数据查询慢、维护难的有效方案,GaussDB支持的范围分区、列表分区等类型,可以覆盖大部分业务场景。设计分区表的时候,核心是选对分区键、规划好分区粒度,根据业务的查询习惯来设计,不能盲目跟风。

对于时间序列数据,优先选范围分区,按时间拆分;对于有固定业务属性的数据,选列表分区,按业务属性拆分。同时要注意分区表的优缺点和注意事项,比如不要给小表做分区、不要跨太多分区查询、提前规划分区数量上限等。

合理的分区表设计,能让海量数据的查询速度提升几十倍,历史数据的维护效率提升数倍,是很多中大型系统必备的性能优化方案。