表数据量较大、DML 操作频繁时,记录被频繁更新、删除,容易产生表数据页碎片,影响表的读写性能。传统碎片整理方式需要对表加锁,期间不允许并发查询与并发 DML,无法满足 7×24 小时业务连续性要求。
DM9 提供了在线碎片整理能力——通过系统函数 SP_REORGANIZE_INDEX_ONLINE 对指定索引进行在线空间整理,整理期间索引保持完全可用,支持并发执行查询、插入、更新、删除操作,从而解决传统方案导致的业务中断问题。
验证 DM9 在线碎片整理技术在以下五个维度的表现:
•功能正确性——索引碎片能否被有效回收,空间是否释放
•并发可用性——整理期间是否允许并发 DML 与查询,不锁表
•性能收益——整理前后全表扫描、索引扫描的效率改善情况
•索引可用性——整理期间索引状态是否保持 VALID,查询能否走索引
•权限控制——不同权限用户跨 Schema 操作是否被正确放行或拒绝
【步骤说明】在目标用户下创建测试用数据表及配套索引。
创建测试表空间(可选)
CREATE TABLESPACE test_ts DATAFILE 'test_ts.dbf' SIZE 1024 AUTOEXTEND ON NEXT 256 MAXSIZE 4096;
创建测试用户(可选,用于验证跨 Schema 权限)
CREATE USER test_user IDENTIFIED BY "Test123!" DEFAULT TABLESPACE test_ts;
GRANT RESOURCE TO test_user;
GRANT DBA TO test_user;
创建测试表及索引
CREATE TABLE test_user.test_fragmentation
(
id NUMBER(10) NOT NULL,
batch_no VARCHAR2(32),
content VARCHAR2(500),
status CHAR(1) DEFAULT '0',
create_time DATE DEFAULT SYSDATE,
CONSTRAINT pk_test_frag PRIMARY KEY(id)
);
CREATE INDEX test_user.idx_test_batch ON test_user.test_fragmentation(batch_no);
CREATE INDEX test_user.idx_test_status ON test_user.test_fragmentation(status);
CREATE INDEX test_user.idx_test_ctime ON test_user.test_fragmentation(create_time );
插入初始数据(建议 1000 万行以上以获得明显的碎片效果):
DECLARE
v_batch_size NUMBER := 998;
v_total_rows NUMBER := 9999999;
v_start_id NUMBER;
v_batches NUMBER;
BEGIN
v_start_id := 1;
v_batches := CEIL(v_total_rows / v_batch_size);
FOR i IN 1..v_batches LOOP
INSERT INTO test_user.test_fragmentation(id, batch_no, content, status, create_time)
SELECT v_start_id + (i - 1) * v_batch_size + LEVEL - 1,
'BATCH_' || MOD(v_start_id + (i - 1) * v_batch_size + LEVEL - 1, 297),
LPAD('CONTENT_', 230, 'X') || TO_CHAR(v_start_id + (i - 1) * v_batch_size + LEVEL - 1),
CASE WHEN MOD(LEVEL, 30) = 0 THEN '0'
WHEN MOD(LEVEL, 27) = 21 THEN '1'
ELSE '2' END,
SYSDATE - MOD(v_start_id + (i - 1) * v_batch_size + LEVEL - 1, 9125)
FROM DUAL
CONNECT BY LEVEL <= v_batch_size;
COMMIT;
IF MOD(i, 20) = 0 THEN
DBMS_OUTPUT.PUT_LINE('已完成 ' || i || '/' || v_batches || ' 批');
END IF;
END LOOP;
COMMIT;
DBMS_OUTPUT.PUT_LINE('生成完成');
END;
/
通过以下三轮操作模拟业务频繁增删改场景,使表空间产生大量碎片。
第一轮:大量 UPDATE(随机更新约 30,000 次)
DECLARE v_loop NUMBER := 35000; BEGIN FOR i IN 1..v_loop LOOP UPDATE test_fragmentation SET content = LPAD('UPDATED_', 420, 'X') || i WHERE id IN (SELECT id FROM test_fragmentation SAMPLE(0.08)); COMMIT; END LOOP; END; /
第二轮:大量 DELETE(每次删除约 60 行,共 8,000 次)
DECLARE v_loop NUMBER := 8000; BEGIN FOR i IN 1..v_loop LOOP DELETE FROM test_fragmentation WHERE id IN (SELECT id FROM test_fragmentation SAMPLE(0.03)) AND ROWNUM <= 50; COMMIT; END LOOP; END; /
第三轮:重新 INSERT 填充(加剧碎片化)
INSERT INTO test_fragmentation(id, batch_no, content, status, create_time) SELECT LEVEL + 38000000, 'BATCH_REINSERT_' || MOD(LEVEL, 240), LPAD('NEW_DATA_', 370, 'Y') || LEVEL, '0', SYSDATE FROM DUAL CONNECT BY LEVEL <= 1200000; COMMIT;
整理前记录各对象的空间占用,作为后续回收效果的对比基准。
SELECT SEGMENT_NAME,
BYTES / 1024 / 1024 AS size_mb,
BLOCKS,
EXTENTS
FROM DBA_SEGMENTS
WHERE OWNER = 'TEST_USER' AND SEGMENT_NAME IN ('TEST_FRAGMENTATION',
'PK_TEST_FRAG',
'IDX_TEST_BATCH',
'IDX_TEST_STATUS',
'IDX_TEST_CTIME')
ORDER BY SEGMENT_TYPE,
SEGMENT_NAME;
全表扫描基线
SELECT COUNT(*), MAX(create_time) FROM test_fragmentation WHERE status = '1';
索引范围扫描基线
SELECT COUNT(*), MIN(id), MAX(id) FROM test_fragmentation WHERE batch_no = 'BATCH_1' AND create_time >= SYSDATE - 545;
查看执行计划
用例编号: TC-ONLINE-001
测试目标: 验证对单个索引执行在线碎片整理的基本功能
前提条件: 表已产生明显碎片,聚簇因子偏高
执行 SQL:
SP_REORGANIZE_INDEX_ONLINE('TEST_USER','IDX_TEST_BATCH');
验证 SQL:
– 验证索引状态
SELECT INDEX_NAME, STATUS, LAST_ANALYZED FROM DBA_INDEXES WHERE OWNER = 'TEST_USER' AND INDEX_NAME = 'IDX_TEST_BATCH';
– 验证查询仍能走索引
EXPLAIN SELECT * FROM test_fragmentation WHERE batch_no = 'BATCH_1';
✓ 预期结果:函数执行成功,索引状态为 VALID,查询仍走索引。
用例编号: TC-ONLINE-002
测试目标 : 验证主键索引也能被在线整理,约束不受影响
前提条件: 同 4.1,主键索引已产生碎片
执行 SQL:
SP_REORGANIZE_INDEX_ONLINE(USER, 'PK_TEST_FRAG');
验证 SQL:
SELECT CONSTRAINT_NAME, CONSTRAINT_TYPE, STATUS FROM DBA_CONSTRAINTS WHERE OWNER = USER AND TABLE_NAME = 'TEST_FRAGMENTATION' AND CONSTRAINT_NAME = 'PK_TEST_FRAG';
✓ 预期结果:主键索引整理成功,约束状态仍为 ENABLED。
用例编号: TC-CONCUR-001
测试目标: 验证在线整理期间表可被正常并发读写,不锁表
测试方法: 双会话并行操作
优先级别: <run bold=“true” color=“C00000”>P0 - 核心功能</run>
会话 A —— 启动在线整理:
SELECT SYSDATE AS start_time FROM DUAL;
SP_REORGANIZE_INDEX_ONLINE(USER, 'IDX_TEST_STATUS');
SELECT SYSDATE AS end_time FROM DUAL;
会话 B —— 整理过程中并发执行:
SELECT COUNT(*), status, MAX(create_time) FROM test_fragmentation WHERE status = '2' GROUP BY status;
– 并发 INSERT
INSERT INTO test_fragmentation(id, batch_no, content, status, create_time) VALUES (99999999, 'CONCUR_TEST', 'Online reorg concurrent insert', '0', SYSDATE);
COMMIT;
==-- 并发 UPDATE ==
UPDATE test_fragmentation SET content = 'Concurrently updated during online reorg' WHERE id = 99999999;
COMMIT;
==-- 并发DELETE ==
DELETE DELETE FROM test_fragmentation WHERE id = 99999999;
COMMIT;
✓ 预期结果:会话A整理期间,会话B所有 DML 与查询均立即成功,无锁等待。
用例编号: TC-CONCUR-002
测试目标: 大表在线整理时,持续写入不中断
优先级别: <run bold=“true” color=“C00000”>P0 - 核心功能</run>
执行 SQL:
DECLARE v_base_id NUMBER; BEGIN FOR i IN 1..100 LOOP SELECT NVL(MAX(id), 0) + 1 INTO v_base_id FROM test_fragmentation; INSERT INTO test_fragmentation(id, batch_no, content, status, create_time) VALUES (v_base_id, 'STRESS_' || i, LPAD('STRESS_TEST_DATA', 480, 'Z'), CASE WHEN MOD(i, 2) = 0 THEN '1' ELSE '2' END, SYSDATE); COMMIT; UPDATE test_fragmentation SET status = CASE WHEN status = '1' THEN '2' ELSE '1' END WHERE id IN (SELECT id FROM test_fragmentation SAMPLE(0.005)); COMMIT; DBMS_LOCK.SLEEP(0.1); END LOOP; END; /
✓ 预期结果:并发无阻塞,所有 DML 正常提交。
用例编号: TC-PERM-001
测试目标: DBA / DB_OBJECT_ADMIN 可整理他人索引;普通用户不能
场景A — DBA 用户执行(预期成功):
SP_REORGANIZE_INDEX_ONLINE('TEST_USER', 'IDX_TEST_BATCH');
场景B — 普通用户跨 Schema(预期报错):
SP_REORGANIZE_INDEX_ONLINE('SYSDBA', 'IDX_FTB_V_CPTY_EXINFO');
✓ 预期结果:场景A 成功执行;场景B 报错。
对比整理前后的段空间占用,评估碎片回收效率。
SELECT SEGMENT_NAME,
BYTES / 1024 / 1024 AS size_mb,
BLOCKS,
EXTENTS
FROM DBA_SEGMENTS
WHERE OWNER = 'TEST_USER' AND SEGMENT_NAME IN ('TEST_FRAGMENTATION',
'PK_TEST_FRAG',
'IDX_TEST_BATCH',
'IDX_TEST_STATUS',
'IDX_TEST_CTIME')
ORDER BY SEGMENT_TYPE,
SEGMENT_NAME;
–执行在线整理,执行完索引水位线无变化
SP_REORGANIZE_INDEX_ONLINE(‘TEST_USER’,‘IDX_TEST_BATCH’);
SP_REORGANIZE_INDEX_ONLINE(‘TEST_USER’,‘IDX_TEST_STATUS’);
SP_REORGANIZE_INDEX_ONLINE(‘TEST_USER’,‘IDX_TEST_CTIME’);
SP_REORGANIZE_INDEX_ONLINE(‘TEST_USER’,‘INDEX33555530’);
–执行SP_REBUILD_INDEX,索引占用水位线已经下降
SP_REBUILD_INDEX('TEST_USER', '33555531');
SP_REBUILD_INDEX('TEST_USER', '33555533');
SP_REBUILD_INDEX('TEST_USER', '33555532');
SP_REBUILD_INDEX('TEST_USER', '33555529');
SP_REBUILD_INDEX('TEST_USER', '33555530');
全表扫描耗时对比
SELECT COUNT(*), MAX(create_time) FROM test_fragmentation WHERE status = '1';
索引扫描耗时对比
SELECT COUNT(*), MIN(id), MAX(id) FROM test_fragmentation WHERE batch_no = 'BATCH_1' AND create_time >= SYSDATE - 1095;
查看执行计划
EXPLAIN SELECT COUNT(*), MAX(create_time) FROM test_fragmentation WHERE status = '1';
• 备份先行:生产环境测试前务必做好数据备份,以防万一。
• 监控归档日志:在线整理会产生大量 UNDO / REDO,需确保归档空间充足。
• 观察锁等待:整理期间可通过 SELECT * FROM V$LOCK 实时监控是否有锁冲突。
• 分批执行:大表建议逐个索引分批整理,避免单次事务日志暴增。
• 及时收集统计信息:整理完毕后务必重新收集统计信息,确保优化器有准确的数据。
• 整理后建议执行:DBMS_STATS.GATHER_TABLE_STATS(USER, ‘TEST_FRAGMENTATION’, CASCADE => TRUE);
【#达梦数据库 #达梦同行者征文】
文章
阅读量
获赞
