1. 索引碎片的前世今生
各位数据库管理员朋友们,今天咱们来聊聊达梦DM8数据库中那个让人又爱又恨的小东西——索引碎片。就像我们家里的衣柜,用久了衣服会乱七八糟一样,数据库索引用久了也会产生"碎片"。
索引碎片到底是什么呢?简单说,就是索引页中的数据排列不再紧凑有序,导致数据库引擎需要读取更多的页来获取相同数量的数据。想象一下,你要找一件衬衫,结果它被分散在衣柜的五个不同抽屉里,是不是很抓狂?
在达梦DM8中,索引碎片主要分为两种:
- 内部碎片:单个页中未使用的空间过多,就像抽屉里衣服没放满,浪费空间
- 外部碎片:逻辑上连续的页在物理存储上不连续,就像相邻抽屉被其他衣服隔开了
2. 索引碎片的危害与检测
碎片多了会有什么问题?最直接的影响就是查询变慢。我见过一个客户的系统,简单查询居然要5秒,重建索引后直接降到0.1秒,效果立竿见影。
在达梦DM8中,我们可以用以下SQL检测索引碎片情况:
-- 检查指定表的所有索引碎片情况
SELECT
TABLE_NAME,
INDEX_NAME,
INDEX_TYPE,
BLOCKS,
BLOCKS*8/1024 AS "索引大小(MB)",
LEAF_BLOCKS,
DISTINCT_KEYS,
CLUSTERING_FACTOR,
STATUS
FROM
USER_INDEXES
WHERE
TABLE_NAME = 'EMPLOYEE';
-- 更详细的碎片分析
SELECT
SEGMENT_NAME,
SEGMENT_TYPE,
BYTES/1024/1024 AS "大小(MB)",
BLOCKS,
EXTENTS,
ROUND(100*(BYTES - NVL(EMPTY_BYTES,0))/BYTES,2) AS "空间利用率(%)"
FROM
USER_SEGMENTS
WHERE
SEGMENT_NAME IN (SELECT INDEX_NAME FROM USER_INDEXES WHERE TABLE_NAME = 'EMPLOYEE');
注释说明:
BLOCKS显示索引占用的数据块数量LEAF_BLOCKS是索引叶子块数量,与碎片程度相关CLUSTERING_FACTOR反映数据有序程度,值接近表行数说明有序性差空间利用率低于70%通常意味着显著碎片
3. 重建索引 vs 整理碎片
达梦DM8提供了两种主要的碎片整理方法:重建索引(REBUILD)和整理碎片(COALESCE)。它们有什么区别?什么时候该用哪个?咱们来掰扯掰扯。
3.1 重建索引(REBUILD)
重建索引相当于把衣柜里的衣服全倒出来,重新叠好再放回去。这是最彻底的整理方式,语法如下:
-- 基本重建语法
ALTER INDEX idx_employee_name REBUILD;
-- 带参数的重建
ALTER INDEX idx_employee_name REBUILD
TABLESPACE users
ONLINE
COMPUTE STATISTICS
PARALLEL 4;
注释说明:
TABLESPACE指定重建后索引存放的表空间ONLINE选项允许在重建期间继续使用索引COMPUTE STATISTICS重建后自动收集统计信息PARALLEL使用并行度加速重建
重建索引的优点:
- 效果最好,能完全消除碎片
- 可以改变索引的存储参数
- 可以并行操作提高速度
缺点也很明显:
- 需要额外空间,相当于创建新索引再删除旧索引
- 大型索引重建可能耗时较长
- 如果不用ONLINE选项会阻塞DML操作
3.2 整理碎片(COALESCE)
整理碎片更像是把衣柜里松散的衣服往里推一推,让空间更紧凑。它只合并相邻的空闲空间,语法更简单:
-- 整理索引碎片
ALTER INDEX idx_employee_name COALESCE;
-- 整理表空间碎片(关联技术)
ALTER TABLESPACE users COALESCE;
整理碎片的优点:
- 操作快速,不需要额外空间
- 对系统影响小,基本不阻塞操作
- 可以频繁执行作为维护手段
但它的局限性也很明显:
- 只能合并相邻空闲空间,无法完全消除碎片
- 不能改变索引的存储结构
- 对严重碎片化的索引效果有限
4. 实战选择策略
知道了两种方法的区别,关键问题来了:什么时候该用哪种方法?根据我的经验,可以遵循以下策略:
4.1 选择重建索引的场景
- 碎片率超过30%:当检测发现索引空间利用率低于70%时
- 性能明显下降:关键查询比历史性能下降20%以上
- 定期维护窗口:如月度维护时对关键表索引重建
- 数据大批量变更后:如ETL过程导入大量数据后
- 需要修改存储参数时:如迁移表空间或调整并行度
-- 实战示例:月度维护脚本
BEGIN
FOR idx IN (SELECT index_name FROM user_indexes WHERE table_name IN ('EMPLOYEE','DEPARTMENT','SALARY'))
LOOP
EXECUTE IMMEDIATE 'ALTER INDEX ' || idx.index_name || ' REBUILD ONLINE COMPUTE STATISTICS';
DBMS_OUTPUT.PUT_LINE('已重建索引: ' || idx.index_name);
END LOOP;
END;
/
4.2 选择整理碎片的场景
- 轻度碎片(10-30%):作为日常维护手段
- 系统高峰时段:需要快速整理又不想影响业务
- 频繁DML的表:如订单表等经常增删的表
- 维护窗口有限时:没有足够时间进行完整重建
- 测试环境验证:先整理看效果再决定是否重建
-- 实战示例:每日碎片整理脚本
BEGIN
FOR idx IN (SELECT index_name FROM user_indexes WHERE table_name IN ('ORDER_DETAILS','CUSTOMERS'))
LOOP
-- 先检查空间利用率
EXECUTE IMMEDIATE 'ALTER INDEX ' || idx.index_name || ' COALESCE';
DBMS_OUTPUT.PUT_LINE('已整理索引: ' || idx.index_name);
END LOOP;
END;
/
5. 性能影响与注意事项
无论是重建还是整理,都会对系统产生一定影响,我们需要特别注意以下几点:
5.1 资源消耗
重建索引是资源密集型操作,特别是大型索引:
- CPU使用率会显著上升
- 磁盘I/O压力增大
- 可能需要大量临时表空间
建议:
- 在业务低峰期执行
- 使用PARALLEL参数控制资源使用
- 监控系统资源使用情况
5.2 锁与阻塞
不带ONLINE选项的重建会:
- 获取排他锁,阻塞所有DML操作
- 可能导致应用程序超时
建议:
- 关键业务表使用ONLINE重建
- 提前通知业务部门维护窗口
- 测试环境验证预估时间
5.3 统计信息
重建索引后:
- 原有统计信息会被清除
- 查询优化器可能暂时选择次优执行计划
建议:
- 使用COMPUTE STATISTICS选项
- 重建后考虑手动收集统计信息
- 对大表使用ESTIMATE STATISTICS减少开销
-- 重建后收集统计信息的推荐做法
ALTER INDEX idx_large_table REBUILD COMPUTE STATISTICS;
-- 对大表使用采样估算
BEGIN
DBMS_STATS.GATHER_INDEX_STATS(
ownname => 'SCOTT',
indname => 'IDX_LARGE_TABLE',
estimate_percent => 20,
degree => 4
);
END;
/
6. 自动化维护策略
对于生产环境,我建议建立自动化的索引维护策略:
- 定期检测:每周自动检测高碎片索引
- 分级处理:根据碎片程度选择重建或整理
- 智能调度:在维护窗口自动执行
- 效果验证:维护后验证空间节省和性能提升
- 异常警报:处理失败或资源超限时告警
-- 自动化维护表示例
CREATE TABLE index_maintenance_log (
log_id NUMBER GENERATED ALWAYS AS IDENTITY,
index_name VARCHAR2(30),
table_name VARCHAR2(30),
operation_type VARCHAR2(10), -- 'REBUILD' or 'COALESCE'
before_blocks NUMBER,
after_blocks NUMBER,
before_bytes NUMBER,
after_bytes NUMBER,
start_time TIMESTAMP,
end_time TIMESTAMP,
status VARCHAR2(10), -- 'SUCCESS' or 'FAILED'
error_message VARCHAR2(4000),
PRIMARY KEY (log_id)
);
-- 自动化维护存储过程示例
CREATE OR REPLACE PROCEDURE auto_index_maintenance AS
CURSOR frag_indexes IS
SELECT i.index_name, i.table_name,
s.bytes, s.blocks,
ROUND(100*(s.bytes - NVL(s.empty_bytes,0))/s.bytes,2) AS usage_pct
FROM user_indexes i
JOIN user_segments s ON i.index_name = s.segment_name
WHERE ROUND(100*(s.bytes - NVL(s.empty_bytes,0))/s.bytes,2) < 75
ORDER BY usage_pct ASC;
v_start_time TIMESTAMP;
v_end_time TIMESTAMP;
v_after_bytes NUMBER;
v_after_blocks NUMBER;
BEGIN
FOR idx IN frag_indexes LOOP
v_start_time := SYSTIMESTAMP;
-- 根据碎片程度选择操作类型
IF idx.usage_pct < 60 THEN
-- 严重碎片,重建索引
BEGIN
EXECUTE IMMEDIATE 'ALTER INDEX ' || idx.index_name || ' REBUILD ONLINE';
-- 获取整理后的大小
SELECT bytes, blocks INTO v_after_bytes, v_after_blocks
FROM user_segments
WHERE segment_name = idx.index_name;
-- 记录日志
INSERT INTO index_maintenance_log VALUES (
DEFAULT, idx.index_name, idx.table_name, 'REBUILD',
idx.blocks, v_after_blocks, idx.bytes, v_after_bytes,
v_start_time, SYSTIMESTAMP, 'SUCCESS', NULL
);
COMMIT;
EXCEPTION
WHEN OTHERS THEN
INSERT INTO index_maintenance_log VALUES (
DEFAULT, idx.index_name, idx.table_name, 'REBUILD',
idx.blocks, NULL, idx.bytes, NULL,
v_start_time, SYSTIMESTAMP, 'FAILED', SQLERRM
);
COMMIT;
END;
ELSE
-- 轻度碎片,整理即可
BEGIN
EXECUTE IMMEDIATE 'ALTER INDEX ' || idx.index_name || ' COALESCE';
-- 获取整理后的大小
SELECT bytes, blocks INTO v_after_bytes, v_after_blocks
FROM user_segments
WHERE segment_name = idx.index_name;
-- 记录日志
INSERT INTO index_maintenance_log VALUES (
DEFAULT, idx.index_name, idx.table_name, 'COALESCE',
idx.blocks, v_after_blocks, idx.bytes, v_after_bytes,
v_start_time, SYSTIMESTAMP, 'SUCCESS', NULL
);
COMMIT;
EXCEPTION
WHEN OTHERS THEN
INSERT INTO index_maintenance_log VALUES (
DEFAULT, idx.index_name, idx.table_name, 'COALESCE',
idx.blocks, NULL, idx.bytes, NULL,
v_start_time, SYSTIMESTAMP, 'FAILED', SQLERRM
);
COMMIT;
END;
END IF;
END LOOP;
END;
/
7. 总结与最佳实践
经过上面的详细讨论,我总结出达梦DM8索引碎片维护的几点最佳实践:
- 定期检测:建立定期(如每周)检测索引碎片的机制
- 分级处理:轻度碎片(>60%)整理,严重碎片(<60%)重建
- 资源规划:大型索引重建安排在业务低峰期,预留足够资源
- 在线操作:关键业务索引使用ONLINE选项重建
- 统计信息:重建后及时更新统计信息
- 效果验证:维护前后记录空间和性能变化,验证效果
- 自动化:建立自动化维护流程,减少人工干预
记住,索引维护不是越频繁越好,需要平衡维护成本和性能收益。对于特别活跃的表,可以考虑稍微容忍一定程度的碎片,减少维护频率。
最后提醒一点,达梦DM8后续版本可能会优化索引维护机制,记得关注版本更新说明,及时调整维护策略。
评论