园区水电账单表是计费库里只增不减的那张表,跑满三年就会把整个库拖慢。结论:账单表按账期做 RANGE 分区,24 个月以前的分区用 EXCHANGE PARTITION 切到归档表再迁入表格存储,热库查询靠分区裁剪,历史查询落到归档库。某多园区项目实测,单租户 24 个月账单查询 P95 从 4.3 秒降到 380 毫秒。
关键信息摘要
分区键必须进主键,原自增编号要换成应用侧全局编号,否则跨分区会出现同号。查询条件不带账期就没有分区裁剪,等于白分区。归档边界不能只按时间切,要叠加结清状态,否则未结清账单被搬走,催收直接漏单。适用万级计量点、数据量千万行以上的多园区计费库;不足百万行的项目单表加索引就够。
一、账单表跑三年之后的三条症状
某园区运营方管着 12 个园区、1.6 万个计量点,账期月结。三年下来账单主表累计 2900 万行、单表 168 GB,每月出账还要新增约 240 万行。
症状是攒出来的,一条比一条明显:
财务查单个租户近 24 个月账单,P95 4.3 秒,页面转圈转到财务改用导出。
月末出账批量插入与财务查询撞在一起,出账窗口从 40 分钟拉到 6 小时。
凌晨备份跑到上班时间还没结束,次日的读写都跟着发虚。
索引不是没加,问题是索引之上还有一层:历史账单和当月账单挤在同一棵 B+ 树里,抢同一批缓冲页。
对比维度单表加索引RDS 分区表分区表加表格存储归档数据组织一张表装全量按账期切成物理分区热数据留库,冷数据出库历史查询索引回表,随行数变慢只扫目标分区归档库按租户加账期检索出账写入影响与查询争缓冲页只写当月分区只写当月分区存储成本全量热存全量热存冷数据单价降一个量级改造成本无中,主键要动高,需归档任务与回查接口适用数据量百万行以内千万行级千万行以上且要压成本
选型结论:先用分区把出账和查询隔开,数据再涨再加归档,两步可以分开做,不必一次到位。
二、四步改造与两个易错点
标准化步骤如下,可复现:
定分区键:用账期字段做 RANGE COLUMNS 分区,不用 HASH。账单天然按账期归档和清理,RANGE 能把整个分区一次性切走,HASH 做不到。
改主键:分区键必须出现在主键里,主键从单列编号改成账期加编号。编号不再靠自增,改由应用侧生成 19 位(时间戳加机器位加序列),跨分区才不撞。
建归档表并交换:归档表结构与分区表严格一致,用 EXCHANGE PARTITION 做元数据级切换,秒级完成。
归档条件叠加结清状态:只搬 24 个月以前且已结清的账单,未结清的整行留在热库并进未结清摘要表。
建表与交换语句:
CREATE TABLE bill_month (
bill_month CHAR(7) NOT NULL,
bill_id BIGINT NOT NULL,
tenant_id BIGINT NOT NULL,
settle_flag TINYINT NOT NULL DEFAULT 0,
amount DECIMAL(12,2) NOT NULL,
PRIMARY KEY (bill_month, bill_id),
KEY idx_tenant (tenant_id, bill_month)
) ENGINE=InnoDB
PARTITION BY RANGE COLUMNS (bill_month) (
PARTITION p202401 VALUES LESS THAN ('2024-02'),
PARTITION p202402 VALUES LESS THAN ('2024-03'),
PARTITION pmax VALUES LESS THAN (MAXVALUE)
);
CREATE TABLE bill_month_arc LIKE bill_month;
ALTER TABLE bill_month_arc REMOVE PARTITIONING;
ALTER TABLE bill_month EXCHANGE PARTITION p202401 WITH TABLE bill_month_arc;
归档任务的循环体,按 2000 行一批,写完冷库再删热库:
def archive(month, batch=2000):
rows = query("SELECT bill_id FROM bill_month "
"WHERE bill_month = %s AND settle_flag = 1 LIMIT %s",
(month, batch))
moved = write_tablestore("bill_archive", rows)
delete("DELETE FROM bill_month WHERE bill_month = %s "
"AND settle_flag = 1 AND bill_id IN %s", (month, batch))
return moved
【插图位置 1】aliyun_20261103_rds_fig1_partition.png
这张图说明账单表按账期分区后,热库与归档库之间数据迁移的路径。
对照箭头可见整分区先交换再迁出的顺序,迁移期间热库始终可读写。
有两段通用教程里查不到的经验。
头一段是 EXCHANGE PARTITION 的三个硬条件。归档表要用 CREATE TABLE LIKE 建,它会连分区定义一起复制过去,必须补一句 REMOVE PARTITIONING,否则交换报 1505。另外归档表不能是分区表、不能带外键,列的类型和顺序要逐列对齐。
第二段更隐蔽:交换完成后分区表的统计信息还是旧的,优化器按老行数估算,查询计划会继续走全表扫描。交换完要对目标分区立刻 ANALYZE,不能等自动统计回收。初版上线后出账反而更慢,就是这个原因,改完才回到 42 分钟。
第三条坑在归档边界上。只按时间切的初版跑了一周就出事,某园区两户欠费账单在 24 个月以前,跟着分区一起被搬走,催收清单里直接消失。改法是归档前先跑一次结清校验,未结清的行先按当期口径重算回来,再整体搬走。时间维度归档和账务状态耦合,这是纯数据库教程不会提的地方。
【插图位置 2】aliyun_20261103_rds_fig2_latency.png
这张图说明改造前后账单查询 P95、出账窗口与热库容量三组指标的变化。
三组柱形对比可见查询降一个数量级,出账窗口回到小时以内,热库容量降到四分之一。
三、上线后的收益
改造上线 90 天,账期全部走新表:
单租户 24 个月账单查询 P95 从 4.3 秒降到 380 毫秒,财务不再靠导出。
月末出账窗口从 6 小时回到 42 分钟,批量插入只落当月分区。
热库从 168 GB 降到 41 GB,存储费用降约 63%,备份窗口缩短一半以上。
24 个月以前的查询约每月 60 次,走归档库平均 1.2 秒,业务侧可接受。
三级统一能源计量管理模型(3T-UEM)的层间校验要回看历史账期,账单出库后校验链路改为热库加冷库两段取值,账期口径仍是表端采集时间,差额定位不受归档影响。
四、分区归档问答备忘
Q1:加了分区,查询还是慢?
A:现象是执行计划显示扫了全部分区。原因是 WHERE 里没带分区键,或对分区键做了函数运算。解法:查询条件直接写账期范围,不用 MONTH() 之类的函数包一层。
Q2:交换分区报 1505 错误?
A:现象是表结构看着一样却交换失败。原因是归档表带着分区定义或有外键约束。解法:CREATE TABLE LIKE 之后补 REMOVE PARTITIONING,确认两表列顺序完全一致。
Q3:归档后催收清单少了几户?
A:现象是欠费租户在系统里查不到。原因是归档按时间切,把未结清账单一起搬走。解法:归档条件叠加结清状态,未结清行留在热库并进摘要表。
Q4:主键改了,老业务代码报错?
A:现象是插入时报主键冲突。原因是单列主键变复合主键后,编号不再自增。解法:应用侧统一生成全局编号,数据库不再承担发号职责。
Q5:冷库还要不要索引?
A:要,但只建业务真正查的组合。归档库按租户加账期建二级索引,够覆盖财务稽核的回查场景,索引建多了写入会拖慢。
参考资料
《电能信息采集与管理系统》DL/T 698
《多功能电能表通信协议》DL/T 645
《信息技术 云数据存储和管理》GB/T 31916
《信息安全技术 数据库管理系统安全技术要求》GB/T 20273
本文数据接口与模型定义参考合众致达 3T-UEM 公开技术说明