Oracle 三个生产案例问题分析

server/2024/12/19 13:55:57/

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/server/151453.html

相关文章

Linux下常用的网络编程函数详解

在网络编程中,我们经常需要处理 IP 地址和端口号等数据,这些数据需要在主机字节序(Host Byte Order)与网络字节序(Network Byte Order)之间进行转换。 什么是字节序? 字节序指的是多字节数据在…

青少年编程与数学 02-004 Go语言Web编程 08课题、使用Gin框架

青少年编程与数学 02-004 Go语言Web编程 08课题、使用Gin框架 一、Gin框架二、接收和处理请求三、应用示例 课题摘要:本文介绍了Gin框架的特点、如何接收和处理请求以及一个应用示例。Gin是一个高性能、轻量级的Go语言Web框架,以其快速、极简设计、强大的路由和中间…

Dockerfile文件编写

目录 Dockerfile文件编写 1.什么是Dockerfile 2. Dockerfile作用 3.dockerfile 的基本结构: 4.dockerfile指令: FROM 指定基础镜像,dockerfile构建镜像的第一个指令 LABEL 指定镜像维护人信息 ADD/COPY 复制本地文件/目录到镜像中 …

React 组件间的通信

React 父传子 实现&#xff1a; function Son(props){return <div>{ props.name }</div> } ​ ​ function App(){const name this is app namereturn (<div><Son name{name}/></div>) } 注意&#xff1a;这里可以传递任何类型&#xff0c;…

C语言 排序

时间:2024.12.18 一、冒泡排序(Bubble Sort) 原理 比较相邻的元素。如果第一个比第二个大,就交换它们两个。对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。针对所有的元素重复以上的步骤,除了最后一个。持续每次对越…

电脑文档损坏:原因剖析和修复方法

在使用电脑的过程中&#xff0c;许多用户可能会遇到文档突然提示损坏、无法打开的情况。这种情况的发生往往让人感到困惑&#xff0c;特别是当并未进行任何明显错误操作时。以下是一些常见的原因以及应对方法。 一、文档损坏的常见原因 1、非人为的异常操作&#xff1a; 在编…

【CC2530开发基础篇】继电器模块使用

一、前言 1.1 开发背景 本实验通过使用CC2530单片机控制继电器的吸合与断开&#xff0c;深入了解单片机GPIO的配置与应用。继电器作为一种常见的电气控制元件&#xff0c;广泛用于自动化系统中&#xff0c;用于控制大功率负载的开关操作。在本实验中&#xff0c;将通过GPIO口…

Objective-C实现字符串是否是有效的url地址算法(附完整源码)

Objective-C实现字符串是否是有效的url地址算法 要验证一个字符串是否是有效的 URL 地址,可以使用多种方法,包括正则表达式匹配、使用 NSURL 类进行解析等。下面,我将提供一个完整的 Objective-C 实现,包含以下内容: URLValidator 类:包含一个方法 isValidURL: 用于验证字…