首页 > 数据库 >通过索引提升SQL性能案例一则

通过索引提升SQL性能案例一则

时间:2023-06-19 12:05:49浏览次数:40  
标签:00 code log 案例 索引 SQL op


最近有个应用,前端调用后台的一个逻辑很慢,请开发提供了对应逻辑使用的SQL,进行脱敏,示例如下,

select t.AGENT as agent,
        nvl(sum(case
        when t.operation_type = 'A' then 1 else 0 end),0) as
        DflCount,
        nvl(sum(case
        when t.operation_type = 'B' then 1 else 0 end),0) as
        IfCount,
        nvl(sum(case
        when t.operation_type = 'C' then 1 else 0 end),0) as
        AecCount,
        nvl(sum(case
        when t.operation_type = 'D' then 1 else 0 end),0) as
        BsCount        
        from OP_LOG t        
        where 
        t.code  = 'AA'
        and t.ORI_CODE =  'ABC'
        and t.T_DATE BETWEEN to_date('20201209','yyyymmdd') and to_date('20201209','yyyymmdd')
        and IS_VALID = 1
        and t.operation_type in ('A','B','C','D')        
        group by t.agent         
        order by agent;

生产环境这张表的统计信息如下,3000多万的数据,

NUM_ROWS    BLOCKS  AVG_ROW_LEN
36181236    866883  118

这张表存在一个主键唯一索引,两个非唯一索引,

SQL> create index idx_op_log_01 on op_log(t_date);
Index created.

SQL> create index idx_op_log_03 on op_log(t_no, ori_code, t_code, t_date);
Index created.

SQL> alter table op_log add constraint pk_op_log_id primary key (id);
Table altered.

这三个索引对应的统计信息,

BLEVEL    LEAF_BLOCKS DISTINCT_KEYS   AVG_LEAF_BLOCKS_PER_KEY AVG_DATA_BLOCKS_PER_KEY CLUSTERING_FACTORY  NUM_ROWS
3         277636      152             1793                    16031                   2436821             30765015
3         270751      1429772         1                       23                      33314457            34989358
3         177428      38178216        1                       1                       37102260            38178216

生产环境SQL的执行计划如下所示,可以看到,选择的是idx_op_log_03的索引跳跃扫描,成本值是2585,buffer是115,

------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name                          | Starts | E-Rows | Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                               |      1 |        |  2585 (100)|          |      0 |00:00:00.01 |     115 |
|   1 |  SORT GROUP BY               |                               |      1 |      4 |  2585   (1)| 00:00:32 |      0 |00:00:00.01 |     115 |
|*  2 |   TABLE ACCESS BY INDEX ROWID| OP_LOG                        |      1 |      4 |  2583   (0)| 00:00:32 |      0 |00:00:00.01 |     115 |
|*  3 |    INDEX SKIP SCAN           | IDX_OP_LOG_03                 |      1 |     25 |  2560   (0)| 00:00:31 |      0 |00:00:00.01 |     115 |
------------------------------------------------------------------------------------------------------------------------------------------------
2 - filter((INTERNAL_FUNCTION("T"."OPERATION_TYPE") AND "IS_VALID"=1))
   3 - access("T"."ORI_CODE"='ABC' AND "T"."T_CODE"='A' AND "T"."T_DATE"=TO_DATE(' 2020-12-09
              00:00:00', 'syyyy-mm-dd hh24:mi:ss'))
       filter(("T"."T_DATE"=TO_DATE(' 2020-12-09 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND "T"."ORI_CODE"='ABC' AND
              "T"."T_CODE"='A'))

这张表是3000万的数据量,单表查询,一个主键索引,一个t_date单键值索引,一个复合索引(t_no,ori_code,t_code,t_date)。原始SQL的条件中包含了idx_op_log_03复合索引除前导列的另外三个字段,常规上可供Oracle优化器选择的执行计划,一个是全表扫描,一个就是idx_op_log_03的索引跳跃扫描,经过计算,Oracle认为索引跳跃扫描的成本值更低,这是他认为当前情况下最优的执行计划。

全表扫描,会多块读3000万数据所有大约86万个数据块,索引跳跃扫描,其实是构建遍历了所有前导列值的索引,伪代码如下,大约27万个数据块,而且还得单块读回表,实际读的数据块会更高,而且t_no的dinstinct,不是很少,因此这两种执行计划,算是半斤八两,

...
where 
t_no='1' and ori_code='A' and t_code='ABC' and t_date ...
and 
t_no='2' and ori_code='A' and t_code='ABC' and t_date ...
and
...
and
t_no='...' and ori_code='A' and t_code='ABC' and t_date ...

这个查询条件还是很简单的,如果根据当前的条件,创建新的索引,会提高效率么?

如果是DG,通常情况,我们可以开启备库的snapshot,模拟创建索引,测试他的效率,但是现在不让做,只可以在测试系统进行测试了。

测试系统这张表的数据量就几万,如果要贴近生产,增加数据量是一种形式,但是增加到几千万的量级,一个是需要消耗时间,另一个就是消耗空间。其实,Oracle是通过统计信息计算执行计划成本值的,因此我们可以借助改造统计信息,来达到让Oracle认为这是“生产”量级的计算,验证我们的调整。

首先,通过dbms_stats.set_table_stats设置表的统计信息值,

SQL> exec dbms_stats.set_table_stats('BISAL','OP_LOG',numrows=>36181263,numblks=>866883);      

PL/SQL procedure successfully completed.

可以看到,当前表的统计信息和生产基本一致了,

SQL> select table_name,num_rows,blocks,avg_row_len from user_tables
  2  where table_name='OP_LOG';

TABLE_NAME       NUM_ROWS   BLOCKS   AVG_ROW_LEN
---------------- ---------- -------- -----------
OP_LOG           36181263   866883   129

接着,通过dbms_stats.set_index_stats设置idx_op_log_03的索引统计信息,

SQL> EXEC dbms_stats.set_index_stats('BISAL','IDX_OP_LOG_03',indlevel=>3,numlblks=>270751,numdist=>1429772,avglblk=>1,avgdblk=>23,clstfct=>33314457,numrows=>34989358);

PL/SQL procedure successfully completed.

执行SQL,在测试环境下,其执行计划和生产的相同,都是idx_op_log_03的索引跳跃扫描。根据SQL条件,我们会创建两个索引,索引01是调整了原始索引03的字段顺序,将t_no置在尾部,索引02是删除t_no,增加了agent,

1. t_code, ori_code, t_date, t_no

2. t_code, ori_code, t_date, agent

SQL> create index idx_op_log_01 on op_log(t_code, ori_code, t_date, t_no);

Index created.

SQL> create index idx_op_log_02 on op_log(t_code, ori_code, t_date, agent);

Index created.

执行SQL,他采用的是idx_op_log_02,cost是7,

---------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name                    | Starts | E-Rows | Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers | Reads  |
---------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                         |      1 |        |     7 (100)|          |      0 |00:00:00.01 |       2 |      1 |
|   1 |  SORT GROUP BY               |                         |      1 |    111 |     7  (15)| 00:00:01 |      0 |00:00:00.01 |       2 |      1 |
|*  2 |   TABLE ACCESS BY INDEX ROWID| OP_LOG                  |      1 |   1545 |     6   (0)| 00:00:01 |      0 |00:00:00.01 |       2 |      1 |
|*  3 |    INDEX RANGE SCAN          | IDX_OP_LOG_02           |      1 |  10817 |     2   (0)| 00:00:01 |      0 |00:00:00.01 |       2 |      1 |
---------------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter((INTERNAL_FUNCTION("T"."OPERATION_TYPE") AND "IS_VALID"=1))
   3 - access("T"."T_CODE"='A' AND "T"."ORI_CODE"='ABC' AND "T"."T_DATE"=TO_DATE(' 2020-12-09 00:00:00',
              'syyyy-mm-dd hh24:mi:ss'))

为了对比,通过HINT使用idx_op_log_01索引,cost是8,01和02索引相差很小,

---------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name                    | Starts | E-Rows | Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers | Reads  |
---------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                         |      1 |        |     8 (100)|          |      0 |00:00:00.01 |       2 |      1 |
|   1 |  SORT GROUP BY               |                         |      1 |    111 |     8  (13)| 00:00:01 |      0 |00:00:00.01 |       2 |      1 |
|*  2 |   TABLE ACCESS BY INDEX ROWID| OP_LOG                  |      1 |   1545 |     7   (0)| 00:00:01 |      0 |00:00:00.01 |       2 |      1 |
|*  3 |    INDEX RANGE SCAN          | IDX_OP_LOG_01           |      1 |  10817 |     2   (0)| 00:00:01 |      0 |00:00:00.01 |       2 |      1 |
---------------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter((INTERNAL_FUNCTION("T".OPERATION_TYPE") AND "IS_VALID"=1))
   3 - access("T"."T_CODE"='A' AND "T"."ORI_CODE"='ABC' AND "T"."T_DATE"=TO_DATE(' 2020-12-09 00:00:00',
              'syyyy-mm-dd hh24:mi:ss'))

因此,单就这个场景,最佳选择是idx_op_log_02,但是保险起见,还是得结合程序中对该表的使用形式,以及数据结构,做下决策,例如选择索引01,除了其成本值,和01相差很小外,还可以删除原始索引03,降低索引个数。还可以考虑是否能将operation_type加入到索引中?

从这个案例,能体会到提升SQL语句的性能,看着好像就是创建索引,但是往细了抠,还是有很多知识点能让我们挖掘的,考虑因素越多,虽然脑壳疼,但是很可能让我们的应用运行起来更顺畅,少些烦恼,这可能就是”磨刀不误砍柴工“,还得持续学习。


标签:00,code,log,案例,索引,SQL,op
From: https://blog.51cto.com/u_13950417/6512300

相关文章

  • 小白学习MySQL - 闲聊聊
    众所周知,在DB-Engines的排行榜,一直占据前两位的数据库,就是Oracle和MySQL,Oracle作为关系型数据库的老大,在这个生态圈中,占据着绝对优势,MySQL作为一款面向“开源"的软件,虽然被Oracle曲线收购,相比之下,还是存在着“开源”的血统,而且有很多分支,无论是国外的MariaDB,还是国内的AliSQL,都在发......
  • 监听Activity生命周期方式及案例讲解
    本篇文章主要讲解如何快速实现Activity生命周期监听,以及其在官方lifecycle、第三方库Glide、PermissionX中的应用1.Activity生命周期监听Fragment实现Activity生命周期监听众所周知,Fragment中生命周期分发主要是依赖Activity,所以为了监听Activity的生命周期我们直接添加一个空的Fr......
  • 小白学习MySQL - MySQL会不会受到“高水位”的影响?
    前两天碰到了一个问题,MySQL的一张表,1220万数据量,需要删除1200万数据,仅存储20万数据,讨论了三种方案,1.00:00直接执行truncate,只存储新数据。2.将1220万中的20万采用CTAS存到一张中间表,再通过rename改这两张表的名称,实现替换操作。3.delete删除1200万数据。经过综合考虑,用的方案3,方......
  • mybatis-plus 数据库日志仅打印SQL
     一、项目背景:springboot、web组件、Mysql8、java8+; 二、开发环境打印SQL及执行结果日志,便于调试接口;mybatis-plus配置文件的configuration节点添加以下配置2、效果   三、生产环境仅打印SQL,避免打印过多的查询结果影响系统性能    1、pom中增加sl4j的......
  • php解决 mysql_connect(): The mysql extension is deprecated and will be removed i
    Themysqlextensionisdeprecatedandwillberemovedinthefuture:usemysq翻译:mysql_connect这个模块将在未来弃用,请你使用mysqli或者PDO来替代。解决方法:打开php.ini配置文件把display_errors=On改为display_errors=Off改完之后重启服务就可以了。  ......
  • python之mysql登录验证
    fromdotenvimportload_dotenvimportpymysqlimportosload_dotenv()connection=pymysql.connect(host=os.getenv("HOST"),database=os.getenv("DATABASE"),user=os.getenv("USERNAME"),password=os.getenv("PASSWORD......
  • SQL Server检索SQL和用户信息的需求
    Oracle中如果需要知道一条SQL是谁执行的,可以通过v$sql的parsing_schema_name字段得到登录的schema名称,相当于SQL和会话登录信息是有绑定的。但是最近有个SQLServer的需求,需要知道历史SQL的执行者。如下SQL,可以找到当前SQLServer跑过的SQL,但是没用户信息,SELECTp.refcounts,p.use......
  • SQL Server中怎么知道哪些表被访问过?
    同事问了个问题,我需要知道SQLServer中的某个库都有哪些表被访问过,这个怎么实现?SQLServer确实不太熟悉,如果是Oracle,我们可以通过AUDIT审计功能,实现表级、字段级这种粒度的监控,另外如果比较粗略的,还可以通过数据字典找到所有SELECT的语句,之所以说粗略,因为缓存是按照LRU算法存储的,如......
  • 超多绑定变量导致异常的一个案例
    最近生产上出现一个问题,某个应用单个SQL中绑定变量个数超过了65535个,导致数据库出现了异常终止的现象。通过trace,看到很多这样的信息(为了脱敏,此处引用MOS的例子),导致问题的SQL诸如这种,BEGINUPDATETESTSETC1=:1,C2=:2,C3=:3,......
  • android连接本地数据库sqlite,实现增删改查
    前言Android应用数据存储简单来说有这么几种:文件存储、SharedPreference存储、SQLite数据库存储、网络服务器存储、ContentProvider等。如果需要存储的数据量大的时候,那么使用文件存储会有很大的弊端,例如:你想修改其中很微小的项就要先读取整个文件的内容,修改后再全部保存,非常耗时。......