生产环境sql语句调优实战第八篇

简介: 生产环境中的sql语句执行时间是很关键的性能指标,如果某个sql语句执行几个小时,优化以后几分钟,几十秒的话。会有很大的成就感,同时如果某个sql语句执行10秒,能够优化到1秒,感觉提升的幅度不是很大,但是如果这条语句执行极为频繁的话,那这种调优还是更有成就感的。
生产环境中的sql语句执行时间是很关键的性能指标,如果某个sql语句执行几个小时,优化以后几分钟,几十秒的话。会有很大的成就感,同时如果某个sql语句执行10秒,能够优化到1秒,感觉提升的幅度不是很大,但是如果这条语句执行极为频繁的话,那这种调优还是更有成就感的。
执行时间是sql调优的一把标尺,但是同时也需要考虑到系统资源的平衡。
今天在系统中发现一条sql语句执行时间很长。平均一个查询要执行一个半小时左右,而且系统的资源消耗极大。
需要说明的 service_details 是数据量过亿的表。 ch_di st ribute 是千万级的表,subscriber是百万级的表。
payment是千万级的表,paychannel是百万级的表。
查看执行计划,倒看不出有明显的异常,这也就是执行计划的一个误区了,我们不能总是参考执行计划来进行调优,很多时候发现执行计划几乎是完美的,但是执行效率却很长。

sql语句如下所示。

SELECT cd.target_pcn, se.agreement_no, s.subscriber_no, s.prim_resource_val
  FROM ch_distribute CD, service_details SE, subscriber S
 WHERE cd.target_pcn IN
       (SELECT 
         cp.pym_channel_no
          FROM paychannel cp, payment pym
         WHERE cp.pym_channel_no IN
               (SELECT cd.target_pcn
                  FROM ch_distribute cd
                 WHERE (cd.agreement_no, cd.soc, cd.soc_seq_no) IN
                       (SELECT sg.agreement_no, sg.soc, sg.soc_seq_no
                          FROM service_details sg
                         WHERE sg.soc_status = 'A'
                           AND sg.agreement_no IN
                               (SELECT 
                                 sg.agreement_no
                                  FROM service_details sg, subscriber s
                                 WHERE s.subscriber_no = sg.agreement_no
                                   AND sg.soc = 50412
                                   AND sg.soc_status = 'A'
                                   AND sg.soc_sts_rsn_cd =
                                       (SELECT param_values
                                          FROM small_table
                                         WHERE param_name =
                                               'XXXXXXXX1'
                                           AND job_name = 'XXXXXX')   --bottleneck 
                                   AND s.subscriber_type IN
                                       (SELECT param_values
                                          FROM small_table
                                         WHERE param_name = 'XXXXXXXX2'
                                           AND job_name = 'XXXXXX')
                                           ))
                   AND expiration_date IS NULL)
           AND cp.ban = pym.account_id
           AND (pym.transaction_id >
               (SELECT param_values
                   FROM small_table
                  WHERE param_name = 'XXXXXXXX3'
                    AND job_name = 'XXXXXX') AND
               pym.transaction_id    AND se.agreement_no = cd.agreement_no
   AND s.subscriber_no = se.agreement_no
 GROUP BY cd.target_pcn,
          se.agreement_no,
          s.subscriber_no,
          s.prim_resource_val
          

猛一看这个查询语句还是挺臃肿的,可以明显的看到反复引用了大表service_details,chg_distribute.
我先把这个问题发给一个性能调优的哥们,他在不改动sql语句的前提下,加了几个Hint,执行时间就从1个半小时降低到4分钟左右,猛一看这是一个极大的提升,看似不用修改sql语句了。
我看了下他建议的hint,从执行时间来说,是很大的提升,但是从系统的资源消耗来看,还存在一定的隐患,建议的Hint如下:
SELECT /*+parallel(pym,4) full(pym) use_hash(pym)*/
         cp.pym_channel_no

SELECT /*+PARALLEL(S,4) full(S) FULL(SG) PARALLEL(SG,4) USE_HASH(S,SG)*/
                                 sg.agreement_no
                                  FROM service_details sg, subscriber s

两个Hint本身也没有什么问题,对于大表的关联用hash_join效率比nested loop要高很多。加上并行,如果查询执行不够频繁,涉及的表不多,确实是很好的选择。
我个人的观点还是从语句本身入手,先来看看有什么可以从结构中的改进,先在头脑中有一个基本的思路,然后主要查找数据的性能瓶颈到底在哪,因为根据在备份库上的测试,这个查询返回的数据条数在几千条左右,从上亿条,上千万的数据中排查出几千条肯定是有一些关键的过滤条件。
使用并行固然好,如果在不使用并行的条件下,高效的使用索引是更好的选择。如果实在条件所限,对个别做表全表扫描速度也是很快的。
在分析了数据的统计信息,索引情况之后,在备份库中进行了简单的数据筛查。
首先定位了性能瓶颈,是如下的这个查询条件。通过如下的条件能够过滤掉99%以上的数据,剩下的数据和其它大表关联,都是可以使用到索引的,速度就会快很多。
select *from agreement_no from service_details  sg
        where 
      sg.soc = 50412 and sg.soc_status = 'A' 

有了这个思路,修改起来就轻松多了.
有了主要的改进,其他的改进就可以锦上添花了。
还有两个需要修改的部分。
一个是简化sql语句的表关联,可以看到很多的表出现了多次,这对查询本身来说也不是必须的,个人认为这个sql语句是在开发人员边开发,变修改导致了查询语句嵌套了很多重复的关联。
一个是关于子查询的优化。有几个子查询会关联到一个小表,对小表中的数据进行反复关联。这对子查询而言,执行频率是极高的。
SELECT param_values
                                          FROM small_table
                                         WHERE param_name =
                                               'XXXXXXXX1'
                                           AND job_name = 'XXXXXX'
对于这种特别的子查询,可以考虑使用with语句来替代。改进后的语句如下,这样看就清晰多了。
with ssrc as (SELECT param_values
                   FROM small_table
                  WHERE param_name = 'XXXXXXXX1'
                    AND job_name = 'XXXXXX') ,
       sub_type as (SELECT param_values
                   FROM small_table
                  WHERE param_name = 'XXXXXXXX2'
                    AND job_name = 'XXXXXX'),
      hr_pay_trx as (SELECT param_values
                   FROM small_table
                  WHERE param_name = 'XXXXXXXX3'
                    AND job_name = 'XXXXXX')     
SELECT
           cp.pym_channel_no,sg.agreement_no, s.subscriber_no, s.prim_resource_val
           FROM service_details sg, subscriber s,chg_distribute chg,paychannel cp,sub_type,ssrc
          WHERE s.subscriber_no = sg.agreement_no
            AND sg.soc = 50412
            AND sg.soc_status = 'A'
            AND sg.soc_sts_rsn_cd =
                ssrc.param_values --bottleneck 
            AND s.subscriber_type =sub_type.param_values
            and sg.agreement_no=chg.agreement_no
            and sg.soc=chg.soc
            and sg.soc_seq_no=chg.soc_seq_no
            and chg.expiration_date is null
            and cp.pym_channel_no=chg.target_pcn
            and exists(
            select 1 from ar1_payment pym,hr_pay_trx            
            where cp.ban = pym.account_id
           AND (pym.transaction_id > hr_pay_trx.param_values
                AND
               pym.transaction_id
可以看到from后面跟了好几个大表,但是性能瓶颈在service_details上所以为了保险起见,我们可以使用hint来指定表的访问顺序。先过滤到99%以上的数据,剩下的就可以自然的走索引扫描了。
添加的Hint如下,对于表service_details,因为没法使用到索引,所以就对这一个表进行全表扫描,走个并行。
SELECT /*+leading(ssrc,sub_type,sg,s,chg,cp) parallel(sg 4) full(sg)*/
           cp.pym_channel_no,sg.agreement_no, s.subscriber_no, s.prim_resource_val

最后在备份库中测试,效果果然很明显,备份库中速度从优化后的4分钟降低到2分钟。
在生产环境中执行,速度更快,稳定在40秒左右。

目录
相关文章
|
6月前
|
SQL 运维 监控
SQL查询太慢?实战讲解YashanDB SQL调优思路
本文是Meetup第十期“调优实战专场”的第二篇技术文章,上一篇《高效查询秘诀,解码YashanDB优化器分组查询优化手段》中,我们揭秘了YashanDB分组查询优化秘诀,本文将通过一个案例,助你快速上手YashanDB慢日志功能,精准定位“慢SQL”后进行优化。
|
5月前
|
SQL 关系型数据库 MySQL
大数据新视界--大数据大厂之MySQL数据库课程设计:MySQL 数据库 SQL 语句调优方法详解(2-1)
本文深入介绍 MySQL 数据库 SQL 语句调优方法。涵盖分析查询执行计划,如使用 EXPLAIN 命令及理解关键指标;优化查询语句结构,包括避免子查询、减少函数使用、合理用索引列及避免 “OR”。还介绍了索引类型知识,如 B 树索引、哈希索引等。结合与 MySQL 数据库课程设计相关文章,强调 SQL 语句调优重要性。为提升数据库性能提供实用方法,适合数据库管理员和开发人员。
|
5月前
|
关系型数据库 MySQL 大数据
大数据新视界--大数据大厂之MySQL 数据库课程设计:MySQL 数据库 SQL 语句调优的进阶策略与实际案例(2-2)
本文延续前篇,深入探讨 MySQL 数据库 SQL 语句调优进阶策略。包括优化索引使用,介绍多种索引类型及避免索引失效等;调整数据库参数,如缓冲池、连接数和日志参数;还有分区表、垂直拆分等其他优化方法。通过实际案例分析展示调优效果。回顾与数据库课程设计相关文章,强调全面认识 MySQL 数据库重要性。为读者提供综合调优指导,确保数据库高效运行。
|
8月前
|
SQL Java 数据库连接
如何在 Java 代码中使用 JSqlParser 解析复杂的 SQL 语句?
大家好,我是 V 哥。JSqlParser 是一个用于解析 SQL 语句的 Java 库,可将 SQL 解析为 Java 对象树,支持多种 SQL 类型(如 `SELECT`、`INSERT` 等)。它适用于 SQL 分析、修改、生成和验证等场景。通过 Maven 或 Gradle 安装后,可以方便地在 Java 代码中使用。
2564 11
|
10月前
|
SQL 数据库 UED
SQL性能提升秘籍:5步优化法与10个实战案例
在数据库管理和应用开发中,SQL查询的性能优化至关重要。高效的SQL查询不仅可以提高应用的响应速度,还能降低服务器负载,提升用户体验。本文将分享SQL优化的五大步骤和十个实战案例,帮助构建高效、稳定的数据库应用。
824 3
|
存储 SQL 关系型数据库
【MySQL调优】如何进行MySQL调优?从参数、数据建模、索引、SQL语句等方向,三万字详细解读MySQL的性能优化方案(2024版)
MySQL调优主要分为三个步骤:监控报警、排查慢SQL、MySQL调优。 排查慢SQL:开启慢查询日志 、找出最慢的几条SQL、分析查询计划 。 MySQL调优: 基础优化:缓存优化、硬件优化、参数优化、定期清理垃圾、使用合适的存储引擎、读写分离、分库分表; 表设计优化:数据类型优化、冷热数据分表等。 索引优化:考虑索引失效的11个场景、遵循索引设计原则、连接查询优化、排序优化、深分页查询优化、覆盖索引、索引下推、用普通索引等。 SQL优化。
1449 15
【MySQL调优】如何进行MySQL调优?从参数、数据建模、索引、SQL语句等方向,三万字详细解读MySQL的性能优化方案(2024版)
|
10月前
|
SQL 缓存 监控
SQL性能提升指南:五大优化策略与十个实战案例
在数据库性能优化的世界里,SQL优化是提升查询效率的关键。一个高效的SQL查询可以显著减少数据库的负载,提高应用响应速度,甚至影响整个系统的稳定性和扩展性。本文将介绍SQL优化的五大步骤,并结合十个实战案例,为你提供一份详尽的性能提升指南。
866 0
|
11月前
|
SQL Oracle 关系型数据库
Oracle SQL:了解执行计划和性能调优
Oracle SQL:了解执行计划和性能调优
239 1
|
11月前
|
SQL 关系型数据库 MySQL
sql注入原理与实战(三)数据库操作
sql注入原理与实战(三)数据库操作
sql注入原理与实战(三)数据库操作
|
11月前
|
SQL 数据处理 数据库
SQL语句优化与查询结果优化:提升数据库性能的实战技巧
在数据库管理和应用中,SQL语句的编写和查询结果的优化是提升数据库性能的关键环节
1080 0