资讯动态

Oracle分区自动增长:按月/按天自增与定时任务实践

发布时间:2026/9/19 0:20:54 来源:尧图企业网站定制
数据量一上来单表最先扛不住的往往不是 CPU 也不是 IO而是维护这两个字。我手上有个订单流水表三年下来攒了 2 亿多行最初设计的时候没做分区结果每个月的归档脚本跑一次要四个多小时删历史数据用 DELETE 能把回滚段撑爆收集统计信息还得全表扫一遍。后来把它改成按天分区归档动作从删数据变成删分区秒级完成统计信息只收当天分区几十秒搞定。再往后就发现光有分区还不够——分区得有人建忘了建分区半个月后运维半夜被电话叫起来处理 ORA-14400那种滋味我不想再体验第二次。所以就有了这篇东西聊的是 Oracle 里怎么让表分区自己长出来重点是按月自增表分区和按天自增表分区这两条最常用的路子。内容会从版本前提、分区键选型一路讲到 INTERVAL 自动分区的建表细节、存储过程加定时任务的兜底方案以及上线之后怎么查、怎么排错。不管你是刚接手的 DBA还是被分区维护折腾过的后端开发照着走一遍基本都能落地。1. 分区表到底在解决什么问题在聊自增之前得先把分区表的价值说清楚不然很容易变成为了分区而分区。1.1 一张单表撑到几亿行之后发生了什么单表膨胀到一定量级症状通常是这样几条同时冒出来查询只要带时间范围执行计划却死活走不上索引因为优化器算出来全表扫反而看起来更便宜历史数据清理只能靠 DELETE一次删几千万行undo 表空间先炸归档日志跟着暴涨主备延迟瞬间拉到几十分钟统计信息收集变成灾难全表 100% 采样跑一整晚跑完业务高峰又来了统计信息立刻过期。分区表的核心思路是分而治之——按时间维度把一张大表在物理上切成很多小段逻辑上还是一张表。好处立刻显现查询带时间条件时触发分区裁剪Partition Pruning只扫命中的那一两个分区IO 量直接砍掉两个数量级清理历史数据用ALTER TABLE ... DROP PARTITION这是一个 DDL 操作几乎是瞬间完成不产生大量 undo 和 redo统计信息可以按分区收集而且可以开增量统计只算新分区。这里有个很多人忽略的细节分区裁剪要生效查询条件里的时间字段必须和分区键是同类型或者显式转换。我见过最典型的翻车是把分区键设成 DATE但 SQL 里写的是WHERE create_time 2024-01-01字符串被隐式转换裁剪失效然后开始怀疑分区是不是建错了。养成写TO_DATE(2024-01-01,YYYY-MM-DD)或者DATE 2024-01-01的习惯能省掉一堆排查时间。1.2 手动建分区为什么把自己逼疯分区表初期都是手动建的建表的时候一口气写上未来十二个月的分区看起来很整齐。问题是这套东西没有自动续期能力。十二个月之后呢靠人记。我见过用 Excel 记的也见过在日历上打红圈的还有干脆写个文本文件放共享盘上交接的时候谁也没看到。手动维护的痛点集中在三处。第一是容易忘尤其是月初、年初这种节点业务高峰撞上忘记加分区插入直接报 ORA-14400页面白屏。第二是命名混乱张三写 P202401李四写 P_2024_01王五写 PART_2401一个库里三种风格后面写运维脚本得写三个匹配规则。第三是加分区要停业务早期版本ALTER TABLE ... ADD PARTITION虽然不锁表数据但会短暂持有 DDL 锁如果在高并发写入时执行会话排队会很明显。所以自增这两个字的价值本质上是把人记换成系统记把高峰执行换成低峰预建。1.3 两条自增分区路线的取舍Oracle 里让分区自动增长主流就两条路。一条是INTERVAL 分区Oracle 11g 引入的官方特性。建表时只需要写一个间隔表达式插入的数据超出已有分区范围时数据库自己把分区建出来。写法极简几乎零维护。另一条是存储过程 DBMS_SCHEDULER 定时任务。自己写逻辑定时提前把分区铺好分区名完全可控清理归档也能一起做。两条路不是替代关系而是适用场景不同。INTERVAL 胜在省事适合没人管、就想它能自己跑的表存储过程方案胜在可控适合分区名需要和归档脚本、外部 ETL 约定好的场景或者需要配合分区交换加载数据的数仓表。下面两条路线我都会完整走一遍你按自己的情况挑。2. 动手前的规划版本、分区键、表空间分区这事最大的坑不在语法而在动手之前的规划。规划错了后期改起来要么停业务要么重建表。2.1 版本与特性对照INTERVAL 分区从 11gR111.1开始提供但早期版本对 TIMESTAMP 类型的分区键支持有欠缺11.2 之后才比较完整。另外ALTER TABLE ... SET INTERVAL这个给已有分区表加上自动分区能力的语法也是 11.2 之后才稳定可用。版本INTERVAL 分区SET INTERVAL 改造老表备注10g不支持不支持只能手动建分区11.1支持不支持TIMESTAMP 分区键限制较多11.2支持支持生产上最常用的起点12c / 19c支持支持在线移动分区、异步全局索引等增强如果你还在 10g那就只有存储过程这一条路而且SPLIT PARTITION的写法要更小心。如果版本在 11.2 及以上两条路都能走。注意INTERVAL 是分区选项不额外收费属于企业版的基础分区特性。但分区本身在企业版里是收费选项标准版上没有动手前先确认一下手上的授权。这个不确认清楚后面做审计的时候很难解释。2.2 分区键选 DATE 还是 TIMESTAMP 还是 NUMBER分区键的选择直接决定了后面所有的写法。选DATE是最稳的。一秒精度占用 7 字节TRUNC()、ADD_MONTHS()这些函数都是原生支持按天按月分区的表达式最简单。绝大多数按时间分区的业务表DATE 就够了。选TIMESTAMP要谨慎。INTERVAL 分区要求间隔类型和分区键类型严格对应DATE 用NUMTOYMINTERVAL/NUMTODSINTERVALTIMESTAMP 在 11.2 之后也支持同样的间隔类型但低版本容易报错。另外 TIMESTAMP 占 11 字节存储和比较开销都更大。除非业务真的需要毫秒精度否则我不建议用 TIMESTAMP 做分区键。选NUMBER往往是历史遗留。有些表把时间存成了20240101这样的数字或者用自增 ID 做分区键。NUMBER 做分区键时INTERVAL 的步长就写INTERVAL (1)这种纯数字。能用但可读性差DROP PARTITION之前还得先算清楚对应哪一天。还有一个硬性前提分区键必须是 NOT NULL。Oracle 不允许分区键为空建表时最好显式加上NOT NULL约束别指望业务代码自觉。2.3 表空间与索引的配套设计分区建好之后如果不指定表空间所有分区都挤在默认表空间里那分区的好处就只剩裁剪运维上的好处丢了一半。推荐的做法是按时间粒度分开比如按月的表把最近 3 个月的分区放高速存储历史分区放普通存储按天的表把最近 30 天放高速盘。索引这边分区表的索引分两种局部索引LOCAL和全局索引GLOBAL。局部索引每个分区一个独立索引段DROP PARTITION时索引自动跟着删代价极低全局索引是一整棵 B 树跨所有分区DROP PARTITION会导致索引失效除非加UPDATE GLOBAL INDEXES但那个代价就大了。我的经验是分区表默认全部用局部索引除非有明确的主键全局唯一需求。如果确实需要全局唯一约束那就得接受运维上的额外成本DROP PARTITION必须加UPDATE GLOBAL INDEXES而且要在低峰做。另外提一个容易被忽视的配置建分区表时加上ENABLE ROW MOVEMENT。开启之后更新分区键导致行跨分区移动时数据库会自动处理否则直接报 ORA-14402。有时候业务需要修正时间字段没有这个选项会很麻烦。3. 路线一INTERVAL 自动分区写一次管一年先说结论如果你的表是纯写入的流水表分区名不需要跟外部系统约定选 INTERVAL没有第二个答案。3.1 按天自增分区的建表语句逐行拆解先看按天自增的完整写法CREATE TABLE t_order_flow ( order_id NUMBER(18) NOT NULL, order_time DATE NOT NULL, user_id NUMBER(12), amount NUMBER(12,2), status VARCHAR2(20), CONSTRAINT pk_t_order_flow PRIMARY KEY (order_id, order_time) ) ENABLE ROW MOVEMENT PARTITION BY RANGE (order_time) INTERVAL (NUMTODSINTERVAL(1, DAY)) ( PARTITION p_20240101 VALUES LESS THAN (DATE 2024-01-02) ) TABLESPACE tbs_order_active;逐段解释。PARTITION BY RANGE (order_time)声明按范围分区这是 INTERVAL 的基础。INTERVAL (NUMTODSINTERVAL(1, DAY))就是自增的核心——每超过一个已有分区的上界一天就自动派生一个新分区。最后那个括号里的p_20240101是初始分区它必须存在而且它的上界就是自动分区的起点。这里VALUES LESS THAN (DATE 2024-01-02)意味着这个分区装的是 2024-01-01 当天及之前的数据从 2024-01-02 开始每过一天自动长一个分区。ENABLE ROW MOVEMENT前面说过了更新分区键时有用。主键我写成(order_id, order_time)因为它包含分区键可以做成局部唯一索引如果只写PRIMARY KEY (order_id)Oracle 会强制建全局唯一索引反而失去了局部索引的好处。这里有个细节值得说明初始分区的边界一定要选在已有数据的最早时间之前。如果表里已经有一批 2023 年的历史数据但初始分区上界写的是 2024-01-02Oracle 在迁移或插入时就会发现这些数据没有分区可落报 ORA-14400。所以正确做法是初始分区上界要早于或者等于历史数据的最小时间。3.2 按月自增分区只改一个表达式按月的写法把间隔表达式换掉就行CREATE TABLE t_order_month ( order_id NUMBER(18) NOT NULL, order_time DATE NOT NULL, user_id NUMBER(12), amount NUMBER(12,2), status VARCHAR2(20) ) ENABLE ROW MOVEMENT PARTITION BY RANGE (order_time) INTERVAL (NUMTOYMINTERVAL(1, MONTH)) ( PARTITION p_202401 VALUES LESS THAN (DATE 2024-02-01) ) TABLESPACE tbs_order_month;注意这里NUMTOYMINTERVAL(1, MONTH)里的单位是MONTH不是MONTHS虽然 Oracle 两种都能识别但按官方写法来更稳妥。同理按年就是NUMTOYMINTERVAL(1, YEAR)按季度可以写NUMTOYMINTERVAL(3, MONTH)。按月分区适合什么场景数据量中等的表比如用户表、配置变更记录、月度对账单。判断标准很简单如果一天的数据量撑不起一个独立的段比如一天只有几千行按天分区反而会因为分区数量过多拖慢数据字典查询DBA_TAB_PARTITIONS 动辄几千行。我的经验阈值是单天数据量在 500 万行以上或者 1GB 以上按天分区否则按月。还有一个混搭技巧分区键用TRUNC(order_time, MM)这种函数不行Oracle 的分区键不支持函数但可以通过在表上额外挂一个月份字段来实现按月分区。这个做法会增加维护成本一般不建议。3.3 自动建分区的真实代价与规避手法INTERVAL 分区不是没有代价最大的坑是自动建分区是一个 DDL会短暂持有锁而且在高并发插入时可能引发争用。设想一下业务在凌晨 0 点零几分开始写入新一天的数据第一个命中新分区的会话会触发一次 DDL 建分区。如果此刻有几百个会话同时插入它们都要等这个 DDL 完成会出现一个短暂的等待波峰。平时无所谓但在大促这种每秒几万笔的场景下几百毫秒的抖动也要计较。规避办法是提前把分区建出来。有个不太为人知的小技巧往表里插一条数据再回滚INSERT INTO t_order_flow (order_id, order_time, amount) VALUES (-1, TRUNC(SYSDATE) 7, 0); ROLLBACK;ROLLBACK会把数据行回滚掉但已经派生出来的分区不会消失。所以只要在业务低峰期跑这么一句就能把未来第 7 天的分区提前铺好。你可以把它写进每日的定时任务里效果等于预热。第二个代价是分区名不可控。INTERVAL 自动建出来的分区名字是系统生成的SYS_P12345这种。如果你需要按分区名去归档比如要求所有历史分区命名成P20240101那就得在自动建完之后ALTER TABLE ... RENAME PARTITION SYS_P12345 TO p_20240101。每次都要重命名还不如直接走存储过程方案。第三个代价是统计信息容易落在新分区上不及时。自动建出来的分区初始是没有任何统计信息的。如果表开了增量统计INCREMENTAL YES收统计信息的时候会只算新分区很快如果没开就要全表重算那自动分区带来的好处会被统计信息收集完全吃掉。建表之后记得补上BEGIN DBMS_STATS.GATHER_TABLE_STATS( ownname USER, tabname T_ORDER_FLOW, granularity PARTITION, cascade TRUE, degree 4 ); END; /3.4 给已经上线的老表加 INTERVAL很多表是先上线、后分区这时候不用重建表可以直接改造-- 前提表上不能有 MAXVALUE 分区 ALTER TABLE t_order_old SET INTERVAL (NUMTODSINTERVAL(1, DAY));如果表上已经有一个VALUES LESS THAN (MAXVALUE)的兜底分区这条语句会直接报错。解决办法是先把这个 MAXVALUE 分区拆开ALTER TABLE t_order_old SPLIT PARTITION p_max AT (DATE 2024-01-02) INTO (PARTITION p_20240101, PARTITION p_max) UPDATE GLOBAL INDEXES;把 MAXVALUE 分区拆小之后再重新执行SET INTERVAL。注意这两步都要在业务低峰做SPLIT PARTITION本身要移动数据如果 MAXVALUE 分区里已经堆了几千万行执行时间会很长而且会消耗大量临时表空间。4. 路线二存储过程 定时任务分区名自己说了算如果你的分区名必须严格可控或者需要做分区交换加载Exchange Partition这类动作那就老老实实写存储过程。4.1 为什么有些场景必须放弃 INTERVAL几种典型情况。第一是数仓场景每天从上游同步数据同步方式是先把数据加载到一张中间表再用ALTER TABLE ... EXCHANGE PARTITION把中间表整块换成分区这个操作要求分区名和目标表严格匹配用 INTERVAL 生成的 SYS_P 名字根本对不上。第二是归档脚本按名索引很多团队的归档系统是通过分区名反推日期的SYS_P12345完全无解。第三是需要预建多天分区比如数据补录场景今天要往未来 30 天里的任意一天插数据INTERVAL 需要每条数据都触发一次 DDL不如一次性建好。这三种情况的共同要求是分区名和业务日期一一对应且分区必须在数据写入之前就存在。4.2 建表留一个 P_MAX 兜底分区存储过程方案的表结构核心区别是留一个 MAXVALUE 兜底分区CREATE TABLE t_order_daily ( order_id NUMBER(18) NOT NULL, order_time DATE NOT NULL, user_id NUMBER(12), amount NUMBER(12,2), status VARCHAR2(20) ) ENABLE ROW MOVEMENT PARTITION BY RANGE (order_time) ( PARTITION p_20240101 VALUES LESS THAN (DATE 2024-01-02), PARTITION p_20240102 VALUES LESS THAN (DATE 2024-01-03), PARTITION p_max VALUES LESS THAN (MAXVALUE) ) TABLESPACE tbs_order_active;这个 P_MAX 有两个作用。一是兜底万一定时任务漏跑新一天的数据不会报 ORA-14400而是安静地堆到 P_MAX 里业务不中断——虽然数据都堆在一起性能会差但至少不挂。二是拆分源后面每次新增分区都是从 P_MAX 里切一块出来。这里有个陷阱如果 P_MAX 长期不清理它会越来越大拆分的代价也越来越高。SPLIT PARTITION的执行时间跟被拆分分区的大小直接相关一个堆了 5000 万行的 P_MAX 拆一次要好几分钟。所以定时任务失败之后的补跑要及时别让它堆。注意用 MAXVALUE 兜底的分区表如果大量数据落入 P_MAX会导致这个分区的段和索引异常庞大查询计划可能变差。上线初期可以设置监控一旦 P_MAX 的行数超过阈值就报警。4.3 存储过程提前 N 天把分区铺好下面这个存储过程是核心逻辑是从明天开始往后 N 天逐个检查分区是否存在不存在就从 P_MAX 里拆出来。CREATE OR REPLACE PROCEDURE prc_add_daily_partition ( p_table_name IN VARCHAR2, p_days_ahead IN NUMBER DEFAULT 3 ) AUTHID CURRENT_USER IS v_table VARCHAR2(64); v_target_date DATE; v_part_name VARCHAR2(30); v_cnt NUMBER; v_sql VARCHAR2(1000); BEGIN v_table : UPPER(p_table_name); FOR i IN 1 .. p_days_ahead LOOP v_target_date : TRUNC(SYSDATE) i; v_part_name : P || TO_CHAR(v_target_date, YYYYMMDD); -- 判断分区是否已存在 SELECT COUNT(*) INTO v_cnt FROM user_tab_partitions WHERE table_name v_table AND partition_name v_part_name; IF v_cnt 0 THEN v_sql : ALTER TABLE || v_table || SPLIT PARTITION p_max AT (DATE || TO_CHAR(v_target_date, YYYY-MM-DD) || ) INTO (PARTITION || v_part_name || , PARTITION p_max) UPDATE GLOBAL INDEXES; EXECUTE IMMEDIATE v_sql; DBMS_OUTPUT.PUT_LINE(created || v_part_name); ELSE DBMS_OUTPUT.PUT_LINE(v_part_name || exists, skip); END IF; END LOOP; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(ERROR: || SQLERRM); RAISE; END prc_add_daily_partition; /几个关键点。AUTHID CURRENT_USER是为了让存储过程以调用者的权限执行这样user_tab_partitions查到的是调用者自己的表。UPDATE GLOBAL INDEXES必须在语句里加上否则如果有全局索引会失效加了之后的代价是 DDL 期间索引会重建一部分但可以保持索引一直可用。SPLIT PARTITION ... AT (DATE 2024-01-05)的含义是把 P_MAX 从 2024-01-05 这个点切开边界以下的部分成为新分区p_20240104注意命名和边界的关系p_20240104上界是 01-05装的是 01-04 当天边界以上留在 P_MAX。所以v_target_date传的是上界日期这个名字我特意起成 target_date 而不是 partition_date就是提醒自己这里是上界。调用方式BEGIN prc_add_daily_partition(T_ORDER_DAILY, 7); END; /这句意思是把未来 7 天的分区都准备好。按月分区的存储过程几乎一样只改两处日期用ADD_MONTHS(TRUNC(SYSDATE,MM), i)计算分区名改成P || TO_CHAR(v_target_month, YYYYMM)AT 的边界自然是下个月 1 号。4.4 DBMS_SCHEDULER 任务配置与补跑逻辑存储过程有了接下来让它每天自动跑。用 DBMS_SCHEDULER别用老的 DBMS_JOB前者支持重复间隔表达式、日志、链式任务运维上省心很多。BEGIN DBMS_SCHEDULER.CREATE_JOB ( job_name JOB_ORDER_DAILY_PART, job_type STORED_PROCEDURE, job_action PRC_ADD_DAILY_PARTITION, number_of_arguments 2, start_date TRUNC(SYSDATE) 1/24, repeat_interval FREQDAILY; BYHOUR1; BYMINUTE30; BYSECOND0, enabled FALSE, comments 每日预建 7 天分区 ); DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE(JOB_ORDER_DAILY_PART, 1, T_ORDER_DAILY); DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE(JOB_ORDER_DAILY_PART, 2, 7); DBMS_SCHEDULER.ENABLE(JOB_ORDER_DAILY_PART); END; /注意CREATE_JOB的时候先enabled FALSE参数设完了再ENABLE否则带参数的 job 在参数没设置完就启用会报错。时间选在凌晨 1 点半理由有几个一是业务已经过了午夜的写入峰值松弛了二是 0 点那一波数据已经落进来能看到实际的分区使用情况三是万一 task 失败还有整个凌晨的时间可以补救。repeat_interval的语法是 iCalendar 风格FREQDAILY; BYHOUR1; BYMINUTE30表示每天 1 点半。如果担心单点失败可以加一个每天中午的补跑任务把p_days_ahead设得更大比如 14 天任务天然是幂等的先查再建重复跑不会出问题。补跑逻辑我一般这么处理加一个监控每天检查一下user_tab_partitions里 P_MAX 之外的最大分区边界距今有多少天如果小于 2 天就发告警。因为任务本身设计成幂等的所以收到告警之后人工跑一次存储过程就行不需要额外写补偿代码。4.5 历史分区的清理与归档分区建起来只是第一步历史分区得定期清。最简单的做法ALTER TABLE t_order_daily DROP PARTITION p_20240101 UPDATE GLOBAL INDEXES;如果表上全是局部索引UPDATE GLOBAL INDEXES可以省略速度飞快。DROP PARTITION会直接把分区段和数据文件回收掉不像 DELETE 那样产生巨量 undo也不像TRUNCATE PARTITION那样在并行场景下争用严重。但大多数场景要求数据留一份归档。这时候可以用分区交换-- 建一张结构相同的归档表 CREATE TABLE t_order_daily_arch AS SELECT * FROM t_order_daily WHERE 10; -- 把 20240101 分区换到归档表 ALTER TABLE t_order_daily EXCHANGE PARTITION p_20240101 WITH TABLE t_order_daily_arch WITHOUT VALIDATION UPDATE GLOBAL INDEXES; -- 把空出来的分区干掉 ALTER TABLE t_order_daily DROP PARTITION p_20240101 UPDATE GLOBAL INDEXES;WITHOUT VALIDATION表示不校验数据行是否真的在分区范围内省去一次全分区扫描前提是你确信数据是有序落进来的。这一步在生产上非常常见整个流程走完一般不到一秒比 DELETE 快了四个数量级。归档动作我建议也写进定时任务比如每月 5 号跑一次把 90 天以前的分区全部交换出去交换完的分区数据再压缩存到归档库。但归档前一定要看一遍分区名对应的日期范围我见过有人算错月份把还在用的分区换走了那个事故处理起来很痛苦。5. 运维阶段查询、监控、排错分区表上线之后日常的活儿主要是查分区状态、看有没有漏建、出了问题怎么快速定位。5.1 三条常用分区信息查询SQL第一条看分区名单和边界SELECT partition_name, partition_position, high_value, num_rows, last_analyzed FROM user_tab_partitions WHERE table_name T_ORDER_DAILY ORDER BY partition_position;第二条看某个分区的实际大小和行数比 num_rows 更准SELECT segment_name, partition_name, bytes / 1024 / 1024 AS mb, bytes FROM user_segments WHERE segment_name T_ORDER_DAILY AND segment_type TABLE PARTITION ORDER BY partition_name;第三条看 P_MAX 里堆了多少数据这个最关键能第一时间发现定时任务漏跑SELECT COUNT(*) FROM t_order_daily PARTITION (p_max);high_value字段是 LONG 类型直接在 SQL Developer 或者 Navicat 里显示会很不友好经常只显示一部分。要加工的话可以写个函数转成 VARCHAR2或者用DBMS_METADATA.GET_DDL取出整张表的定义看。LONG 类型不能直接WHERE或者比较也不能SUBSTR这是老版本的一个坑12c 之后可以用TO_LOB转成 CLOB 再处理。5.2 常见报错速查表分区相关的报错不多但每一条都不太好猜。整理一下我实际遇到过的报错代码报错信息大意常见原因处理方式ORA-14400插入的分区键没有映射到任何分区分区没提前建或上界算错检查 P_MAX 是否存在补跑存储过程ORA-14759不允许为 INTERVAL 分区表指定 MAXVALUE 分区建表时同时写了 INTERVAL 和 MAXVALUE去掉 MAXVALUE保留初始分区ORA-14760不允许对 INTERVAL 分区表执行 ADD PARTITION用存储过程的思路去操作 INTERVAL 表先SET INTERVAL (NULL)再 ADD或改手工ORA-14074新分区界限必须高于最后一个分区ADD PARTITION的界值算错检查MAX(high_value)再定界ORA-14402更新分区键导致行需要跨分区移动表没开ENABLE ROW MOVEMENT加上ENABLE ROW MOVEMENTORA-14100分区界限的数据类型不匹配AT 的表达式类型和分区键不一致显式用DATE 2024-01-01或 TO_DATEORA-01502索引或分区索引不可用DROP/SPLIT PARTITION未带UPDATE GLOBAL INDEXES重建索引或补上更新选项这里重点说 ORA-14760。很多人第一次用 INTERVAL 表的时候想手动补一个分区结果报这个错。原因是 INTERVAL 表的自动派生机制和ADD PARTITION是互斥的。要手动加得先把自动机制关掉ALTER TABLE ... SET INTERVAL (NULL);然后就能自由 ADD/DROP 了。反过来如果哪天不想要手动维护了再执行SET INTERVAL (NUMTODSINTERVAL(1,DAY))把自动开回来。5.3 踩坑清单与经验最后把几个我踩过、也见别人踩过的坑列一下都是文档里不太会写的东西。第一别在业务高峰期跑 SPLIT。SPLIT PARTITION会移动数据被拆分区越大耗时越长期间会持有排他 DDL 锁表上的 DML 全部阻塞。我有次在一个 4000 万行的 P_MAX 上做拆分本来以为几秒钟的事结果跑了将近两分钟业务全卡在那差点出事故。经验值是P_MAX 分区行数超过 200 万就要考虑加监控告警了。第二分区名里不要用下划线前缀以外的特殊字符。有人喜欢写P-2024-01-01结果在动态 SQL 里拼接的时候被当成减号处理语法错误查半天。统一用P 纯数字最省事。第三ENABLE ROW MOVEMENT只在需要的时候开。它带来便利的同时更新分区键的操作开销会变大因为要跨分区移动数据行。如果业务上从来不更新时间字段那不开也没问题。第四局部索引不是万能的。如果查询条件里不包含分区键局部索引会退化成扫所有分区的索引段性能可能比全局索引差。分区表的设计原则是查询模式里必须有分区键这是用分区表的前提假设。如果业务里有大量不带时间条件的查询得考虑建几个独立的物化视图或者汇总表来兜底。第五统计信息要按分区收且开增量。建表时加上INCREMENTAL YESALTER TABLE t_order_daily SET INCREMENTAL ON;这样每次只收新分区和变化分区的统计信息全表收集从几个小时缩短到几分钟。前提是开启了分区级的统计且表的分区有PARTITION粒度的统计。第六备份策略要跟着改。分区表之后可以按分区做备份比如只备份最近 30 天的分区历史分区从归档库恢复。但 Rman 的备份策略要重新设计特别是有DROP PARTITION操作的时候默认的全库备份会很快把备份空间吃掉。第七也是我最想强调的一点定时任务本身要有监控。任务配好了不代表它会一直跑可能会因为权限、表空间满、存储过程被改坏等等原因静默失败。DBA_SCHEDULER_JOB_LOG里能看到运行日志每天扫一遍有 FAILED 状态就报警。分区维护这个事的正确心态不是配好了就完事而是配好了然后盯着它。SELECT job_name, log_date, status, error#, additional_info FROM dba_scheduler_job_log WHERE job_name JOB_ORDER_DAILY_PART AND log_date SYSDATE - 7 ORDER BY log_date DESC;这条查询我基本每个月都会跑一次尤其是做变更之后。关于选用哪条路线我自己的判断标准很朴素分区名不需要跟任何外部系统对齐就用 INTERVAL需要对齐就用存储过程。别为了省事硬把 INTERVAL 塞进数仓流程也别为了可控在纯流水表上写一堆存储过程两种选择都是在给未来省维护成本。分区表这玩意儿真正花时间的从来不是建表那几分钟而是后面一两年的持续照看把自动化做扎实才是真正意义上的省心。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价