注册
达梦大表如何优雅地删数据
培训园地/ 文章详情 /

达梦大表如何优雅地删数据

潘栋民 2026/08/19 307 0 0

达梦大表如何优雅地删数据——不锁表、不拖REDO、不撑满 UNDO

适用版本:DM7 / DM8(单机、主备、读写分离、DMDSC 等架构)
适用场景:日志表 / 流水表 / 历史订单表,数据量千万~百亿级大表,需定期清理或一次性归档
核心目标:业务不中断、锁粒度最小、归档日志可控、回滚段不爆


一、为什么"直接 DELETE"是生产事故的开端

先看一个真实现场:某业务表 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(仅适合万级以内)           │  ← 小表/小范围才用
└──────────────────────────────────────────────┘

下面逐个展开,每个方案都给"适用条件 + 操作步骤 + 风险点"


三、方案一:分区表 TRUNCATE PARTITION(首选)

如果你的表已经按时间分区(RANGE 分区最常见),清理历史分区是达梦最优雅的操作。

3.1 为什么它最完美

维度 DELETE TRUNCATE PARTITION
速度 逐行删,慢 秒级(只改元数据)
日志量 大量 REDO 极少(DDL 级)
行锁→表级等待 短暂表级锁,不等数据量
UNDO 全量旧版本 几乎为零

3.2 操作步骤

-- 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;

3.3 注意事项

  • 锁表窗口极短(毫秒级元数据锁),正常业务 INSERT/SELECT 其他分区不受影响(本地索引时);
  • 如果分区上有全局索引,TRUNCATE PARTITION 会导致全局索引 UNUSABLE,需重建:
    ALTER INDEX IDX_TRADE_GLOBAL REBUILD;
  • 如果分区上只有本地索引(推荐),TRUNCATE PARTITION 不影响索引可用性。

设计建议:流水类大表从建表第一天就用 RANGE 分区(按月/按天),清理时永远走 TRUNCATE PARTITION,这是最省心的方案。


四、方案二:迁移保留数据 + 离线 DROP(准实时、最安全)

表没分区,但数据逻辑上可以"整段"归档(比如某月数据已经冷了

适用:表 10 亿行,要删 80%,且业务可接受该表停写业务

4.1 核心思想

"反向思维"——只把要留的 SELECT 出来建新表,DROP 旧表 RENAME 新表。

4.2 操作步骤(无分区表变通版)

-- 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; (确认无误后)

4.3 优劣对比

维度 评价
速度 比 DELETE 快 5~10 倍(只写一次有效数据)
风险 需要停写窗口,索引重建
日志 新建表有日志,但 DROP 旧表可快速清理
适用 月结/夜跑/可维护窗口场景
停写 必须,适合大版本维护窗口

五、方案三:循环小批量 DELETE(通用兜底方案)

不能停业务、表没分区、数据量千万~亿级——这是最常用方案。

5.1 核心原则

  • 每次 DELETE ≤ 5000 行(视 UNDO 大小调);
  • 显式 COMMIT,不让事务跨批;
  • 索引条件(走索引,不扫全表);
  • 不锁业务热点行(按时间范围切批)。

5.2 标准存储过程模板(可直接用)

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);

5.3 关键参数怎么定

参数 建议值 依据
单批行数 1万~5万 观察 UNDO 表空间使用率 < 40%
批次间隔 1~3 秒 给 REDO 刷盘和复制/归档留余量
并发 单会话串行 多会话 DELETE 同一表易互相阻塞
时间条件 有索引列 create_time 上必须有索引

5.4 如何验证"不锁业务"

-- 另开一个会话,模拟业务写入 INSERT INTO TRADE_LOG(...) VALUES(...); -- 应秒级成功 -- 查看锁等待 SELECT * FROM V$LOCK WHERE BLOCKED=1;

✅ 只要 DELETE 条件是 create_time < 冷数据边界,且边界不在业务当前写入区间,行锁不会撞上新数据。


六、大表操作需要事项清单

⚠️ 注意 1:DELETE 大表不提交,UNDO 必爆

百万行以上 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');

⚠️ 注意 2:归档日志频繁切换,导致归档保留周期较短

大 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'); -- 删除指定时间前归档

⚠️ 注意 3:DELETE 后表空间不回收

DELETE 只删数据行,数据块仍标记为空闲但不还给 OS,高水位线不降。

-- 查看表高水位 SELECT TABLE_NAME, BLOCKS, EMPTY_BLOCKS FROM USER_TABLES;

⚠️ 注意 4:批量 DELETE 时不适合开启并行

行锁、UNDO 段、REDO 缓冲、事务槽 等资源竞争,不适合并行。

-- 这是错的(并行 DML 对 DELETE 不友好) DELETE /*+ PARALLEL(8) */ FROM BIG_TABLE WHERE ...;

大表并行 DELETE 容易产生锁等待和日志争用。小批串行 + 索引条件 更稳。

⚠️ 注意 5:主备集群下大 DELETE 拖垮备库

产生大量归档,备库重演压力较大

-- 清理前检查主备延迟 SELECT * FROM V$RECOVERY_STATUS; -- 主库看备库重做状态 SELECT * FROM V$ARCH_SEND_INFO; -- 归档发送进度 -- 小批量 + 分批提交可控制延迟 -- 清理期间不要做主备切换

七、决策树:你该选哪个方案

表是分区表吗?
 ├─ 是 → 按时间条件恰好是分区边界?
 │       ├─ 是 → TRUNCATE PARTITION ✅(首选)
 │       └─ 否 → 分区内小批量 DELETE(按分区 + ROWNUM 限行)
 └─ 否 → 有维护窗口吗?
         ├─ 有 → CTAS 重建 / RENAME 切换 ✅(最快)
         └─ 无 → 循环小批量 DELETE ✅(通用兜底)

八、给开发团队的最佳实践建议

以下内容可直接贴到《数据库开发规范》中。

  1. 流水/日志类表从 Day 1 就用 RANGE 分区(按月),清理永远走 TRUNCATE PARTITION
  2. 禁止生产执行无 LIMIT / 无时间条件的 DELETE
  3. 批量清理脚本必须带 ROWNUM 分批 + 每批 COMMIT,且单批 ≤ 5 万行;
  4. 清理窗口期提前检查:UNDO 空间、归档目录剩余空间、主备延迟(主备集群);
  5. 主备/读写分离环境:大 DELETE 前确认备库重演速度,必要时暂停应用写备机窗口;
  6. 清理完成后验证数据量和高水位
    SELECT COUNT(*) FROM TRADE_LOG; SELECT TABLE_NAME, BLOCKS, EMPTY_BLOCKS FROM USER_TABLES WHERE TABLE_NAME='TRADE_LOG';

九、一句话总结

能 TRUNCATE 就不 DELETE,能分批就不一把梭,能停写就重建——记住这三句,线上删数据基本不会出事故。


十、附录:主备集群(DataWatch)额外注意

项目 建议
主备延迟 大事务 DELETE 会导致备库重做堆积,延迟飙升;小批量 + 分批提交可控制延迟
实时主备 TRUNCATE PARTITION 是 DDL,备库秒级应用,影响极小
归档 主库归档暴涨,备库重演日志有压力;提前检查
切换演练 清理期间不要做主备切换,等事务结束

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服