如何调优SQL Server的冷缓存SQL语句?

对于不经常执行的SQL语句,相关的数据可能不在缓存区,冷缓存会显著的影响这类SQL语句的性能。 用于热缓存的高性能SQL语句在冷缓存环境中可能表现的不好。经验丰富的开发人员会调优他们的SQL, 使其在两种情况下都能得到良好运行。

下面是一个SQL示例:
select * from
EMPLOYEE A
where A.EMP_ID IN (SELECT B.EMP_ID from EMP_SUBSIDIARY B
     where B.EMP_DEPT < ‘D’)

下面是Tosska的树结构执行计划,第一次执行因为缓存延迟需要8.024秒,第二次执行无需缓存延迟只需要3.7秒。

根据执行计划,您可能发现IO消耗最多的是[EMPLOYEE]表的表扫描。为了模拟冷缓存环境,我们可以在每次执行SQL语句前使用DBCC DROPCLEANBUFFERS命令来清除数据缓存。

让我为这条SQL添加OPTION(LOOP JOIN)提示,并尝试将执行计划的哈希匹配更改为嵌套循环连接。因此,将使用[EMPLOYEE]的EMP_ID(EMPLOYEE_PK)和RID查找,而不是使用表扫描。我希望RID查找可以从硬盘中选择较少的数据,同时在[EMPLOYEE]和[EMP_SUBSIDIARY]中匹配EMP_ID。

select *
from   EMPLOYEE A
where  A.EMP_ID in (select  B.EMP_ID
          from   EMP_SUBSIDIARY B
          where  B.EMP_DEPT < ‘D’) OPTION(LOOP JOIN)

根据下面的执行计划,携带数据缓存开销的执行时间从8.024秒减少到1.565秒,物理读也从190,621减少到39,044。如果您用SQL Server的EstimateIO乘以EstimiateExecutions来得到IO估计值,这是错误的。

下面的人工智能调优工具还提供了其它更好的调优解决方案:

Tosska SQL Tuning Expert (TSES™) for SQL Server® – Tosska Technologies Limited

下面带有提示的SQL语句生成一个更复杂的执行计划,最好的执行时间为0.7秒。该SQL是在冷缓存下进行调优的,每次执行SQL语句之前都会清除缓存数据。

如何调优SQL Server数据库包含EXISTS子查询的SQL语句(二)?

在我上一篇文章中,带有Exists子查询的SQL语句通过以下改写执行速度快了90倍。

SELECT *
  FROM DEPARTMENT
   where exists (select ‘x’
        from employee
        where emp_id > 2700000
        and emp_dept=DPT_ID)

执行计划:

改写的SQL语句:

select *
from   DEPARTMENT
where DPT_ID in (select  isnull(emp_dept,emp_dept)
         from    employee
         where  emp_id > 2700000)
         group by emp_dept)
执行计划:

语法改写的解决方案
DBA或者开发人员通常使用语法改写技术来改进SQL语句,尤其是对于Oracle或者MySQL数据库,但对于MS SQL Server或者IBM Db2 LUW的用户来说语法改写并不容易使用。原因是MS SQL Server和IBM Db2 LUW在它们的SQL优化器中有一个强大的内部改写引擎。这个内部SQL改写引擎会尝试将SQL语法改写为它们的内部规范语法。这意味着,无论您如何改写您的SQL语句,MS SQL Server或者IBM Db2 LUW都会尝试将SQL重新改写回它们内部假定的最优语法。所以如果假定的最优语法不好,就很难调优SQL。因为用户不容易通过简单的SQL改写来影响数据库SQL优化器生成更好的执行计划。

查询提示解决方案
为了解决这个问题,SQL Server为用户提供了查询提示功能,以帮助它的SQL优化器生成更好的执行计划。 它不像SQL改写方法,经验丰富的开发人员可能会说出改写SQL的最终执行计划是什么。查询提示是一个精确的解决方案,它通常应用于整个执行计划中的特定步骤,但对特定步骤的更改将会对整个执行计划的其他步骤产生多米诺效应。因此MS SQL Server必须调整其它步骤以实现用户对SQL语句中查询提示的预期。所以最终的执行计划并不容易被用户预测,尤其是对复杂的SQL语句。

以下由Tosska SQL Tuning Expert生成带提示的SQL比原来的SQL快了4倍左右,耗时0.639秒。

select  *
from    DEPARTMENT
where    exists (select  ‘x’
        from   employee
        where emp_id > 2700000
           and emp_dept = DPT_ID) OPTION(LOOP JOIN,HASH GROUP)

还有一个带提示的SQL,它比原始的SQL快了50倍左右,只需0.055秒。这个执行计划非常接近我上一篇文章中的改写调优。

select  *
from    DEPARTMENT
where    exists (select  ‘x’
        from   employee WITH(INDEX(EMPS_DPT_INX))
        where emp_id > 2700000
           and emp_dept = DPT_ID)

语法改写加提示的解决方案
对于一些SQL语句,语法改写或者提示方法可能无法单独解决一个复杂的SQL性能问题,有些人可能会想,是否有可能同时改写和应用提示来改进一条SQL语句?是的,这在Tosska SQL Tuning Expert人工智能引擎中是可能的,这种技术可以解决更多的SQL性能问题。稍后我将在我的博客中讨论这项技术。

Tosska SQL Tuning Expert (TSES™) for SQL Server® – Tosska Technologies Limited

下面显示Tosska SQL Tuning Expert在考察300种加提示的SQL写法后挑选出178条等价SQL,这远远超出了一个专家在10分钟内所能达到的水平。MS SQL Server是市场上对提示注入最敏感的数据库,SQL Server的查询提示通常能够影响SQL优化器生成特定的执行计划,因此MS SQL Server的SQL调优远比其他数据库更具挑战性。

如何在不修改源代码的情况下调优Oracle应用程序包的SQL

介绍

应用程序包软件是为授权给第三方组织而开发的软件程序的集合。虽然软件包软件可以通过参数或表来满足用户的特定需求,但软件本身并不是专门为组织开发的。因此,用户不能拥有源代码,也没有方法修改嵌入式SQL语句来进行性能调优。有很多基于Oracle RDBMS的应用程序包,如Siebel、PeopleSoft JD Edwards、SAP等。为了帮助应用程序包的用户,Oracle提供了一些特性来帮助用户调优SQL语句,且不需要更改源代码。

SQL profile

它是由Oracle SQL Tuning Advisor生成的概要文件。SQL概要文件包含对错误估计的统计信息和辅助信息的更正。因此,SQL profile只是将优化器引导到一个更好的计划,但它们并不保证每次解析语句时都使用相同的计划。对于某些SQL语句,无论统计数据有多好,Oracle SQL optimizer仍然无法在特定的环境中生成更好的计划。对于这些类型的SQL语句,人工干预是必要的,但是对于开发人员来说,SQL profile不是一个方便的工具,在不更改程序源代码的情况下,不能强迫Oracle执行新的计划。

SQL plan baselines and stored outlines

由于Oracle环境的变化或Oracle数据库版本升级,它可能会针对Oracle SQL优化器为某些SQL语句生成新的计划。如果它不好,我们需要一些东西来保存旧的计划,以适应新的环境。为了实现SQL计划的稳定性,stored outlines是Oracle数据库早期版本中的主要工具。Oracle数据库11g仍然支持该特性;但是,在将来的版本中,它可能会贬值,并被SQL计划管理所替代。SQL Plan Baselines的机制是保持指定SQL语句的性能,而不管数据库环境的变化或版本升级。此外,还可以手动为SQL语句创建Plan Baselines,并且该技术可以帮助开发人员指导Oracle SQL优化器为性能较差的SQL语句生成特定的计划。因此,当Oracle SQL optimizer下次接收到相同的SQL语句时,将根据数据库中存储的新计划基线组成更好的性能计划。不需要更改源程序中的SQL语句。
例如,如果您想要调优一条执行plan-A计划的SQL(目前Oracle SQL optimizer在您的数据库中使用这条SQL),并且您想要调优这条SQL(带有提示),使Oracle SQL optimizer生成一个新的计划plan-B。你需要做的是用执行调优后的SQL(带新的提示),并使用Oracle提供的以下方法:

使用优化后带提示的SQL执行,并在SGA中缓存plan B。

SET SERVEROUTPUT ON
DECLARE
    My_Plan  PLS_INTEGER;
BEGIN
     My_Plan := DBMS_SPM.load_plans_from_cursor_cache(
              sql_id          => 'Plan-B SQL_ID',
              plan_hash_value => 'Plan-B plan_hash_value’,
              sql_handle      => 'Original SQL’s sql_handle');
      DBMS_OUTPUT.put_line('Plan Loaded=> ' || My_plan);
END;

要启用调优计划,手动将调优后的计划更改为固定计划,方法是将其FIXED属性设置为YES。
要启用SQL plan baselines,请确保optimizer_use_sql_plan_baslines初始化参数设置为TRUE。

使用SQL Plan baselines进行SQL调优的缺点

由于SQL Plan baselines的设计目的是为了在以下环境发生变化后保持SQL语句的性能:

  • 新的优化器版本
  • 更改优化器统计信息和优化器参数
  • 更改schema和元数据定义
  • 更改系统设置
  • SQL profile 创建

您可以看到,它不是为手动SQL调优而设计的。还有一些额外的限制,比如SQL Plan Baselines不支持Parallel Hints,您不能为你的SQL加载一个应用Parallel Hints的Plan-B,而原始计划Plan-A的性能很差。 有时候, Parallel Hints 在Oracle SQL optimizer中往往能生成更好的计划。

SQL Patches

SQL Patches是SQL Repair Advisor提供的特性之一,用于修复SQL语句的关键故障,比如返回错误的结果。 SQL Repair Advisor分析有问题的语句,并在许多情况下建议使用SQL patch来修复该语句。SQL patch将影响Oracle SQL优化器为将来的执行选择一个替代的执行计划,而不是使用原来有问题的执行计划。在Oracle Database 12c Release 2之后,提供了一个公共API来手动创建SQL patch。DBMS_SQLDIAG.CREATE_SQL_PATCH包可以帮助用户为特定的SQL语句创建用于SQL调优的SQL patch。你可以改变一个性能不好的SQL语句的执行计划,而不需要修改程序源代码, 如下:

DECLARE
    Patch_name  VARCHAR2(32767);
BEGIN
    Patch_name := SYS.DBMS_SQLDIAG.create_sql_patch(
        sql_text  => 'SELECT *
                      FROM   employees
                      WHERE  emps_dept IN
                     (SELECT dpts_id
                      FROM   departments
                      WHERE  dpts_avg_salary <200000)', hint_text => 'INDEX(@SEL$1 EMPLOYEES) INDEX(@SEL$2 DEPARTMENTS)',
        name      => 'my_sql_patch_name');
END;

如果数据库版本在Oracle database 12c Release 2之前,则必须使用DBMS_SQLDIAG_INTERNAL.i_create_patch包代替。SQL文本和SQL ID都可以用于SQL提示注入。为SQL注入的提示应该放在hint_text输入参数中。只有一行 Hints 文本可以用于SQL,而且无法为任何子查询块定义自己的查询块名称。因此,如果您的SQL有多个子查询,并且希望指示Oracle在子查询的块中执行某些操作,则必须在注入的 Hints 文本中使用Oracle默认的查询块名称。
hint_text => ‘ INDEX(@SEL$1 EMPLOYEES) INDEX(@SEL$2 DEPARTMENTS) ‘
上面示例中的提示文本显示@SEL$1和@SEL$2是Oracle在SQL执行计划中提供的默认查询块名称。提示告诉Oracle使用索引搜索查询块@SEL$1中的EMPLOYEES表,同时使用索引搜索查询块@SEL$2中的DEPARTMENTS表。

使用SQL Patches来调优SQL的优缺点

没有SQL Plan Baselines的限制,SQL patch更灵活地接受提示指令,带parallel操作的复杂提示通常能被SQL patch接受。在创建 SQL Patches之后,不需要额外的维护工作来告诉Oracle使用 SQL Patches。 Oracle将使用存储的提示优化任何具有相同SQL ID或SQL文本的SQL,并生成更好的性能执行计划。此外,您还可以使用 SQL Patches 来禁用已在包应用程序中写入的破坏性提示的 SQL,甚至使用它来控制一个具有绑定感知的SQL的执行行为。
由于注入的提示文本必须放在一个文本行中,并且只使用默认的查询块名称,因此手工编写一个预期的提示来改进SQL语句,对于大多数SQL开发人员来说是一项困难的任务,尤其是对于包含许多子查询的复杂SQL语句。

一个自动创建Hints和SQL Patches的工具

到目前为止,市场上只有一个工具能够生成更好的提示且完全自动化的方式创建SQL Patch。
Tosska SQL Tuning Expert Pro 是一个用户可以在不接触其程序源代码的情况下提高 SQL 性能的工具。用户可以为不同大小的生产数据库部署不同的性能查询计划,而不需要保存程序源代码的多个版本,并且特别适合于不拥有其应用程序源代码的包应用程序用户。该工具将尝试最有用的提示组合来调优性能较差的SQL语句,最好的提示组合SQL性能将与原始SQL并排进行基准测试。用户将得到准确的性能改进,没有任何猜测或不确定的成本评估。

您可以访问我们的网站了解产品详情
https://www.tosska.cn/tosska-sql-tuning-expert-pro-tse-pro-for-oracle-zh/

从另一个角度看为什么要使用Oracle In-Memory数据库选项

很多人都在谈论为什么或者为什么不在他们的应用程序中使用 Oracle In-memory 数据库,他们中的大多数都过于关注数据库的大小,或者它是否是 OLAP 应用程序。中小型数据库似乎不适合使用 Oracle In-memory数据库选项。但是,如果您的 OLTP 数据库存在性能瓶颈,并且您正在寻找解决方案,我认为 Oracle In-memory数据库选项应该位于您的解决方案列表中,尤其是在计划升级硬件时。

在Oracle In-Memory 数据库中,SQL优化器优化计划空间越来愈大
计划空间是数据库 SQL optimizer 在输入 SQL 语句之前将考虑的潜在处理方法的大小。更大的计划空间大小意味着数据库 SQL optimizer 将考虑处理 SQL 语句的更多潜在方法。因此,您的 SQL 语句有更多运行更快的机会。使用 Oracle In-Memory 数据库新的 In-Memory数据访问方法,Oracle SQL 优化器将在 SQL 语句执行之前考虑更大的计划空间。由于乘数效应,新的 In-Memory 执行计划步骤可能会导致计划空间大小发生巨大扩展。例如, In-Memory 包含 5 个表的 SQL 语句,每个表的访问方法的”Table Access Inmemory Full”步骤,结果是 C(5,1)+C(5,2)+C(5,3)+C(5,4)C(5,5)=5=10=10=5=1=31。但不要忘记,此乘法器适用于原始计划空间树中的每个执行计划,这意味着新计划空间大小可能比原始计划空间大 31 倍。

OLTP 数据库如何从 In-Memory 数据库中获得优势?
OLTP 数据库是面向事务的应用程序,它要求每个事务都有快速的响应时间,但这并不意味着在工作时间没有用于联机报告或数据整合的复杂 SQL。如果此类慢速的 SQL 语句与其他联机事务同时运行,则整体性能将受到影响。如果硬件升级是其中一个选项,应考虑使用 Oracle In-Memory作为替代解决方案之一。
下面的示例向您展示了OLTP SQL语句如何从使用Oracle In-Memory数据库选项中获益。
下面是一个典型的 OLTP SQL,对所有表都进行了分析,原始执行计划显示了一个 Oracle 自适应计划,其中哈希联接或嵌套循环将在执行的初始阶段确定。此 SQL 的运行时间约为 1 分 35 秒。

L让我们使用强制提示将 EMPLOYEE 表放入Oracle In-Memory中,然后 Oracle 访问它时,采用”Table Access Inmemory Full”扫描,下面的基准测试显示,“Auto 1” SQL仅花费0.45秒就完成了,比原始执行计划快200多倍。该解决方案表明,对EMPLOYEE表引入新的“Table Access Inmemory Full”扫描,创建了比原来更好的执行计划。

对于OLTP的SQL,不需要将所有表放入 In-Memory
使用 OLTP 数据库时,我们不希望将所有表填充到In-Memory,从而给联机事务带来过多的开销。因此,对于OLTP 的 SQL 使用 In-Memory SQL 调优的目标不是选择最佳性能解决方案,而是具有较少In-Memory表的最经济高效的解决方案,且其性能改进仍是可以接受的。
如下面: “Auto 3” 解决方案把包含的所有表都被放入In-Memory,但改进仅比 “Auto 1” 高 0.01 秒,后者只需要将一个表放入In-Memory。因此,”Auto 1″显然是一个更可取的选择。

Oracle应该根据 In-Memory 的大小为 In-Memory 选项装载
实际上,Oracle In-Memory数据库不仅仅有利于OLAP用户,而是对于所有数据库用户(比如OLTP用户)来说,都是一个很好的SQL性能增强工具。鉴于Oracle In-Memory 数据库选项定价过高,限制了该新技术的快速适应,建议Oracle根据 In-Memory 大小来决定价格。换句话说,更小的尺寸和更低的价格肯定会帮助 Oracle
In-Memory数据库可广泛渗透到所有数据库用户,包括 OLTP 用户。

Author: Richard To (Richard.to@tosska.com), CTO of Tosska Technologies Limited