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。

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

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

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$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;

  2. 加载执行计划基线:

    BEGIN
    DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE(
    PLAN_HASH_VALUE => your_plan_hash_value,
    SQL_ID => 'your_sql_id'
    );
    END;
    /

  3. 检查基线:

    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. 创建任务:

    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;
    /

  2. 执行任务:

    BEGIN
    DBMS_SQLTUNE.EXECUTE_TUNING_TASK('my_tuning_task');
    END;
    /

  3. 查看结果:

    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运营商行业生产实践分享》。

相关推荐
口_天_光健1 小时前
两款轻量级数据库SQLite 和 TinyDB,简单!实用!
数据库·python·sqlite·非关系型数据库
notfindjob1 小时前
sqlite加密-QtCipherSqlitePlugin 下
数据库·算法·sqlite
凡人的AI工具箱1 小时前
每天40分玩转Django:Django部署
数据库·后端·python·算法·django
装不满的克莱因瓶1 小时前
【Redis经典面试题一】如何解决Redis和数据库一致性的问题?
数据库·redis·缓存·一致性·延迟双删·双写一致性
woshilys2 小时前
sql server msdb数据库备份恢复
数据库·sqlserver
play_big_knife2 小时前
鸿蒙项目云捐助第十六讲云捐助使用云数据库实现登录注册
数据库·华为云·harmonyos·鸿蒙·云开发·云数据库·鸿蒙开发
火鸟22 小时前
Java 初学者的第一个 SpringBoot3.4.0 登录系统
数据库·通用代码生成器·编程初学者·第一个系统·电音之王·springboot3.4.0·java初学者
总是学不会.3 小时前
【Mysql面试】MyISAM 与 InnoDB相关问题
数据库·mysql·面试
qq_2518364573 小时前
基于asp.net游乐园管理系统设计与实现
开发语言·前端·数据库·后端·asp.net
Navicat中国3 小时前
Navicat 17 功能简介 | SQL 美化
数据库·sql·mysql·dba·mariadb·navicat