1. 生产环境SQL性能异常排查实录
最近在生产环境遇到一个典型的SQL性能问题:同一条分页查询语句在测试环境执行仅需几十毫秒,但在生产环境却要耗费7秒多。作为DBA,这种"测试环境正常、生产环境异常"的问题往往最让人头疼。经过完整排查,最终发现问题出在统计信息收集方式的差异导致优化器选择了不同的执行计划。下面我将详细还原整个排查过程,并分享一些关键经验。
2. 环境与问题现象
2.1 基础环境对比
-
生产环境:
- Oracle 11.2.0.4
- 刚完成数据迁移并运行一段时间
- 使用
dbms_stats.gather_table_stats收集统计信息
-
测试环境:
- Oracle 11.2.0.1
- 使用
analyze语句收集统计信息
2.2 性能差异现象
执行相同的分页查询SQL:
- 测试环境:毫秒级响应
- 生产环境:约7秒响应时间
执行计划关键差异:
- 测试环境:走
RF_RECEIVEFILE表的索引扫描 - 生产环境:对
RF_RECEIVEFILE表进行全表扫描
提示:当发现生产与测试环境执行计划不一致时,首先要考虑的就是统计信息差异问题。
3. 详细排查过程
3.1 统计信息对比分析
首先检查两环境的表统计信息:
sql复制-- 生产环境统计信息
SELECT table_name, num_rows, blocks, last_analyzed
FROM dba_tables
WHERE owner='IOA5' AND table_name='RF_RECEIVEFILE';
-- 测试环境统计信息
SELECT table_name, num_rows, blocks, last_analyzed
FROM dba_tables
WHERE owner='IOA5' AND table_name='RF_RECEIVEFILE';
对比发现:
- 表的基本统计信息(行数、块数等)基本一致
- 但生产环境多了直方图统计信息
3.2 索引状态检查
确认索引是否有效:
sql复制-- 检查索引状态
SELECT index_name, status
FROM dba_indexes
WHERE owner='IOA5' AND table_name='RF_RECEIVEFILE';
结果显示:
- 生产与测试环境的索引状态均为VALID
- 排除索引失效导致的问题
3.3 索引统计信息对比
深入检查索引的统计信息:
sql复制SELECT index_name, blevel, leaf_blocks, distinct_keys, clustering_factor
FROM dba_indexes
WHERE owner='IOA5' AND table_name='RF_RECEIVEFILE';
发现:
- 索引的层级、叶子块数等关键指标基本一致
- 但生产环境的聚簇因子(CLUSTERING_FACTOR)略高
3.4 直方图信息分析
检查列的直方图信息:
sql复制-- 生产环境直方图
SELECT column_name, histogram, num_buckets, last_analyzed
FROM dba_tab_columns
WHERE owner='IOA5' AND table_name='RF_RECEIVEFILE';
-- 测试环境直方图
SELECT column_name, histogram, num_buckets, last_analyzed
FROM dba_tab_columns
WHERE owner='IOA5' AND table_name='RF_RECEIVEFILE';
关键发现:
- 生产环境:存在高度均衡(height-balanced)直方图
- 测试环境:无直方图信息(NONE)
4. 统计信息收集方式差异
4.1 生产环境收集方式
生产环境使用DBMS_STATS:
sql复制BEGIN
DBMS_STATS.gather_table_stats(
ownname => 'IOA5',
tabname => 'RF_RECEIVEFILE',
estimate_percent=> 100,
cascade => true
);
END;
/
特点:
- 默认会收集直方图信息
- 采用AUTO采样方式决定是否创建直方图
4.2 测试环境收集方式
测试环境使用ANALYZE:
sql复制ANALYZE TABLE RF_RECEIVEFILE COMPUTE STATISTICS;
特点:
- 不收集直方图信息
- 仅收集基本统计信息
5. 问题根因分析
5.1 直方图的影响机制
直方图会显著影响优化器的选择率(selectivity)估算:
- 无直方图时:优化器假设数据均匀分布
- 有直方图时:根据实际数据分布估算选择率
在本案例中:
- 生产环境的直方图导致优化器高估了索引扫描的成本
- 测试环境因无直方图,优化器做出了不同的成本估算
5.2 执行计划选择差异
具体到本案例的分页查询:
- 测试环境:优化器认为索引扫描成本更低
- 生产环境:直方图导致优化器认为全表扫描更优
注意:当SQL包含ORDER BY和ROWNUM限制时(典型分页场景),直方图的影响会被放大。
6. 解决方案与验证
6.1 临时解决方案
删除问题列的直方图:
sql复制BEGIN
DBMS_STATS.delete_column_stats(
ownname => 'IOA5',
tabname => 'RF_RECEIVEFILE',
colname => '问题列名',
cascade_parts => TRUE
);
END;
/
验证:
- 执行计划立即改为走索引
- 查询性能恢复到毫秒级
6.2 长期解决方案
调整统计信息收集策略:
sql复制BEGIN
DBMS_STATS.gather_table_stats(
ownname => 'IOA5',
tabname => 'RF_RECEIVEFILE',
estimate_percent=> 100,
method_opt => 'for all columns size 1', -- 不收集直方图
cascade => true
);
END;
/
或者针对特定列控制直方图收集:
sql复制BEGIN
DBMS_STATS.gather_table_stats(
ownname => 'IOA5',
tabname => 'RF_RECEIVEFILE',
estimate_percent=> 100,
method_opt => 'for columns 问题列名 size 1', -- 仅该列不收集
cascade => true
);
END;
/
7. SQL不走索引的常见情况总结
根据多年经验,我整理了Oracle不使用索引的常见场景:
-
统计信息问题
- 统计信息过时
- 直方图导致选择率估算偏差
- 索引统计信息不准确
-
索引状态问题
- 索引状态为INVALID
- 索引被标记为INVISIBLE
- 索引未被收集统计信息
-
SQL写法问题
- 对索引列使用函数(非函数索引)
- 隐式类型转换
- 使用
<>、!=、NOT IN等否定操作符 - LIKE操作以
%开头 - 对索引列进行运算
-
复合索引使用不当
- 未使用前导列
- 列顺序不符合最左前缀原则
-
数据特性问题
- 表数据量很小(优化器认为全表扫描更快)
- 索引列NULL值过多
- 索引聚簇因子过高
8. 性能排查实用技巧
8.1 获取真实执行统计
使用以下方法获取SQL的实际执行统计:
sql复制ALTER SESSION SET statistics_level=ALL;
-- 执行问题SQL
SELECT /*+ MONITOR */ * FROM ...;
-- 查看实际执行统计
SELECT * FROM TABLE(dbms_xplan.display_cursor(null,null,'allstats last'));
关键指标:
A-Rows:实际返回行数Starts:操作执行次数E-Rows:优化器估算的行数Buffers:逻辑读次数
8.2 执行计划绑定
对于关键SQL,可以使用SQL Profile固定执行计划:
sql复制-- 1. 捕获好的执行计划
DECLARE
l_plan VARCHAR2(100);
BEGIN
l_plan := DBMS_SPM.load_plans_from_cursor_cache(
sql_id => '好的SQL_ID',
plan_hash_value => 好的PLAN_HASH_VALUE
);
END;
/
-- 2. 绑定到问题SQL
DECLARE
l_plan VARCHAR2(100);
BEGIN
l_plan := DBMS_SPM.load_plans_from_cursor_cache(
sql_id => '问题SQL_ID',
plan_hash_value => 好的PLAN_HASH_VALUE
);
END;
/
8.3 索引使用建议
设计高效索引的几个原则:
- 为高频查询条件创建索引
- 注意复合索引的列顺序
- 定期维护索引统计信息
- 监控索引使用频率,及时清理无用索引
检查索引使用情况:
sql复制SELECT index_name, table_name, used
FROM v$object_usage
WHERE used='NO';
9. 经验总结与最佳实践
通过这次排查,我总结了以下几点重要经验:
-
统计信息收集策略:
- 生产与测试环境应保持一致的收集方式
- 对于关键表,建议使用DBMS_STATS并明确method_opt参数
- 定期验证统计信息的准确性
-
直方图使用原则:
- 只在数据分布严重倾斜的列上收集直方图
- 避免在分页查询的排序列上收集直方图
- 监控直方图对执行计划的影响
-
环境一致性检查:
- 上线前对比测试与生产环境的表统计信息
- 检查索引状态、参数设置等关键配置
- 使用SQLT等工具进行全面的执行计划比对
-
分页查询优化建议:
- 尽量使用基于主键或时间范围的分页
- 避免复杂的多列排序
- 考虑使用物化视图优化高频分页查询
这次案例再次证明,数据库性能问题往往隐藏在细节之中。作为DBA,我们需要建立系统化的排查思路,同时积累对各种"异常"现象的经验判断。统计信息作为优化器的"眼睛",其重要性怎么强调都不为过。