应使用V$SEGMENT_STATISTICS查分区最后读取时间,因其记录实时I/O统计;LAST_ANALYZED仅表示统计信息收集时间。删除冷分区前须检查全局索引、物化视图日志及外键依赖,否则将导致查询失败。
查分区最后一次被读取的时间:别信LAST_ANALYZED,用V$SEGMENT_STATISTICS
oracle 不记录每个分区的“最后访问时间”,user_tab_partitions.last_analyzed只是统计信息收集时间,和实际查询无关。真要判断冷分区,得看段级 i/o 统计。关键视图是 v$segment_statistics,过滤条件必须带 object_type = 'partition' 和 statistic_name in ('physical reads', 'logical reads')。
常见错误是直接查 DBA_HIST_SEG_STAT——它依赖 AWR 快照,默认只保留 8 天,且需额外许可;而 V$SEGMENT_STATISTICS 是实时内存数据,无需快照,只要实例没重启就一直累积。
示例语句(需 DBA 权限):
SELECT owner, object_name AS table_name, subobject_name AS partition_name,
SUM(CASE WHEN statistic_name = 'physical reads' THEN value ELSE 0 END) phy_reads,
SUM(CASE WHEN statistic_name = 'logical reads' THEN value ELSE 0 END) log_reads
FROM v$segment_statistics
WHERE object_type = 'PARTITION'
AND owner = 'YOUR_SCHEMA'
AND object_name = 'YOUR_TABLE'
GROUP BY owner, object_name, subobject_name
ORDER BY phy_reads + log_reads;注意:V$SEGMENT_STATISTICS 的计数在实例重启后归零,所以它只反映当前生命周期内的热度。
删除冷分区前必须检查的三个依赖项
直接 ALTER TABLE ... DROP PARTITION 很容易失败或引发后续故障,不是语法问题,而是依赖未清理:
- 全局索引是否启用
UPDATE GLOBAL INDEXES?不加这句,索引会进UNUSABLE状态,下次查询直接报错 ORA-01502 - 该分区是否被物化视图日志引用?查
USER_MVIEW_LOGS,若LOG_TABLE列指向该分区所在表,删分区前得先DROP MATERIALIZED VIEW LOG - 是否有外键指向这个分区?
USER_CONSTRAINTS.CONSTRAINT_TYPE = 'R'且R_OWNER/R_CONSTRAINT_NAME关联到本表,说明存在引用完整性约束,删分区可能违反外键
漏掉任意一项,都可能导致应用查询突然失败,且错误不会立刻暴露——比如全局索引失效,要等第一次走索引的查询才触发。
TRUNCATE PARTITION vs DROP PARTITION:选哪个取决于你是否还要保留分区结构
如果目标只是清空数据、释放空间、但保留分区定义(比如下个月还要按同样规则加载新数据),用 TRUNCATE PARTITION 更安全:
- 不校验分区边界依赖,不会触发 ORA-14400
- 不破坏本地索引结构,本地索引保持
VALID - 比
DROP+ADD快一个数量级,尤其对大分区
但如果分区本身已过时(比如按年分区,2020 年分区永远不会再用),且你想彻底移除其元数据和段对象,那就必须 DROP PARTITION。注意:哈希分区在 Oracle 12c 才支持 DROP PARTITION,11g 及更早版本执行会直接报 ORA-00905。
示例(安全删除冷分区):
-- 先清空(可选,为避免锁表太久) ALTER TABLE sales TRUNCATE PARTITION p_2020 DROP STORAGE; <p>-- 再删结构(确认无依赖后) ALTER TABLE sales DROP PARTITION p_2020 UPDATE GLOBAL INDEXES;
自动识别 + 删除冷分区的最小可行脚本
手动查再删太慢,适合写成匿名块批量处理。核心逻辑是:查出物理读为 0 的分区 → 排除系统关键分区(如 MAXVALUE、DEFAULT)→ 按顺序逐个删(避免并发冲突)。
关键限制:不能在同一个事务里删多个分区,ALTER TABLE ... DROP PARTITION 是 DDL,每次都会隐式提交;也不能把 EXECUTE IMMEDIATE 塞进游标循环里不加异常捕获——万一中间某个分区因依赖失败,整个块就停住。
建议写法:
DECLARE
CURSOR cold_parts IS
SELECT partition_name
FROM user_tab_partitions
WHERE table_name = 'SALES'
AND partition_name NOT IN ('P_MAX', 'P_DEFAULT') -- 排除边界分区
AND partition_name NOT IN (
SELECT subobject_name
FROM v$segment_statistics
WHERE object_name = 'SALES'
AND statistic_name = 'physical reads'
AND value > 0
);
BEGIN
FOR p IN cold_parts LOOP
BEGIN
EXECUTE IMMEDIATE 'ALTER TABLE SALES DROP PARTITION ' || p.partition_name ||
' UPDATE GLOBAL INDEXES';
DBMS_OUTPUT.PUT_LINE('Dropped: ' || p.partition_name);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Failed on ' || p.partition_name || ': ' || SQLERRM);
END;
END LOOP;
END;真正难的是判断“长时间”——V$SEGMENT_STATISTICS 没有时间戳,只能靠运维约定(比如连续 90 天无读取)。这点没法自动化,得人工核对业务 SLA。


















