最喜欢的性能调优技巧


126

当您有需要性能调整的查询或存储过程时,首先要尝试哪些操作?


以下是一些SQL Server查询优化技巧
SQLMenace

我同意这不是建设性的,可以在Google中进行搜索,但是为什么它具有118 uv?:)
FLICKER

Answers:


114

这是我经常给别人询问优化问题的便捷清单。
我们主要使用Sybase,但是大多数建议将全面适用。

例如,SQL Server附带了许多性能监视/调整位,但是如果您没有这样的功能(甚至可能没有),那么我将考虑以下内容...

我看到的问题中99%是由于在联接中放置太多表引起的。解决此问题的方法是进行一半的连接(使用某些表),并将结果缓存在临时表中。然后在该临时表上进行其余查询的联接。

查询优化清单

  • 在基础表上运行UPDATE STATISTICS
    • 许多系统将其作为计划的每周工作运行
  • 从基础表中删除记录(可能存档已删除的记录)
    • 考虑每天或每周一次自动执行此操作。
  • 重建索引
  • 重建表(bcp数据输出/输入)
  • 转储/重新加载数据库(严重,但可能会修复损坏)
  • 建立更合适的新索引
  • 运行DBCC以查看数据库中是否可能损坏
  • 锁/死锁
    • 确保没有其他进程在数据库中运行
      • 特别是DBCC
    • 您在使用行级还是页面级锁定?
    • 在开始查询之前以独占方式锁定表
    • 检查所有进程是否以相同顺序访问表
  • 是否正确使用了索引?
    • 如果两个表达式的数据类型完全相同,则联接将仅使用索引
    • 仅当索引中的第一个字段在查询中匹配时才使用索引
    • 是否在适当的地方使用聚簇索引?
      • 范围数据
      • 值1和值2之间的WHERE字段
  • 小联接就是好联接
    • 默认情况下,优化程序一次只考虑表4。
    • 这意味着在连接超过4个表时,很有可能选择非最佳查询计划
  • 分手加入
    • 你能分手加入吗?
    • 将外键预选到临时表中
    • 进行一半的连接并将结果放入临时表中
  • 您使用的是正确的临时表吗?
    • #temp表的性能可能比@table大容量(数千行)的变量好得多。
  • 维护汇总表
    • 在基础表上使用触发器进行构建
    • 每天/每小时/等等构建
    • 临时创建
    • 逐步构建或拆卸/重建
  • 使用SET SHOWPLAN ON查看查询计划是什么
  • 看看SET STATS IO ON实际发生了什么
  • 使用编译指示强制索引:(索引:myindex)
  • 使用SET FORCEPLAN ON强制执行表顺序
  • 参数嗅探:
    • 将存储过程分为2
    • 从proc1调用proc2
    • 如果@parameter已被proc1更改,则允许优化程序在proc2中选择索引
  • 您可以改善硬件吗?
  • 你什么时候跑步?有安静的时间吗?
  • Replication Server(或其他不间断进程)是否正在运行?你可以暂停吗?运行它例如。每小时?

2
您指的是哪一位?
AJ。

2
这是一些很酷的东西,但我希望您能对某些主张提供一些参考。例如:我从未听说过优化一次只考虑4个表。我不明白这怎么可能是对的。您能为此提供一些参考吗?我很想知道你在哪里得到这个。
SheldonH

19
  1. 对运行查询的最佳路径有一个很好的了解。
  2. 始终检查查询计划。
  3. 打开STATS,以便可以检查IO和CPU性能。集中精力减少这些数字,而不必减少查询时间(因为这可能会受到其他活动,缓存等的影响)。
  4. 寻找进入运算符的大量行,但找出少量的行。通常,索引可以通过限制进入的行数来帮助(这可以节省磁盘读取)。
  5. 首先关注最大成本的子树。更改该子树通常可以更改整个查询计划。
  6. 我见过的常见问题是:
    • 如果有很多联接,则Sql Server有时会选择扩展联接,然后应用WHERE子句。通常,您可以通过将WHERE条件移到JOIN子句或带有内联条件的派生表中来解决此问题。视图可能导致相同的问题。
    • 次优联接(LOOP,HASH,MERGE)。我的经验法则是,当顶行与底行相比很少时,使用LOOP连接;当集合大致相等且有序时,使用MERGE;对于其他所有内容,则使用HASH。添加联接提示可让您测试理论。
    • 参数嗅探。如果首先使用不切实际的值(例如,用于测试)运行存储的proc,则对于生产值,缓存的查询计划可能不是最佳的。使用RECOMPILE再次运行应进行验证。对于某些存储过程,尤其是那些处理大小范围不同的过程(例如,今天和昨天之间的所有日期-这将需要INDEX SEEK-或去年和今年之间的所有日期-最好使用INDEX SCAN ),您可能每次都必须使用RECOMPILE来运行它。
    • 缩进错误...好吧,因此Sql Server对此没有问题-但我确定在确定格式之前,无法理解查询。

1
+1表示缩进不良。格式化是关键!:)
mwigdahl 2011年

18

稍微偏离主题,但是如果您可以控制这些问题,那么...
高层次和高影响力。

  • 对于高IO环境,请确保您的磁盘适用于RAID 10或RAID 0 + 1或RAID 1和RAID 0的某些嵌套实现。
  • 不要使用少于1500K的驱动器。
  • 确保磁盘仅用于数据库。IE浏览器没有登录没有操作系统。
  • 关闭自动增长或类似功能。让数据库使用所有预期的存储。不一定当前正在使用什么。
  • 设计用于类型查询的架构和索引。
  • 如果它是日志类型表(仅插入)并且必须在数据库中,则不要为其编制索引。
  • 如果您要分配报告(带有多个联接的复杂选择),则应考虑使用星型或雪花模式创建数据仓库。
  • 不要害怕复制数据以换取性能!

8

CREATE INDEX

确保您的WHEREJOIN子句有可用的索引。这将大大加快数据访问速度。

如果您的环境是数据集市或仓库,则几乎所有可能的查询都应包含索引。

事务性环境中,索引的数量应更少,其定义应更具战略意义,以便索引维护不会拖累资源。(索引维护是指必须更改索引的叶子以反映基础表中的更改,例如INSERT, UPDATE,and DELETE操作。)

另外,请注意索引中字段的顺序-字段的选择性越强(基数越高),它应该在索引中越早出现。例如,假设您要查询二手车:

SELECT   i.make, i.model, i.price
FROM     dbo.inventory i
WHERE    i.color = 'red'
  AND    i.price BETWEEN 15000 AND 18000

价格通常具有较高的基数。可能只有几十种颜色可用,但可能有成千上万种不同的要价。

在这些索引选择中,idx01提供了满足查询条件的更快路径:

CREATE INDEX idx01 ON dbo.inventory (price, color)
CREATE INDEX idx02 ON dbo.inventory (color, price)

这是因为满足价格要求的汽车要比颜色选择少,因此查询引擎要分析的数据少得多。

众所周知,我有两个非常相似的索引,只是在字段顺序上有所不同,以加快一个查询(名字,姓氏)和另一个查询(姓氏,名字)的速度。


6

我最近了解到的一个技巧是,SQL Server可以在更新语句中更新局部变量以及字段。

UPDATE table
SET @variable = column = @variable + otherColumn

或更易读的版本:

UPDATE table
SET
    @variable = @variable + otherColumn,
    column = @variable

在实现递归计算时,我用它来替换复杂的游标/联接,并且还获得了很多性能。

这里的详细信息和示例代码极大地提高了性能:http : //geekswithblogs.net/Rhames/archive/2008/10/28/calculating-running-totals-in-sql-server-2005---the-optimal。 aspx


5

假设这里使用MySQL,请使用EXPLAIN找出查询的内容,确保尽可能高效地使用索引,并尝试消除文件排序。高性能MySQL:优化,备份,复制和其他功能以及MySQL Performance Blog都是一本关于此主题的好书。


3
这对MySQL很好,但是问题被标记为“ sqlserver”。尽管如此,这样做还是一件好事。在SSMS中要做的类似事情是使用“显示估计的执行计划”和“包括实际的执行计划”。如果您可以消除庞大的表扫描并使用聚簇索引查找,那么您就可以很好地实现最佳性能。
eksortso,2009年


3

有时,在SQL Server中,如果在where子句中使用OR,它将真正提高性能。而不是使用OR,只需执行两个选择并将它们合并在一起。您可以以1000倍的速度获得相同的结果。


我已经看到了这种无法解释的行为。
Esen 2013年

2

查看where子句-验证索引的使用/验证没有傻事

where SomeComplicatedFunctionOf(table.Column) = @param --silly

2

我通常从联接开始-一次将每个联接从查询中剔除,然后重新运行查询以了解是否存在我遇到的特定联接。


2

在我的所有临时表上,我喜欢添加唯一的约束(在适当的情况下)以建立索引,并添加主键(几乎总是)。

declare @temp table(
    RowID int not null identity(1,1) primary key,
    SomeUniqueColumn varchar(25) not null,
    SomeNotUniqueColumn varchar(50) null,
    unique(SomeUniqueColumn)
)

2

我已经习惯了总是使用绑定变量。如果RDBMS不缓存SQL语句,则绑定变量可能无济于事。但是,如果您不使用绑定变量,则RDBMS将没有机会重用查询执行计划和已解析的SQL语句。节省的费用可能是巨大的:http//www.akadia.com/services/ora_bind_variables.html。我主要使用Oracle,但Microsoft SQL Server的工作方式几乎相同。

以我的经验,如果您不知道是否正在使用绑定变量,则可能不是。如果您的应用程序语言不支持它们,请找到一种支持的语言。有时,您可以通过对查询B使用绑定变量来修复查询A。

之后,我与我们的DBA进行了交谈,以找出导致RDBMS最痛苦的原因。请注意,您不应询问“为什么此查询速度慢?” 这就像要求您的医生将您的阑尾取出。当然,您的查询可能是问题所在,但很可能其他地方出了问题。作为开发人员,我们倾向于考虑代码行。如果线路较慢,请修复该线路。但是RDBMS是一个非常复杂的系统,查询速度慢可能是更大问题的征兆。

太多的SQL调优技巧是狂热的偶像。在大多数情况下,问题与您使用的语法无关或具有最小的关系,因此通常最好使用可以使用的最简洁的语法。然后,您可以开始研究调整数据库的方法(而不是查询)。仅在失败时调整语法。

像任何性能调整一样,请始终收集有意义的统计信息。除非您正在调整用户体验,否则不要使用挂钟时间。取而代之的是看CPU时间,读取的行和从磁盘读取的块之类的事情。人们常常为错误的事情进行优化。


2

第一步:查看查询执行计划!
TableScan->错误的
NestedLoop->警告
嵌套NestedLoop后的TableScan-> DOOM!

设置统计信息IO ON
设置统计信息TIME ON


2

在我的地方,使用WITH(NoLock)运行查询几乎是标准操作。任何人都会在数十GB的表上捕获正在运行的查询,而不会将其取出并删除。


2
应该谨慎使用,而不是习惯使用。锁定不是邪恶的,只是被误解了。

2

如果可能,将NOT IN查询转换为LEFT OUTER JOIN。例如,如果要在表1中查找表2中的外键未使用的所有行,可以执行以下操作:

SELECT *
FROM Table1
WHERE Table1.ID NOT IN (
    SELECT Table1ID
    FROM Table2)

但是,您可以获得以下更好的性能:

SELECT Table1.*
FROM Table1
LEFT OUTER JOIN Table2 ON Table1.ID = Table2.Table1ID
WHERE Table2.ID is null

1

@ DavidM

假设这里使用MySQL,请使用EXPLAIN找出查询的内容,并确保尽可能有效地使用索引...

在SQL Server中,执行计划使您有同感–告诉您正在命中哪些索引,等等。



1

本身不一定是SQL性能技巧,但肯定相关:

一个好主意是在可能的情况下使用memcached,因为它将直接从内存中获取预编译的数据而不是从数据库中获取预编译的数据会更快。还有一种内置了memcached的MySQL(第三方)。


1

确保索引长度尽可能小。这使DB一次可以从文件系统中读取更多键,从而加快了连接速度。我认为这适用于所有数据库,但是我知道这是针对MySQL的特定建议。


1

我寻找:

  • 展开所有CURSOR循环,并将其转换为基于集合的UPDATE / INSERT语句。
  • 请注意以下任何应用程序代码:
    • 调用返回大量记录的SP,
    • 然后在应用程序中,遍历每条记录并调用带有参数的SP以更新记录。
    • 将其转换为一个事务中可以完成所有工作的SP。
  • 任何执行大量字符串操作的SP。有证据表明数据结构不正确/未规范化。
  • 任何重新发明轮子的SP。
  • 一分钟内我无法理解它正在尝试执行的任何SP!

1
SET NOCOUNT ON

通常,除非实际需要使用,否则我的存储过程通常位于第一行@@ROWCOUNT


2
无论如何设置@@ ROWCOUNT。NOCOUNT禁用“受影响的xx行”语句。
Sklivvz

这真的会在性能上产生明显的不同吗?
JohnFx

是的,那么每次运行SQL语句时,计数不会自动计算。在有条件查询和无条件查询的情况下,很容易就可以看出它确实有所作为。
travis

无论如何,SQL Server中都会跟踪该计数。您看到的任何性能差异都是因为计数必须通过网络到达前端。如果您要执行单个SELECT,则不会产生明显差异。如果您有一个带有100000次插入的循环,那么网络上就会有很多额外的花费。
汤姆H

1

在SQL Server中,使用nolock指令。它允许select命令完成而不必等待-通常是其他事务完成。

SELECT * FROM Orders (nolock) where UserName = 'momma'

3
NOLOCK仅用于您不关心正确结果的查询
Mark Sowul

1

删除不需要的游标。


是的,光标是个诅咒!;)
Sklivvz

8
啊。不要把那些不合格的东西扔掉。游标就像枪。他们自己并不坏,只是人们与他们一起做真正的坏事。
JohnFx

1

删除Sprocs中的函数调用,其中很多行将调用该函数。

我的同事使用函数调用(例如从userid获取lastlogindate)返回非常宽的记录集。

经过优化任务后,我用函数的代码替换了存储过程中的函数调用:我使许多存储过程的运行时间从> 20秒减少到<1。


0
  • 用dbo前缀所有表。防止重新编译。
  • 查看查询计划并搜索表/索引扫描。
  • 2005年,在管理视图中搜索缺少的索引。


0

不要用“ sp_”作为存储过程名称的前缀,因为系统过程都以“ sp_”开头,并且在调用过程时,SQL Server必须更加努力地寻找过程。


1
您是否实际对这一基准进行了基准测试?如果SQL Server在做合理的事情(使用哈希算法来定位存储的Proc),那么这将没有任何区别。实际上,如果SQL Server 这样做,则系统性能似乎会发臭(因为它大概调用了它自己的proc)。
John Stauffer

1
我认为这属于过早优化的阶段。避免对人们造成混淆,这可能是一个好习惯,但是作为优化技巧... D-
JohnFx

0

脏读 -

set transaction isolation level read uncommitted

在绝对不需要事务完整性的情况下防止死锁(通常是这样)


1
是的,但是这会导致很难发现的怪异错误。
格兰特·约翰逊

0

我总是先去SQL Profiler(如果它是具有很多嵌套级别的存储过程)或查询执行计划器(如果它是一些没有嵌套的SQL语句)。您有90%的时间可以使用这两种工具之一立即发现问题。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.