一个SQL性能问题的优化探索

今天同事问我一个问题,看起来比较常规,但是仔细分析了一圈,发现实在是有些晕,我隐隐感觉这是一个bug,但是有感觉问题还有很多需要确认和理解的细节。

    同事说有个10gR2的数据库SQL语句有性能问题,DB
time有报警,但是还没有明确的思路,让我帮忙看看有什么好的建议。我也没有问是哪个SQL语句,根据IP登录到服务器端查看了下数据库的负载,发现负载已经有了较大的波动。这是事后补充的一张较为全面的DB
time监控图。

   然后根据负载的情况,定位了大体的时间范围,就大体是11点左右。

得到了一个SQL的DB time占比图,可以很清楚看到是一个SQL导致了性能消耗极大。

 SNAP_ID SQL_ID        EXECUTIONS_DELTA ELAPSED_TI PER_TOTAL
---------- ------------- ---------------- ---------- ----------
    108652 5dw94xr3c8hy8                4 4338s      92%
    108652 0p1ntk9bv0hrr             6582 20s        0%
    108652 2bfm7hrkwu0s7             1240 0s         0%
    108652 3qd7k5q9gsnmx              434 4s         0%
    108652 4c075dx0fqsjk             7530 1s         0%    马上定位到这个SQL语句,和同事确认,就是这个语句,看语句的执行情况,可以看到执行的效率很差,这个SQL语句大体是下面的样子。

SQL_FULLTEXT
----------------------------------------------------------------------------------------------------
SELECT ID,CN,CARD_NO,TO_CHAR(CHARGE_DATE,'yyyy-MM-dd HH24:mi:ss') AS CHARGE_DATE ,IP,POINT,PRESENT_P
OINT,SUCCESS,CARD_TYPE,PRODUCTNUM,SALEMETHOD,ITEM_ID,SERVICEID,ORDER_NO,TO_CHAR(EXPIREDATE,'yyyy-MM-
dd HH24:mi:ss') AS EXPIREDATE,REMARK FROM RECHARGE_LOG WHERE CARD_NO = TO_NUMBER(:1)

   看到这个地方,很明显是SQL的性能问题,而性能瓶颈似乎在于过滤条件,是不是隐式转换导致的全表扫描呢?

   查看执行计划,我发现说对了一半,确实是全表扫描,而另一半就很难说得清楚了。因为这个表的字段CARD_NO数据类型是number(20,0),对于隐式转换,其实对于varchar2->number 的方式是兼容的,而反过来number->varchar2就很容易出现问题,具体可以参考:

MySQL和Oracle中的隐式转换(r6笔记第45天)

语句的执行计划如下:

查看谓词信息可以清楚的看到是一个filter的过滤条件,难道这个表没有索引,或者索引有什么特别之处?

然后这是一个很常规的非唯一性索引。

CREATE INDEX "BILL"."IDX_RECHARGE_LOG_CARD_NO" ON "BILL"."RECHARGE_LOG" ("CARD_NO")

有索引却不用,这个实在有些奇怪,看到这里就开始有些疑惑了。

是不是绑定变量的值有一些问题。如果要查看这类信息有两种方式,一种是从缓存中查看V$SQL_BIND_CAPTURE,另外一种是从快照数据中查看dba_hist_sqlbind得到。

select instance_number, sql_id,name, datatype_string,
last_captured,value_string from dba_hist_sqlbind where
sql_id='5dw94xr3c8hy8' order by LAST_CAPTURED,POSITION;

输出的结果如下:

SQL_ID        NAME   DATATYPE_STRING LAST_CAPTURE VALUE_STRING
- ------------- ------ --------------- ------------ ------------------------------
5dw94xr3c8hy8 :1     VARCHAR2(128)   03-JAN-17    71307116310414511505
5dw94xr3c8hy8 :1     VARCHAR2(128)   03-JAN-17    71447219414105360326
5dw94xr3c8hy8 :1     VARCHAR2(128)   03-JAN-17    71447219414105360326

可以看到,这个绑定变量无可挑剔,是一个标准的字符类型。

而通过客户端模拟复现的时候,发现输出却大大不同,因为尝试模拟,却发现优化器会自动去走索引扫描而非全表扫描。

这样一个输出结果就很尴尬了。


尝试了多种方式复现,但是优化器都会乖乖的走索引。

如果这是一个很紧急的情况下,需要迅速做出响应,那么可以注意几个地方,一个是统计信息,一个是执行计划。统计信息和同事确认,他说之前收集过一次,而且查看统计信息的情况,竟然收集时间是今天,这一点值得进一步确认,因为没有手工触发过,另外一点就是执行计划,如果执行计划发现了变化,对于一个核心业务,影响范围较大的SQL语句,需要尽快修复,修改执行计划就是一种快速解决方式,当然对于这个问题,查看历史情况,这个语句的执行频率并不高,而且性能SQL也已经在执行中,我们先把问题确认清楚,再操作不迟。

除了需要确认统计信息收集时间外,还需要确认准确度,大体来看,字段也存在直方图,bucket数为254。

SQL>select owner,table_name,
               column_name,
               utl_raw.cast_to_number(low_value) low,
               utl_raw.cast_to_number(high_value) hight
    from dba_tab_col_statistics
         where table_name = 'RECHARGE_LOG'
          and column_name = 'CARD_NO'
OWNER    TABLE_NAME       COLUMN_NAM        LOW                          HIGHT
------------------------- ---------- ---------- ------------------------------
TEST     RECHARGE_LOG     CARD_NO             0           71657919645407470488

  可以通过SQLT得到这条语句执行计划的变化,可以赫然看到执行计划的天壤之别。

PLAN_HASH_VALUE AVG_ET_SECS
--------------- -----------
     1818596128        .013
      878630897    4247.848

两者在参数设置上看起来是一样的,唯一的就是扫描路径不同,一个索引,一个全表扫描。

走索引的部分:

DBMS_LOB.CLOSE(sql_txt);
h := SYS.SQLPROF_ATTR(
q'[BEGIN_OUTLINE_DATA]',
q'[IGNORE_OPTIM_EMBEDDED_HINTS]',
q'[OPTIMIZER_FEATURES_ENABLE('10.2.0.4')]',
q'[OPT_PARAM('optimizer_index_cost_adj' 30)]',
q'[OPT_PARAM('optimizer_index_caching' 90)]',
q'[ALL_ROWS]',
q'[OUTLINE_LEAF(@"SEL$1")]',
q'[INDEX_RS_ASC(@"SEL$1" "RECHARGE_LOG"@"SEL$1" ("RECHARGE_LOG"."CARD_NO"))]',
q'[END_OUTLINE_DATA]');

走全表扫描的部分:

DBMS_LOB.CLOSE(sql_txt);
h := SYS.SQLPROF_ATTR(
q'[BEGIN_OUTLINE_DATA]',
q'[IGNORE_OPTIM_EMBEDDED_HINTS]',
q'[OPTIMIZER_FEATURES_ENABLE('10.2.0.4')]',
q'[OPT_PARAM('optimizer_index_cost_adj' 30)]',
q'[OPT_PARAM('optimizer_index_caching' 90)]',
q'[ALL_ROWS]',
q'[OUTLINE_LEAF(@"SEL$1")]',
q'[FULL(@"SEL$1" "RECHARGE_LOG"@"SEL$1")]',
q'[END_OUTLINE_DATA]');

 所以上面的信息,可以看出影响这个执行计划,还和参数optimizer_index_cost_adj和optimizer_index_caching有关,但是根据经验值,似乎也在合理范围内。所以,看起来合理的数据,得到了一个不合理的结果。解释起来就很纠结了。

   预知后事如何,且听下回分解。

时间: 2024-09-23 20:27:55

一个SQL性能问题的优化探索的相关文章

一个SQL性能问题的优化探索(二)(r11笔记第38天)

继续前几天的一个案例一个SQL性能问题的优化探索(一)(r11笔记第33天) 如下的SQL语句存在索引字段CARD_NO,但是执行的时候却走了全表扫描,因为这是一个核心表,数据量很大,导致数据库负载很高. SQL_FULLTEXT ---------------------------------------------------------------------------------------------------- SELECT ID,CN,CARD_NO,TO_CHAR(CHAR

SQL性能优化:如何定位网络性能问题

一同事跟我反馈他遇到了一个SQL性能问题,他说全表只有69条记录,客户端执行耗费了两分多钟,这不科学呀.要我分析一下原因并解决.我按照类似表结构,构造了一个案例,测试截图如下所示   这 个表有13800KB(也就是13M多大小),因为该表将图片保存到数据库(Item_Photo字段为iamge类型),这个是历史原因,暂且不喷这种 的设计.看来这个SQL执行时间长的性能问题不在于IO和SQL本身执行计划是否有问题,而是在网络数据传时间上(服务器与客户端位于异地,两地专线带宽 6M,不过很多应用.

SQL性能优化之定位网络性能问题的方法(DEMO)_MsSql

最近项目组同事跟我说遇到一个SQL性能问题,他说全表只有69条记录,客户端执行耗费了两分多钟,很不科学.我帮了分析出了原因并得到解决.下面小编安装类似表结构,构造了一个案例,测试截图如下所示: 这个表有13800KB(也就是13M多大小),因为该表将图片保存到数据库(Item_Photo字段为iamge类型),这个是历史原因,暂且不喷这种的设计.看来这个SQL执行时间长的性能问题不在于IO和SQL本身执行计划是否有问题,而是在网络数据传时间上(服务器与客户端位于异地,两地专线带宽6M,不过很多应

SQL性能优化之定位网络性能问题的方法(DEMO)

最近项目组同事跟我说遇到一个SQL性能问题,他说全表只有69条记录,客户端执行耗费了两分多钟,很不科学.我帮了分析出了原因并得到解决.下面小编安装类似表结构,构造了一个案例,测试截图如下所示: 这个表有13800KB(也就是13M多大小),因为该表将图片保存到数据库(Item_Photo字段为iamge类型),这个是历史原因,暂且不喷这种的设计.看来这个SQL执行时间长的性能问题不在于IO和SQL本身执行计划是否有问题,而是在网络数据传时间上(服务器与客户端位于异地,两地专线带宽6M,不过很多应

ORACLE SQL性能优化系列 (一)

oracle|性能|优化 1. 选用适合的ORACLE优化器      ORACLE的优化器共有3种:    a.  RULE (基于规则)   b. COST (基于成本)  c. CHOOSE (选择性)      设置缺省的优化器,可以通过对init.ora文件中OPTIMIZER_MODE参数的各种声明,如RULE,COST,CHOOSE,ALL_ROWS,FIRST_ROWS . 你当然也在SQL句级或是会话(session)级对其进行覆盖.    为了使用基于成本的优化器(CBO,

ORACLE SQL性能优化系列 (十二)

oracle|性能|优化 39.       总是使用索引的第一个列 如果索引是建立在多个列上, 只有在它的第一个列(leading column)被where子句引用时,优化器才会选择使用该索引.   译者按: 这也是一条简单而重要的规则. 见以下实例.   SQL> create table multiindexusage ( inda number , indb number , descr varchar2(10)); Table created. SQL> create index

ORACLE SQL性能优化系列 (十四) 完结篇

oracle|性能|优化 46.       连接多个扫描 如果你对一个列和一组有限的值进行比较, 优化器可能执行多次扫描并对结果进行合并连接. 举例:     SELECT *     FROM LODGING     WHERE MANAGER IN ('BILL GATES','KEN MULLER');       优化器可能将它转换成以下形式     SELECT *     FROM LODGING     WHERE MANAGER = 'BILL GATES'     OR MA

ORACLE SQL性能优化系列 (四)

oracle|性能|优化   13.       计算记录条数      和一般的观点相反, count(*) 比count(1)稍快 , 当然如果可以通过索引检索,对索引列的计数仍旧是最快的. 例如 COUNT(EMPNO)   (译者按: 在CSDN论坛中,曾经对此有过相当热烈的讨论, 作者的观点并不十分准确,通过实际的测试,上述三种方法并没有显著的性能差别)   14.       用Where子句替换HAVING子句        避免使用HAVING子句, HAVING 只会在检索出所

ORACLE SQL性能优化系列 (六)

oracle|性能|优化 20.       用表连接替换EXISTS        通常来说 , 采用表连接的方式比EXISTS更有效率       SELECT ENAME       FROM EMP E       WHERE EXISTS (SELECT 'X'                       FROM DEPT                       WHERE DEPT_NO = E.DEPT_NO                       AND DEPT_CA