适用版本:DM7 / DM8(单机、主备、读写分离、DMDSC 等架构)
适用场景:日志表 / 流水表 / 历史订单表,数据量千万~百亿级大表,需定期清理或一次性归档
核心目标:业务不中断、锁粒度最小、归档日志可控、回滚段不爆
先看一个真实现场:某业务表 TRADE_LOG 约 8 亿行,DBA 执行:
DELETE FROM TRADE_LOG WHERE create_time < '2026-01-01';
-- 跑了1小时甚至更久,业务开始大面积超时,sql无法执行完成,无法close会话,产生大量回滚
事后复盘,踩了四个坑:
| 问题 | 根因 |
|---|---|
| 业务卡死 | DELETE 默认逐行加行锁,扫描期间大量行被锁,并发 DML 排队 |
| 归档日志暴涨 | 大事务 REDO 全量记录,日志量 ≈ 删除数据量的 1~2 倍 |
| UNDO 撑满 | 未提交前旧版本全在回滚段,回滚表空间 100% |
| 无法中断 | KILL 会话后回滚同样耗时,业务"二次卡顿" |
结论:达梦里删大表数据,超过 百万行 级别就不该用单条 DELETE。
按对业务影响从小到大排序:
┌──────────────────────────────────────────────┐
│ ① 分区表 TRUNCATE PARTITION(最优) │ ← 秒级、极少日志、不锁全表
├──────────────────────────────────────────────┤
│ ② CTAS 重建法(表可停写时最快) │ ← RENAME 切换,历史数据扔给归档表,保留要的数据
├──────────────────────────────────────────────┤
│ ③ 循环小批量 DELETE(通用兜底) │ ← 每次 1~5 万行,分批提交
├──────────────────────────────────────────────┤
│ ④ 普通 DELETE(仅适合万级以内) │ ← 小表/小范围才用
└──────────────────────────────────────────────┘
下面逐个展开,每个方案都给"适用条件 + 操作步骤 + 风险点"。
如果你的表已经按时间分区(RANGE 分区最常见),清理历史分区是达梦最优雅的操作。
| 维度 | DELETE | TRUNCATE PARTITION |
|---|---|---|
| 速度 | 逐行删,慢 | 秒级(只改元数据) |
| 日志量 | 大量 REDO | 极少(DDL 级) |
| 锁 | 行锁→表级等待 | 短暂表级锁,不等数据量 |
| UNDO | 全量旧版本 | 几乎为零 |
-- 1) 查看表分区信息
SELECT table_name, partition_name, high_value
FROM user_tab_partitions
WHERE table_name = 'TRADE_LOG'
ORDER BY partition_name;
-- 2) 确认要清理的分区(比如 2021 年 Q1)
-- 假设分区名:P2021Q1
-- 3) 执行(在线,秒级完成)
ALTER TABLE TRADE_LOG TRUNCATE PARTITION P2021Q1;
-- 4) 如果需回收空间(可选)
ALTER TABLE TRADE_LOG TRUNCATE PARTITION P2021Q1 DROP STORAGE;
ALTER INDEX IDX_TRADE_GLOBAL REBUILD;
✅ 设计建议:流水类大表从建表第一天就用 RANGE 分区(按月/按天),清理时永远走
TRUNCATE PARTITION,这是最省心的方案。
表没分区,但数据逻辑上可以"整段"归档(比如某月数据已经冷了
适用:表 10 亿行,要删 80%,且业务可接受该表停写业务。
"反向思维"——只把要留的 SELECT 出来建新表,DROP 旧表 RENAME 新表。
-- 1) 创建新表(只保留最近 1 年数据):
-- 当根据时间过滤数据比较少的情况可以这样建表,如果数据量依然比较大,可以选择使用dts迁移数据。
CREATE TABLE TRADE_LOG_NEW AS
SELECT * FROM TRADE_LOG
WHERE create_time >= DATE '2026-01-01';
-- 2) 补索引、补约束(在新表上建好)
CREATE INDEX IDX_TRADE_TIME ON TRADE_LOG_NEW(create_time);
-- 3) 停写窗口(应用侧或触发器/重命名窗口)
-- 4) 原子切名(毫秒级)
alter table TRADE_LOG RENAME TO TRADE_LOG_OLD;
alter table TRADE_LOG_NEW RENAME TO TRADE_LOG;
-- 5) 旧表归档到冷库或 DROP
-- DROP TABLE TRADE_LOG_OLD; (确认无误后)
| 维度 | 评价 |
|---|---|
| 速度 | 比 DELETE 快 5~10 倍(只写一次有效数据) |
| 风险 | 需要停写窗口,索引重建 |
| 日志 | 新建表有日志,但 DROP 旧表可快速清理 |
| 适用 | 月结/夜跑/可维护窗口场景 |
| 停写 | 必须,适合大版本维护窗口 |
不能停业务、表没分区、数据量千万~亿级——这是最常用方案。
CREATE OR REPLACE PROCEDURE P_DEL_TRADE_LOG(p_batch_size INT DEFAULT 5000)
AS
v_cnt INT := 1;
v_total INT := 0;
v_cutoff DATE := DATE '2026-01-01';
BEGIN
WHILE v_cnt > 0 LOOP
DELETE FROM TRADE_LOG
WHERE create_time < v_cutoff
AND ROWNUM <= p_batch_size; -- 关键:限制行数
v_cnt := SQL%ROWCOUNT;
v_total := v_total + v_cnt;
COMMIT; -- 每批提交,释放 UNDO 和锁
-- 可选:每批之间让出一点 IO
-- DBMS_LOCK.SLEEP(1);
END LOOP;
DBMS_OUTPUT.PUT_LINE('Total deleted: ' || v_total);
END;
/
执行:
SET SERVEROUTPUT ON;
CALL P_DEL_TRADE_LOG(5000);
| 参数 | 建议值 | 依据 |
|---|---|---|
| 单批行数 | 1万~5万 | 观察 UNDO 表空间使用率 < 40% |
| 批次间隔 | 1~3 秒 | 给 REDO 刷盘和复制/归档留余量 |
| 并发 | 单会话串行 | 多会话 DELETE 同一表易互相阻塞 |
| 时间条件 | 有索引列 | create_time 上必须有索引 |
-- 另开一个会话,模拟业务写入
INSERT INTO TRADE_LOG(...) VALUES(...); -- 应秒级成功
-- 查看锁等待
SELECT * FROM V$LOCK WHERE BLOCKED=1;
✅ 只要 DELETE 条件是
create_time < 冷数据边界,且边界不在业务当前写入区间,行锁不会撞上新数据。
百万行以上 DELETE 不 COMMIT 是会导致ROLL撑得特别大,数据库重启会特别慢。
-- 提前检查 UNDO 配置,UNDO_RETENTION 建议不超过900
SELECT PARA_NAME, PARA_VALUE FROM V$DM_INI
WHERE PARA_NAME IN ('UNDO_RETENTION','UNDO_EXTENT_NUM','ROLL_SEG_SIZE');
大 DELETE 产生大量 REDO → 归档目录写满 。
虽然达梦数据库归档日志已经设置比较大的空间上限,不会因为大量归档而撑满磁盘,但是会导致归档保留时间较短。
# 提前确认归档路径和剩余空间
# DM SQL
SELECT ARCH_DEST FROM V$DM_ARCH_INI;
# OS
df -h /dmarch
-- 清理旧归档(DM 自带)
SP_ARCH_DELETE('2026-08-01 00:00:00'); -- 删除指定时间前归档
DELETE 只删数据行,数据块仍标记为空闲但不还给 OS,高水位线不降。
-- 查看表高水位
SELECT TABLE_NAME, BLOCKS, EMPTY_BLOCKS FROM USER_TABLES;
行锁、UNDO 段、REDO 缓冲、事务槽 等资源竞争,不适合并行。
-- 这是错的(并行 DML 对 DELETE 不友好)
DELETE /*+ PARALLEL(8) */ FROM BIG_TABLE WHERE ...;
大表并行 DELETE 容易产生锁等待和日志争用。小批串行 + 索引条件 更稳。
产生大量归档,备库重演压力较大
-- 清理前检查主备延迟
SELECT * FROM V$RECOVERY_STATUS; -- 主库看备库重做状态
SELECT * FROM V$ARCH_SEND_INFO; -- 归档发送进度
-- 小批量 + 分批提交可控制延迟
-- 清理期间不要做主备切换
表是分区表吗?
├─ 是 → 按时间条件恰好是分区边界?
│ ├─ 是 → TRUNCATE PARTITION ✅(首选)
│ └─ 否 → 分区内小批量 DELETE(按分区 + ROWNUM 限行)
└─ 否 → 有维护窗口吗?
├─ 有 → CTAS 重建 / RENAME 切换 ✅(最快)
└─ 无 → 循环小批量 DELETE ✅(通用兜底)
以下内容可直接贴到《数据库开发规范》中。
TRUNCATE PARTITION;SELECT COUNT(*) FROM TRADE_LOG;
SELECT TABLE_NAME, BLOCKS, EMPTY_BLOCKS FROM USER_TABLES WHERE TABLE_NAME='TRADE_LOG';
能 TRUNCATE 就不 DELETE,能分批就不一把梭,能停写就重建——记住这三句,线上删数据基本不会出事故。
| 项目 | 建议 |
|---|---|
| 主备延迟 | 大事务 DELETE 会导致备库重做堆积,延迟飙升;小批量 + 分批提交可控制延迟 |
| 实时主备 | TRUNCATE PARTITION 是 DDL,备库秒级应用,影响极小 |
| 归档 | 主库归档暴涨,备库重演日志有压力;提前检查 |
| 切换演练 | 清理期间不要做主备切换,等事务结束 |
文章
阅读量
获赞
