Oracle 三个生产案例问题分析
1. 案例一:表空间暴涨
1.1. 问题背景
一个平时不怎么增长的表空间连续告警,持续加了几百G的空间短时间被耗光。
1.2. 问题排查
1.2.1. 统计表空间的日增长量
通过统计表空间的日增长量可以看出有几天表空间的增长量是有 100 多 G 一天。
# 统计表空间的日增长量
select sample_time,
size_gb - lag(size_gb, 1) over(order by size_gb asc) as increment_gb
from (select to_char(to_date(rtime, 'mm/dd/yyyy hh24:mi:ss') - 1,
'yyyy-mm-dd') sample_time,
round(tablespace_usedsize * 8 / 1024 / 1024, 2) size_gb,
row_number() over(partition by to_char(to_date(rtime, 'mm/dd/yyyy hh24:mi:ss'), 'yyyy-mm-dd') order by snap_id) rn
from dba_hist_tbspc_space_usage t, v$tablespace s
where t.tablespace_id = s.TS#
and s.NAME = '&TABLESPACE_NAME'
and to_date(rtime, 'mm/dd/yyyy hh24:mi:ss') > sysdate - 10
order by t.snap_id)
where rn = 1
order by sample_time;
1.2.2. 分析表空间的增长趋势
按照时间的分布来查看表空间的增长趋势。
select s.NAME tablespace_name,
to_char(to_date(rtime, 'mm/dd/yyyy hh24:mi:ss'),
'yyyy-mm-dd hh24:mi') sample_time,
round(tablespace_size * 8 / 1024 / 1024, 2) tablespace_size_gb,
round(tablespace_usedsize * 8 / 1024 / 1024, 2) tablespace_usedsize_gb,
round(tablespace_usedsize * 100 / tablespace_size, 2) tbs_usage
from dba_hist_tbspc_space_usage t, v$tablespace s
where t.tablespace_id = s.TS#
and s.NAME = '&TABLESPACE_NAME'
and to_date(rtime, 'mm/dd/yyyy hh24:mi:ss') > sysdate - 7
order by t.snap_id;
1.2.3. 统计表空间中的对象大小
通过表空间中对象的大小分布,判断空间被大量使用的对象,可以看到主要是表的数据。
select owner,
segment_name,
segment_type,
sum(bytes) / 1024 / 1024 / 1024 size_db
from dba_segments
where tablespace_name = 'ITPUX01'
group by owner, segment_name, segment_type
order by 4 desc;
1.2.4. 分析对象的增长趋势
select to_char(s.end_interval_time,'yyyy-mm-dd hh24') sample_time,
round(sum(t.space_allocated_delta) / 1024 / 1024 / 1024, 2)
SPACE_ALLOCATED_GB
from dba_hist_seg_stat t, dba_hist_snapshot s
where t.snap_id = s.snap_id
and t.instance_number = s.instance_number
and t.obj# in ( SELECT object_id
FROM DBA_OBJECTS
where owner = '&OWNER'
and object_name = ‘&TABLE_NAME' )
and s.begin_interval_time > sysdate - 7
group by to_char(s.end_interval_time,'yyyy-mm-dd hh24')
order by to_char(s.end_interval_time,'yyyy-mm-dd hh24');
1.2.5. 查找SQL语句
-- 从ASH中,通过对象找SQL语句
select sql_id, sql_opname, sum(DELTA_WRITE_IO_BYTES)
from dba_hist_active_sess_history t
where to_char(t.sample_time, 'yyyy-mm-dd') = '2022-01-10'
and DELTA_WRITE_IO_BYTES is not null
and t.current_obj# in (SELECT object_id
FROM DBA_OBJECTS
where owner = 'hfedu'
and object_name = 'T')
group by sql_id, sql_opname;
1.2.6. 查询SQL的历史执行情况
SELECT TO_CHAR(END_INTERVAL_TIME, 'YYYY-MM-DD') SAMPLE_TIME,
T1.SQL_ID,
SUM(T1.EXECUTIONS_DELTA) EXECUTIONS,
SUM(T1.ROWS_PROCESSED_DELTA) AS ROWS_PROCESSED
FROM DBA_HIST_SQLSTAT T1, DBA_HIST_SNAPSHOT T2
WHERE T1.INSTANCE_NUMBER = T2.INSTANCE_NUMBER
AND T1.SNAP_ID = T2.SNAP_ID
AND T1.SQL_ID = 'abbd'
AND T1.EXECUTIONS_DELTA > 0
AND T2.BEGIN_INTERVAL_TIME > SYSDATE - 7
GROUP BY TO_CHAR(END_INTERVAL_TIME, 'YYYY-MM-DD'), T1.SQL_ID
ORDER BY SAMPLE_TIME ASC;
-- EXECUTIONS ≠ROWS_PROCESSED ?
-- Oracle在PL/SQL中提供的一种批量处理语法FORALL
-- forall i in stu_table.first.. stu_table.last
-- insert into t_student values stu_table(i);
1.3. 最后的确认
确认是因为插入的数据导入表空间暴涨,可以和前面的问题对应起来了,更有解释力。
--SQL执行的次数,如何与空间使用建立关系?
SQL> select avg_row_len from dba_tables where owner='&OWNER' and table_name='&TABLE_NAME';
AVG_ROW_LEN
-----------
268
SQL> select 268*734195000/1024/1024/1024 from dual;
268*734195000/1024/1024/1024
----------------------------
183.250997
1.4. 问题总结
在生产中经常遇到表空间满的问题,但是如果频繁的发生,这样也会影响业务的生产,这时候需要给客户或者甲方一个合理的解释,让客户知道不是运维的问题,用这种途径来给客户解释,是很容易得到客户的认可和理解。
2. 高并发插入下的索引争用
2.1. 问题背景
某存储过程转移历史数据出现积压,然后自动恢复,并且反复出现。
2.2. 问题排查
2.2.1. 通过ASH找问题出现的时间段
SELECT
SAMPLE_TIME,
SESSION_ID,
SESSION_SERIAL#,
SQL_ID,
EVENT,
WAIT_TIME,
P1,
P2,
P3
FROM
V$ACTIVE_SESSION_HISTORY
WHERE
SAMPLE_TIME BETWEEN TO_DATE('2024-12-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS')
AND TO_DATE('2024-12-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS')
ORDER BY
SAMPLE_TIME;
2.2.2. 统计某一时间段内等待事件的分布
SELECT event, count(*)
FROM ash_20211228 T
WHERE INST_ID = 2
and sample_time between
to_date('2021-12-28 10:30:39','yyyy-mm-dd hh24:mi:ss') and
to_date('2021-12-28 11:07:34', 'yyyy-mm-dd hh24:mi:ss')
and t.plsql_entry_object_id = 252262
group by event
order by count(*) desc;
从哪个等待事件开始入手?
- db file sequentital read:单块读,通过发生在通过索引访问数据
- buffer busy waits:同一时刻对同一内存块进行读写发生了争用
- enq: TX – index contention:插入时在索引块上发生了争用
2.2.3. 关于enq: TX- index contention
当事务修改索引中的数据时,而相关索引块没有足够的空间的时候,就会发生索引块的分裂。在分裂的过程中,前台进程需要等待分裂完毕才能继续操作。如果这个时候其他会话也要修改这个索引块的数据,那么将会出现索引块的竞争。
索引分裂分为三种,root block(根块)、branch block(枝块)、leaf block(叶块)。
索引分裂发生在不同的索引block上也就分了不同种类的分裂,root block上的分裂是root node split,branch block上的分裂是branch node split,leaf block上的分裂自然就是leaf node split,但leaf node split又根据不同的分裂行为分为90-10分裂和50-50分裂。常见的索引分裂绝大部分都发生在leaf block上。
(enq: TX- index contention)一般索引块的分裂持有资源和释放非常短,并不会对数据库造成严重的影响。但是对表操作并发量很大的情况下可能导致严重的竞争。
2.2.4. 通过等待时间查找SQL
SELECT sql_id, Sql_Opname,count(*)
FROM ash_20211228 T
WHERE INST_ID = 2
and sample_time between
to_date('2021-12-28 10:30:39','yyyy-mm-dd hh24:mi:ss') and
to_date('2021-12-28 11:07:34', 'yyyy-mm-dd hh24:mi:ss')
and t.plsql_entry_object_id = 252262
and event='enq: TX - index contention'
group by aql_id,Sql_OPname
order by count(*) desc;
2.2.5. 验证buffer busy waits发生的对象
SELECT o.OWNER, o.OBJECT_NAME,count(*)
FROM ash_20211228 T,dba_objects o
WHERE INST_ID = 2
and sample_time between
to_date('2021-12-28 10:30:39','yyyy-mm-dd hh24:mi:ss') and
to_date('2021-12-28 11:07:34', 'yyyy-mm-dd hh24:mi:ss')
and t.plsql_entry_object_id = 252262
and event='buffer busy waits'
and t.current_obj# = o.OBJECT_ID
group by o.OWNER, o.OBJECT_NAME
order by count(*) desc;
2.3. 索引分裂的处理手段
(1)将索引建成哈希分区索引(global hash partition index)
(2)将该数据表改造成哈希分区表,索引为本地分区索引
(3)将索引建成反向键索引,列的值被反向存储在索引块中,索引的插入值被分散到不同的叶块上
(4)增大pctfree
参考文档:Troubleshooting 'enq: TX - index contention' Waits (Doc ID 873243.1)
2.4. 问题总结
索引分裂分类
- 说到索引块分裂需要先要了解索引的数据块分为三种,root block(根块)、branch block
(枝块)、leaf block(叶块),理解为树形结构的根、枝、叶。索引分裂发生在不同的索引
block上也就分了不同种类的分裂,root block上的分裂是root node split,branch block上的分
裂是branch node split,leaf block上的分裂自然就是leaf node split,但leaf node split又根据
不同的分裂行为分为90-10分裂和50-50分裂。常见的索引分裂绝大部分都发生在leaf block上。
- root node split 索引的根节点分裂是不常发生的,当索引发生根分裂意味着索引的层高
(dba_indexes.blevel)也会增加。
- branch node split索引分支节点的分裂也都是leaf node发生分裂聚变导致的分支节点的分裂。
- leaf node split叶节点的分裂根据分裂行为90-10 leaf node split和50-50 leaf node split。
叶节点分裂是最常见的分裂方式,也是最常见的引发性能抖动的重要因素之一。
- 90-10 leaf node split:
- 50-50 leaf node split:
当索引leaf满分裂时,存在两种情况:
- 1.如果插入的键值是最大值,分裂按照90-10 split.
- 2.如果不是,按照50-50分裂。
3. 执行计划突变
3.1. 问题背景
前台业务出现数据库操作超时。
3.2. 问题分析
3.2.1. 通过v$sql查询sql的执行情况
前台业务出现数据库操作超时。
查询 v$sql 表,发现执行计划发生了改变。
问题点:
09:35:17的时候sql发生了重解析,走了另外一个执行计划
从sql的平均执行时间和buffer gets来看,基本可以确认新的执行计划效率变差
上面的问题如何解决,这时候有 sql_id,可以使用 SQL Profile 来绑定执行计划,例子:
-- 执行SQL语句以生成执行计划
SELECT /*+ dynamic_sampling(0) */ * FROM employees WHERE department_id = 10;
-- 获取执行计划
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR);
-- 将当前执行计划加载到SQL计划基线
BEGIN
DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE(
sql_id => 'your_sql_id',
plan_hash_value => your_plan_hash_value,
fixed => 'YES' -- 固定此计划
);
END;
/
3.2.2. SQL发生重新解析的原因
上面是如何解决问题,现在是如何分析问题:
sql 没有共享,可以去共享游标区出查下 sql 不共享的原因,每一个列都代表一个不共享的原因,Y 的是表示是因为这个原因(ROLL_INVALID_MISMATCH)导致的。--统计信息问题
在对表收集统计信息时,对现有sql游标失效的方式有3中,其中AUTO_INVALIDATE是默认值,即在收集完表的统计信息后,Oracle会根据内部的算法,在未来的几个小时内,逐渐将该表的游标失效,失效意味着sql将重新解析。
- TRUE: does not invalidate the dependent cursors -->不失效游标
- FALSE: invalidates the dependent cursors immediately -->立即失效游标(业务高峰期不能使用)
- AUTO_INVALIDATE (default): have Oracle decide when to invalidate dependent cursors -->Oracle根据内部自己的算法来滚动失效游标 (默认的,oracle 自己去判断)
参考文档:Troubleshooting: High Version Count Issues (Doc ID296377.1)
3.2.3. 重新解析后执行计划走错的原因?
对比统计信息
统计信息几乎没有任何变化,但是执行计划为什么会发生变化
3.2.4. 对比执行计划
差异在最外层的关联:
正确的为NESTED LOOPS
错误的为MERGE JOIN SEMI
3.2.5. 哪一步评估差异较大?
通过gather_plan_statistics提示,获取详细的执行统计信息
第6、7步的预估和实际Rows偏差较大
3.2.6. 通过10053分析评估出错的地方
找到基数2345K的算法
3.2.7. 通过10053分析评估出错的地方
很遗憾,无法跟踪到selectivity的具体算法……
哪种写法导致?
3.3. 测试
再把这个值带进去,这个值就变成 16 了。
这个库是 11203 版本,可能是优化器上面有不完美的地方。
3.4. 问题总结
Oracle 数据库中的执行计划突变是指某个查询的执行计划突然发生变化,导致查询性能下降。执行计划突变可能是由多种原因引起的,包括统计信息的差异、绑定变量的值变化、系统参数的调整等。解决执行计划突变的问题需要综合考虑多个方面。以下是一些常见的解决方法和步骤:
3.4.1. 收集和分析执行计划
首先,需要收集并分析当前的执行计划,以确定问题的根源。
3.4.1.1. 收集执行计划
可以使用 EXPLAIN PLAN
或 DBMS_XPLAN
等工具来收集查询的执行计划。
EXPLAIN PLAN FOR
SELECT * FROM your_table WHERE some_column = some_value;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
3.4.2. 检查统计信息
统计信息是优化器生成执行计划的重要依据。统计信息的不准确或过时可能导致执行计划突变。
3.4.2.1. 更新统计信息
使用 DBMS_STATS
包来更新表和索引的统计信息。
BEGIN
DBMS_STATS.GATHER_TABLE_STATS(
ownname => 'YOUR_SCHEMA',
tabname => 'YOUR_TABLE',
cascade => TRUE,
estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
method_opt => 'FOR ALL COLUMNS SIZE AUTO'
);
END;
/
3.4.3. 使用绑定变量
绑定变量的值变化可能导致优化器选择不同的执行计划。确保使用绑定变量时,值的分布是合理的。
3.4.3.1. 检查绑定变量
可以在 V$SQL
和 V$SQL_BIND_CAPTURE
视图中检查绑定变量的值。
SELECT sql_id, CHILD_NUMBER, BIND_NAME, VALUE_STRING
FROM V$SQL_BIND_CAPTURE
WHERE sql_id = 'your_sql_id';
3.4.4. 使用 SQL 指令
使用 SQL 指令(如 /*+ INDEX */
或 /*+ FULL */
)来强制优化器选择特定的执行计划。
SELECT /*+ INDEX(your_table your_index) */ * FROM your_table WHERE some_column = some_value;
3.4.5. 使用 SQL 计划管理(SPM)
SQL 计划管理(SQL Plan Management, SPM)可以帮助固定优化器的执行计划,防止突变。
3.4.5.1. 启用 SPM
- 生成基数反馈:
ALTER SYSTEM SET OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES = TRUE;
- 加载执行计划基线:
BEGIN
DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE(
PLAN_HASH_VALUE => your_plan_hash_value,
SQL_ID => 'your_sql_id'
);
END;
/
- 检查基线:
SELECT SQL_HANDLE, PLAN_NAME, ENABLED, ACCEPTED
FROM DBA_SQL_PLAN_BASELINES;
3.4.6. 调整优化器参数
有时调整优化器参数可以解决执行计划突变的问题。
3.4.6.1. 常见优化器参数
OPTIMIZER_MODE
:优化器的模式(如ALL_ROWS
或FIRST_ROWS
)。CURSOR_SHARING
:控制绑定变量的共享。OPTIMIZER_INDEX_CACHING
和OPTIMIZER_INDEX_COST_ADJ
:索引的性能调整。
3.4.7. 检查并解决索引问题
索引的缺失、损坏或不合适的索引可能导致执行计划突变。
3.4.7.1. 检查索引
SELECT INDEX_NAME, STATUS
FROM DBA_INDEXES
WHERE TABLE_NAME = 'YOUR_TABLE';
3.4.7.2. 重建索引
ALTER INDEX your_index REBUILD;
3.4.8. 使用 SQL 调优顾问(STA)
SQL 调优顾问(SQL Tuning Advisor)可以提供优化建议和执行计划分析。
3.4.8.1. 运行 SQL 调优顾问
- 创建任务:
DECLARE
tuning_task_id VARCHAR2(100);
BEGIN
tuning_task_id := DBMS_SQLTUNE.CREATE_TUNING_TASK(
sql_id => 'your_sql_id',
scope => DBMS_SQLTUNE.SCOPE_COMPREHENSIVE,
time_limit => 60,
task_name => 'my_tuning_task',
description => 'Tuning task for a specific SQL statement'
);
END;
/
- 执行任务:
BEGIN
DBMS_SQLTUNE.EXECUTE_TUNING_TASK('my_tuning_task');
END;
/
- 查看结果:
SELECT DBMS_SQLTUNE.REPORT_TUNING_TASK('my_tuning_task') AS report
FROM DUAL;
3.4.9. 跟踪和监控
使用 AWR 报告、ASH 报告等工具来跟踪和监控数据库的性能。
3.4.9.1. 生成 AWR 报告
@?/rdbms/admin/awrrpt.sql
3.4.9.2. 生成 ASH 报告
@?/rdbms/admin/ashrpt.sql
3.4.10. 总结
解决 Oracle 数据库中的执行计划突变问题需要综合考虑统计信息、绑定变量、优化器参数、索引等多个方面。通过上述方法,可以有效地识别和解决执行计划突变的问题,提升查询性能。
文章主要内容摘抄墨天轮恩墨大讲堂《Oracle运营商行业生产实践分享》。