如何为SQL Server调优子查询中带有OR条件的SQL语句?

下面示例显示带有EXISTS子查询的SQL语句。如果在DEPARTMENT表的子查询中满足OR条件,SQL会对EMPLOYEE表中的记录进行计数。

select countn(*) from employee a where
exists (select ‘x’ from department b
   where a.emp_id=b.dpt_manager or a.emp_salary=b.dpt_avg_salary
    )

下面是Tosska专有的树结构执行计划,它需要4分29秒才能完成。

该执行计划显示了从EMPLOYEE到全表扫描DEPARTMENT表的嵌套循环,这是整个执行计划的主要问题。因为 SQL Server 无法通过其他连接操作来解决这个OR条件“a.emp_id=b.dpt_manager or a.emp_salary=b.dpt_avg_salary”。

下面我将子查询中的OR条件改写为UNION ALL子查询,子查询中UNION ALL的第一部分表示“a.emp_id=b.dpt_manager”条件,第二部分表示“a.emp_salary=b.dpt_avg_salary”条件,但排除已经满足第一个条件的数据。

select count(*)
from  employee a
where exists ( select ‘x’
        from   department b
        where a.emp_id = b.dpt_manager
        union all
        select  ‘x’
        from   department b
        where ( not ( a.emp_id = b.dpt_manager )
           or b.dpt_manager is null )
           and a.emp_salary = b.dpt_avg_salary )

下面是改写后SQL的执行计划,看起来有点复杂,但现在性能很好,只需要0.447秒。有两个散列匹配连接用于替换原来从EMPLOYEE表到全表扫描DEPARTMENT表的嵌套循环。

虽然最终改写的步骤有点复杂,但这种改写可以由Tosska SQL Tuning Expert for SQL Server自动完成,这表明该改写SQL比原始SQL快了600多倍。

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

如何让SQL语句运行得更慢…但让用户的体验更好(Oracle)?

您的最终用户可能会不断抱怨他们的数据库应用程序的某些功能运行缓慢,但您可能会发现,在当前的Oracle和硬件配置中,这些SQL语句已经达到了它们的最大速度。除非您愿意升级硬件,否则可能没有办法改进SQL。有时,为了让用户感觉更好,您不必调优SQL以使其运行得更快,而只需调优SQL以使某些应用程序的SQL语句运行得更慢。

这是一个示例,该SQL用于显示表Emp_sal_hist和表Employee在满足某些条件后的信息。该SQL是在线查询执行的,用户必须等待至少5秒,才能在单击鼠标后在屏幕上显示任何数据。

select * from employee a,emp_sal_hist c
where a.emp_name like ‘A%’
and a.emp_id=c.sal_emp_id
and c.sal_salary<1800000
order by c.sal_emp_id

下面是SQL的执行计划和执行统计,提取全部79374条记录花费了10.41秒,第一条记录返回的反应时间为5.72秒。该查询显示了EMPLOYEE和EMP_SAL_HIST表的MERGE JOIN,在将其合并到最终结果之前,对两个相对应的表进行排序操作。这就是用户必须等待至少5秒才能看到屏幕上显示的内容的原因。

作为条件“a.emp_id = c.sal_emp_id”,我们知道“ORDER BY c.sal_emp_id”和“ORDER BY a.emp_id”是等效的,因为SQL语法改写不能强制对该SQL的执行计划进行指定的操作,所以我添加了一个优化器提示/*+ INDEX(@SEL$1 A EMPLOYEE_PK) */来减少“order by a.emp_id”的排序时间。

SELECT   /*+ INDEX(@SEL$1 A EMPLOYEE_PK) */ *
FROM   employee a,
     emp_sal_hist c
WHERE  a.emp_name LIKE ‘A%’
   AND a.emp_id = c.sal_emp_id
   AND c.sal_salary < 1800000
ORDER BY c.sal_emp_id

虽然在新的执行计划中,总体运行时间增加了3秒,但响应时间从5.72秒减少到1.16秒,因此用户可以更快的看到屏幕上第一页信息。我相信大多数用户不在乎是否还有3秒可以返回所有79374条记录。这就是为什么在管理用户期望时,SQL调优更像一门艺术而不是科学。
这种改写可以由Tosska SQL Tuning Expert Pro for Oracle自动实现。

https://www.tosska.cn/tosska-sql-tuning-expert-pro-tse-pro-for-oracle-zh/

如何调优SQL Server性能不好并且设置返回行数的SQL语句?

在设置返回行数或者使用Top关键字后,某些SQL语句会运行的非常慢。设置返回行数和使用Top关键字会告诉SQL Server从SQL语句中选择特定行数而不是提取所有记录。没有多少人知道在设置返回行数或者使用Top关键字后SQL Server会尝试重新优化您的SQL语句,重新优化后的SQL语句产生的执行计划通常可以更快的检索前几条记录,但是也可能适得其反。

设置返回行数重新优化查询的好示例
下面是一个示例,显示了该SQL从数据库中检索217500行记录需要6.78秒。这是一个好的执行计划,因为它对[DEPARTMENT]和[EMPLOYEE]的两个表扫描进行哈希匹配。

下面屏幕显示了设置返回行数为1后新的执行计划把哈希匹配改为嵌套循环。嵌套循环操作通常提供更快的前几条记录检索时间,但在某些情况下可能不利于整体记录的提取。很高兴看到SQL Server提取此SQL的第一行记录仅用了0.013秒。

设置返回行数重新优化查询的坏示例
让我们来看一个坏示例,它展示了在使用设置返回行数为1后,SQL Server如何将一个好的执行计划变成一个糟糕的执行计划。下面一个示例显示,该SQL从数据库中检索1613行记录花费了0.118秒,虽然该执行计划有点复杂,但对于检索完所有行来说是一个很好的执行计划。

下面屏幕显示了设置返回行数为1后生成的新执行计划,该执行计划现在更改为带有两次表扫描的嵌套循环。新的执行计划提取第一条记录需要1.312秒,比从数据库提取所有行所用的0.118秒还要慢。

如何解决这个问题呢?
我们可以使用提示注入或者SQL语法改写来影响SQL Server为设置返回行数或者Top关键字操作改回原始的执行计划或者生成更好的执行计划。下面是提示注入生成的一个很好的执行计划,它比设置返回行数为1的原始SQL快了90倍。

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

如何调优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调优远比其他数据库更具挑战性。

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

下面示例展示了一个带有Exists子查询的SQL语句。该SQL语句查询DEPARTMENT表中DPT_ID列的值和employee表中emp_dept列的值相等并且employee表中emp_id列的值大于2700000的记录。

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

下面是Tosska树结构执行计划,该sql语句执行需要2.23秒才能完成。

该执行计划显示了从[EMPLOYEE].[EMPLOYEE_PK]到[EMPLOYEE].[EMPS_DPT_INX]的两个哈希匹配,然后合并连接到排序的[DEPARTMENT]表。这个执行计划看起来很合理,但是在第一阶段从[EMPLOYEE]表扫描的记录数太多了,我们可以用比较小的[DEPARTMENT]表去扫描[EMPLOYEE]表来改善这条SQL语句。

下面让我将EXISTS子查询改写为IN子查询,但是执行计划并没有按照预期的改变。

select *
from   DEPARTMENT
where DPT_ID in (select  emp_dept
         from    employee
         where  emp_id > 2700000)

我将进一步的改写SQL,并且添加伪函数“isnull(emp_dept,emp_dept)”到查询列表中,但它不能停止哈希匹配到[EMPLOYEE].[EMPS_DPT_INX]的操作。

select *
from   DEPARTMENT
where DPT_ID in (select  isnull(emp_dept,emp_dept)
         from    employee
         where  emp_id > 2700000)

为了进一步停止“哈希匹配到[EMPLOYEE].[EMPS_DPT_INX]”这一操作,我尝试在子查询中添加“group by emp_dept”伪操作。

select *
from   DEPARTMENT
where DPT_ID in (select  isnull(emp_dept,emp_dept)
         from    employee
         where  emp_id > 2700000)
         group by emp_dept)

下面是最终改写后的执行计划,SQL Server首先对[DEPARTMENT]进行表扫描和EMPS_DPT_INX索引从[EMPLOYEE]中寻找RID的嵌套循环操作,因此[DEPARTMENT]表中的每条记录最多匹配一次[EMPLOYEE]。现在的执行速度是0.024秒,比原来的SQL快很多。

尽管改写的步骤有点复杂,但它可以由Tosska SQL Tuning Expert for SQL Server自动实现,并且它比原始SQL快了90多倍。

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

释放强大的 Oracle Optimizer Hints 来优化SQL语句

常见的SQL调优方法
SQL 调优是提高 SQL 语句性能,达到用户期望的过程。开发人员或 DBA 通常使用三种方法,这些方法包括创建新索引、重写 SQL 语句和将 Oracle 优化器提示应用于 SQL 语句。每种方法都有其优缺点,适用于应用周期的不同阶段。让我们在下面讨论这三种方法。

为SQL语句创建新的索引
为SQL语句创建新的索引是提高SQL性能的一种非常常见的方法,在数据库开发过程中尤其重要。由于SQL语句的新索引不仅影响当前SQL,还影响同一数据库上运行的其他SQL语句。因此,在生产数据库中应谨慎使用。通常,用户需要对新创建的索引的其他相关SQL语句进行影响分析。

重写 SQL 语句
有很多人通过互联网教授 SQL 重写技能。这些重写规则大多都基于 Oracle 规则的 SQL 优化器或基于成本的 SQL 优化器的旧版本继承。例如,有些人可能认为以下 SQL 可能具有不同的性能:
Select * from A where A.unique_key in (select B.unique_key from B);
Select * from A where exists (select ‘x’ from B where A.unique_key=B.unique_key);

实际上,如果将这两个 SQL 放入 Oracle 数据库中,您可能会从 Oracle 获得相同的查询计划;这是因为 Oracle SQL 优化器将在内部将这两个 SQL 重写为一个标准形式,以便生成更好的查询计划。在过去的二十年中,Oracle SQL optimizer开发了一种更强大的内部重写能力。因此,Oracle已经解决了一些明显的问题,如“KEY IS NOT NULL”或“NVL(KEY, ‘ ABC ‘) = ‘ ABC ‘”不能使用索引。当然,对于复杂的SQL转换,Oracle SQL optimizer仍然存在一些限制,因此经验丰富的用户仍然可以通过SQL重写来调优SQL语句,从而影响Oracle SQL optimizer生成更好的查询计划。但是,由于 SQL 语法和最终查询计划生成之间的关系越来越弱,因此 DBA 越来越难以应用此方法,这是因为Oracle基于成本的优化器(CBO) 变得越来越智能,并且 SQL 文本的语法不再是成本计算和计划生成中的主导因素。
SQL 重写在开发和生产数据库中仍然很有用,因为它是对数据库的单独更改,并且不会影响其他 SQL 的性能,并且比创建新索引更安全。但是它需要在程序源代码中修改SQL语句,因此单元测试或集成测试可能仍是必需的。相反,使用提示来调整 SQL 相对更安全,因为不容易意外地更改SQL的语义。

对SQL语句应用提示
市场上的大多数数据库都提供了一些查询计划控制语言,以帮助其优化器更好地优化特定的 SQL 语句。例如:IBM LUW 优化指南和 SQL Server 计划指南已经提供了多年。Oracle 提供了嵌入 SQL 文本的优化器提示,用于告诉 Oracle SQL 优化器如何优化 SQL 语句。由于提示不会影响 SQL 语句的语义,因此它比 SQL 重写和生成新索引相对更安全。Oracle提供了超过100个文档化的优化提示。使用正确的提示来调整 SQL 是并不容易掌握,除非您是使用优化器提示的专家,即使是专家,如果从一百个提示中挑选只3个提示,那也是非常的困难的, 因为它是接近一百万的排列。
让我们用一个带有以下 Optimizer Hints 的简单SQL示例来展示优化提示是如何工作的,这些提示用于告诉Oracle SQL Optimizer在查询计划选择期间尽可能使用DEPARTMENT表的索引。Oracle将通过对DEPARTMENT表的索引检索,在所有计划中寻找成本最低的计划。
Select /*+ INDEX(@QB2 DEPARTMENT) */ *
From employee
Where emp_dept in (Select /*+ QB_NAME(QB2) */ dpt_id
    From department
    Where dpt_name LIKE ‘D%’);

Oracle SQL提示不是一种编程语言
Oracle Optimizer hint不是一种带有正确语法检查或错误返回的编程语言。这意味着一个无效的SQL语句提示指令,Oracle SQL优化器不会返回错误消息。此外,即使这是一条有效的提示指令,Oracle SQL优化器实际上不能遵守,也不会返回错误消息。因此,在影响SQL优化器生成特定的更好的查询计划之前,用户必须进行大量的反复试验。

了解解决方案
这是一个非常常见的调优实践,人们试图为性能较差的SQL语句找到最佳的查询计划,它的工作就像一个人模拟Oracle SQL优化器的工作,用人的知识优化SQL语句。如果SQL语句很简单,而且Oracle SQL优化器犯了一个明显的错误,那么人工干预对这种简单的SQL很有效。举个例子:
Select * from A where A.KEY<’ABC’;
如果Oracle SQL optimizer没有使用索引从表A检索记录,而使用索引KEY1实际上是一个更好的解决方案。您可以使用优化器提示来指示Oracle对该SQL语句使用索引而不是全表扫描。
Select /*+ INDEX(KEY1 KEY1_INX) */ from A where A.KEY1<’ABC’ and A.KEY2<123 and A.KEY3<’C’;
了解最佳解决方案对于简单的 SQL 语句来说很容易,但对于复杂的 SQL 语句来说却很难。由于复杂 SQL 的查询计划中有很多执行步骤,因此人类无法估计每个步骤并制定一系列查询步骤来组成最佳性能查询计划。因此,使用 Oracle 优化器提示来指示 SQL 优化器为复杂 SQL 生成特定的查询计划可能并不容易,即使对于经验丰富的 SQL 调优专家也是如此。

了解问题
与了解 SQL 语句的解决方案不同,人工专家可以相对更轻松地查找问题在复杂查询计划中的位置。告诉 Oracle 绕过此问题的方法是应用前缀为”NO_”的提示,如 NO_INDEX 或 NO_USE_HASH。它告诉 Oracle 不要在查询计划中使用指定的操作,而是选择成本较低的其他操作。这种方法在市场上并不普遍采用,因为人们通常受以解决方案为导向的思维的约束。
例如:
Select /*+ NO_INDEX(KEY2 KEY2_INX) */ * from A where A.KEY1<’ABC’ and A.KEY2<123 and A.KEY3<’C’;
如果您认为 KEY2_INX 不是从表 A 检索记录的好索引,则可以通过应用 /* NO_INDEX(KEY2 KEY2_INX)*/ 来禁用 KEY2 索引,并让 Oracle 选择其他索引来检索记录。

释放Oracle优化器提示的潜在功能
无论您是使用面向解决方案的方法还是问题绕过方法,您都需要了解 SQL 查询计划的详细信息、数据分布、每个步骤的成本,并尝试预测计划的最终实际聚合成本是什么。有时,您必须将这两种技术用于复杂的 SQL 语句;您必须为查询计划的某些部分提供最佳查询步骤,并使用”NO_”绕过查询计划中的已知问题。这是一个非常复杂的过程,如果没有对 SQL 调优的深入了解,普通 SQL 开发人员很难执行。
下面的示例显示了一个花费8.56秒完成的SQL,查询计划看起来很正常。SQL语法是完整的,因此没有太多空间进行SQL重写。可能并行执行可以帮助改进SQL语句,但是应该使用哪个表或索引进行并行执行,以及新的并行执行步骤如何影响整个查询计划。即使对于经验丰富的 SQL 调优专家来说,这也不是一件容易的事。这就是为什么Oracle优化器提示没有从一开始就被充分挖掘的原因。

借助最新的 AI 算法,计算机搜索引擎可以极大地释放用户发现提示组合的努力,而无需经历巨大的提示排列空间。它使 hint SQL调优变得更容易,并可以解决比您预期的更多的问题。
以下解决方案显示了一系列提示组合,这些提示组合告诉 Oracle 不要使用 EMPLOYEES.EMPSS_GRADE_INX 索引和排除哈希联接以联接表,然后使用表 EMPLOYEES 的并行索引扫描。它生成比原始计划快 70% 的新查询计划。整个调优过程无需人工干预即可完成,结果是安全的,因为它不涉及任何语法重写。


https://www.tosska.cn/tosska-sql-tuning-expert-tse-oracle-zh/

Tuning SQL without touching the its SQL text
如果您是一个应用程序用户,您可能想知道,如果无法直接编辑查询,如何调整 SQL。或者,如果您是应用程序开发人员,您希望快速修复 SQL 性能,而无需花时间进行思考源代码更改和单元测试。Oracle 提供了多个功能,例如 SQL 配置文件、SQL Plan Baselines和 SQL Patch, 您可以使用这些功能告诉 Oracle 修复 SQL 的查询计划,而不需要更改SQL文本。但是这些功能的使用仅受 Hints 注入的限制,您不能使用不同的语法重写 SQL,并且要求原始 SQL 接受重写的 SQL 查询计划。因此,在当今瞬息万变的数据库应用程序中,基于Hints的 SQL 调优变得越来越重要。
实际上,SQL 性能应该与应用程序源代码分离,SQL 性能应该易于管理,以便随时随地部署和回滚。它还应该为一个源 SQL 代码进行定制,以适应不同大小的数据库。基于hints的 SQL 调优将是释放 Oracle 数据库 SQL 性能管理潜在功能的关键,在现代数据库 SQL 调优过程中,它变得越来越重要。