Oracle 三个生产案例问题分析

devtools/2024/12/23 21:57:56/

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_gbfrom (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) rnfrom dba_hist_tbspc_space_usage t, v$tablespace swhere t.tablespace_id = s.TS#and s.NAME = '&TABLESPACE_NAME'and to_date(rtime, 'mm/dd/yyyy hh24:mi:ss') > sysdate - 10order by t.snap_id)where rn = 1order 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_usagefrom dba_hist_tbspc_space_usage t, v$tablespace swhere t.tablespace_id = s.TS#and s.NAME = '&TABLESPACE_NAME'and to_date(rtime, 'mm/dd/yyyy hh24:mi:ss') > sysdate - 7order by t.snap_id;

1.2.3. 统计表空间中的对象大小

通过表空间中对象的大小分布,判断空间被大量使用的对象,可以看到主要是表的数据。

select owner,segment_name,segment_type,sum(bytes) / 1024 / 1024 / 1024 size_dbfrom dba_segmentswhere tablespace_name = 'ITPUX01'group by owner, segment_name, segment_typeorder 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 twhere to_char(t.sample_time, 'yyyy-mm-dd') = '2022-01-10'and DELTA_WRITE_IO_BYTES is not nulland t.current_obj# in (SELECT object_idFROM DBA_OBJECTSwhere 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_PROCESSEDFROM DBA_HIST_SQLSTAT T1, DBA_HIST_SNAPSHOT T2WHERE T1.INSTANCE_NUMBER = T2.INSTANCE_NUMBERAND T1.SNAP_ID = T2.SNAP_IDAND T1.SQL_ID = 'abbd'AND T1.EXECUTIONS_DELTA > 0AND T2.BEGIN_INTERVAL_TIME > SYSDATE - 7GROUP BY TO_CHAR(END_INTERVAL_TIME, 'YYYY-MM-DD'), T1.SQL_IDORDER 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 TWHERE INST_ID = 2and sample_time betweento_date('2021-12-28 10:30:39','yyyy-mm-dd hh24:mi:ss') andto_date('2021-12-28 11:07:34', 'yyyy-mm-dd hh24:mi:ss')and t.plsql_entry_object_id = 252262group by eventorder 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 oWHERE INST_ID = 2and sample_time betweento_date('2021-12-28 10:30:39','yyyy-mm-dd hh24:mi:ss') andto_date('2021-12-28 11:07:34', 'yyyy-mm-dd hh24:mi:ss')and t.plsql_entry_object_id = 252262and event='buffer busy waits'and t.current_obj# = o.OBJECT_IDgroup by  o.OWNER, o.OBJECT_NAMEorder 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。

叶节点分裂是最常见的分裂方式,也是最常见的引发性能抖动的重要因素之一。

  1. 90-10 leaf node split:

  1. 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计划基线
BEGINDBMS_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 PLANDBMS_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 包来更新表和索引的统计信息。

BEGINDBMS_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$SQLV$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
  1. 生成基数反馈:
ALTER SYSTEM SET OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES = TRUE;
  1. 加载执行计划基线:
BEGINDBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE(PLAN_HASH_VALUE => your_plan_hash_value,SQL_ID => 'your_sql_id');
END;
/
  1. 检查基线:
SELECT SQL_HANDLE, PLAN_NAME, ENABLED, ACCEPTED
FROM DBA_SQL_PLAN_BASELINES;

3.4.6. 调整优化器参数

有时调整优化器参数可以解决执行计划突变的问题。

3.4.6.1. 常见优化器参数
  • OPTIMIZER_MODE:优化器的模式(如 ALL_ROWSFIRST_ROWS)。
  • CURSOR_SHARING:控制绑定变量的共享。
  • OPTIMIZER_INDEX_CACHINGOPTIMIZER_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 调优顾问
  1. 创建任务:
DECLAREtuning_task_id VARCHAR2(100);
BEGINtuning_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;
/
  1. 执行任务:
BEGINDBMS_SQLTUNE.EXECUTE_TUNING_TASK('my_tuning_task');
END;
/
  1. 查看结果:
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运营商行业生产实践分享》。


http://www.ppmy.cn/devtools/144806.html

相关文章

Flutter:key的作用原理(LocalKey ,GlobalKey)

第一段代码实现的内容:创建了3个块,随机3个颜色,每次点击按钮时,把第一个块删除 import dart:math; import package:flutter/material.dart; import package:flutter_one/demo.dart;void main() {runApp(const App()); }class App…

flask before_request 请求拦截器返回无值则放行,有值则拦截

环境 Python 3.11.5 Flask 2.2.2完整代码如下: from flask import Flask, make_response, Blueprintapp Flask(__name__) user_blue Blueprint(user, __name__, url_prefix/api/user) user_blue.before_request def befor…

12.7深度学习_经典神经网络_VGG

一、VGG神经网络 ​ VGG的亮点在于它通过堆叠多个卷积层,以小的卷积核和池化层的方式来增加网络深度,从而实现高精度的图像识别。这种方法可以有效地捕获图像中的高级特征,并通过不断拟合训练数据来提高识别准确率。 1. 小卷积作用 ​ DC …

堆排序【东北大学oj数据结构9-4】C++

堆排序是一种基于堆的数据结构的排序,是一种快速排序算法,可以在输入数组中实现排序处理(内存高效)。 堆排序可以实现如下: maxHeapify(A, i) l left(i) r right(i) // select the node which has the m…

Coding Caprice - Linked-List 1

203. 移除链表元素 class Solution { public:ListNode* removeElements(ListNode* head, int val) {ListNode* Head new ListNode();Head->next head;ListNode* out1 Head;while(Head!nullptr && Head->next!nullptr){if(Head->next->val val){ListNo…

whisper实时语音转文字

import whisperimport osdef check_file_exists(file_path):if not os.path.exists(file_path):raise FileNotFoundError(f"音频文件不存在: {file_path}")# 音频文件路径 audio_path r"D:\视频\temp_audio.wav"# 检查文件是否存在 check_file_exists(aud…

Crawl4AI:一个为大型语言模型(LLM)和AI应用设计的网页爬虫和数据提取工具实战

这里写目录标题 一、crawl4AI功能及简介1、简介2、特性 二、项目地址三、环境安装四、大模型申请五、代码示例1.生成markdown2.结构化数据 一、crawl4AI功能及简介 1、简介 Crawl4AI 是一个开源的网页爬虫和数据抓取工具,一个python项目,主要为大型语言…

OpenEuler Linux上怎么测试Nvidia显卡安装情况

当安装好显卡驱动后怎么样知道驱动程序安装好了,这里以T400 OpenEuler 正常情况下,我们只要看一下nvidia-smi 状态就可以确定他已经正常了 如图: 这里就已经确定是可以正常使用了,这里只是没有运行对应的程序,那接来下我们就写一个测试程序来测试一下:以下代码通过AI给出然后…