什么是最佳的SQL Server性能优化技术?

17

我一直采取的方法是先使用最小的索引部署数据库,然后根据性能情况添加/更改索引。

这种方法还算有效。然而,它仍然不能告诉我在哪里可以提高性能。它只能告诉我性能有多糟糕,以至于用户会抱怨。

目前,我正在对许多应用程序上的数据库对象进行重构。

那么,既然“过早优化是万恶之源”,我是否不必寻找性能改进呢?

在重构应用程序代码时,开发人员不断寻找提高代码质量的方法。是否有一种方法可以不断地寻找数据库性能的改进方式?如果有的话,你认为哪些工具和技术最有帮助?

我曾经简单尝试过“数据库引擎调整顾问”,但并没有发现有任何帮助。也许我只需要更多解读结果的经验。

14个回答

11

我的方法是使用SQL Server Profiler将针对服务器或数据库的命令收集到表中。一旦完成,您可以基于最大和平均执行时间、最大和平均CPU时间以及(同样重要的)查询运行的次数来查询。

由于我尝试将所有数据库访问代码放入存储过程中,因此很容易分离查询。如果您使用内联SQL,可能会更难,因为对查询中某个值的更改会使其看起来像不同的查询。您可以尝试使用LIKE运算符将相同类型的查询放入同一桶中以计算聚合(最大值、平均值、计数)。

一旦您有了潜在问题的“前10名”列表,就可以逐个查看它们,看看是否可以重新设计查询、是否可以使用索引或者需要进行小型架构更改。要想得出前10名,请尝试以不同的方式查看数据:周期内的总成本的平均 * 计数,最严重的违规者的最大值,仅仅平均值等。

最后,请务必根据需要监视不同的时间段。在每个人都开始运行他们的日常报告的早晨,数据库使用情况可能与中午用户输入新数据时不同。您还可以决定,即使某些夜间过程比任何其他查询都要慢,但由于它是在非工作时间运行的,因此并不重要。

祝你好运!


11

"过早优化是万恶之源"

就数据库编程而言,我认为这句话是无意义的。因为如果开发人员不在第一次编写代码时关注效率,那么重写整个应用程序将会非常昂贵。所有的T-SQL代码都应该首先考虑数据完整性,其次是如何影响数据库性能。除了数据完整性外,性能应该胜过其他一切。

是的,有些优化方法在出现问题之前不应该使用,但有些事情应该作为例行公事而不是之后再修复。编写具有更高效率可能性的代码并不需要比编写效率低的代码花费更多的时间,只要了解了如何通过糟糕的代码影响效率即可。Cervo讨论游标代码的部分是一个例子。与游标解决方案相比,基于集合的操作几乎总是更快的,因此最初不应该编写游标,而应该使用基于集合的解决方案。对于我来说,编写基于集合的解决方案要比编写游标解决方案更省时间,但要想做到这一点,唯一的办法就是从不编写游标。

而且,没有理由使用select *而不是指定字段名称。在MSSQL中,您可以从对象资源管理器中拖动那些名称,所以您不能告诉我这样做太难了。但是,只指定实际需要的字段可以节省网络资源、数据库服务器资源和Web服务器资源。那么为什么程序员应该采取select *的懒惰选择,并担心之后进行优化呢?

索引也是同样的情况。你说你只建立最小限度的索引。根据你定义最小的方式,这可能可以接受,但是在所有外键上都要有索引是至关重要的,而且我不会想要推销一个没有在where子句中经常出现的一些字段上建立索引的数据库。如果你的用户是外部客户而不是内部人员,他们不会抱怨你的网站速度慢,他们会转向别处。从一开始规划高效的数据库访问对于商业意义来说是必要的。

我对不从一开始考虑效率的问题之一是,当事情变得太慢时,公司往往会只是投入更多设备来解决问题,而不是性能调优。当人们开始进行性能调优时,你已经有了一个几千兆字节或更大的数据库,其中有许多不满意的客户因为超时而无法获得结果。此时,通常必须重新编写数据库中的几乎所有内容,同时您也可能失去客户。我记得在一家商业应用公司提供支持的时候,客户服务代表移动一个屏幕需要花费十分钟的时间,这时他们正在试图帮助已经不满意的客户。由于商业产品中的查询设计不良而导致的客户流失,我们无法改变这种情况,您可以想象公司失去了多少客户。


我同意这个观点 - 数据库系统涉及到金属部件的来回运动。这些金属部件并没有像集成电路那样呈指数级增长速度,而且查询错误可能会对性能产生数量级的影响。 - ConcernedOfTunbridgeWells
事实上,我用基于集合的代码替换了游标,将性能从几分钟提高到了几毫秒,有时甚至可以将几小时的时间缩短为几秒钟。本来就没有必要写这种代码。 - HLGEM
1
过早的优化是指在没有测量节省多少时间/精力时避免优化某些东西。避免使用游标并不是过早的优化,因为避免使用游标确实有非常强有力且有充分文献支持的性能改进。 - Nat
为了配合@nat的观点,有很多不同的查询方法可以得到相同的结果,但是它们之间的性能差异有令人信服的文档记录。除了游标之外,使用可搜索的where子句、使用where exists而不是IN或避免相关子查询等都是一些需要注意的事项。但是,关于性能调优的整本书籍都会教你哪些是编写查询的不良技术,哪些是良好的技术。在阅读完这些书籍之前,没有人应该编写SQL代码(或使用ORM)。 - HLGEM
继续:在已知更好的执行技术需要相同或更少的编码时间时,使用已知效果不佳的技术只会导致糟糕的编码。这通常是因为人们不知道查询的构建方式会产生差异。 - HLGEM


2
在对代码进行分析后,将那些看起来有问题的查询放入SQL查询分析器中,并显示执行计划。确定查询中执行代价高昂的表扫描部分,并重新索引这些表以最小化代价。
参考资料:
优化 SQL:Optimizing SQL 如何优化查询:How to Optimize Queries

1

在数据库表列中应用适当的索引

  • 确保您的数据库中的每个表都有一个主键。

这将确保每个表都创建了一个聚集索引(因此,表的相应页面在磁盘上根据主键字段物理排序)。因此,使用主键从表中检索任何数据,或者使用主键字段进行任何排序操作,或者在where子句中指定任何一系列主键值将非常快地检索表中的数据。

  • 在使用搜索条件的列上创建非聚集索引

    用于频繁查询的列。

    用于连接其他表的列。

    用作外键字段的列。

    具有高选择性(返回特定值的行占总行数的低百分比(0-5%)的列)。

    在ORDER BY子句中使用的列。

不要在SQL查询中使用“SELECT *”

多余的列可能会被获取,这会增加数据检索时间的开销。 数据库引擎无法利用“覆盖索引”的好处,因此查询执行缓慢。

例如:

SELECT Cash, Age, Amount FROM Investments; 

改为:

SELECT * FROM Investments;

尽量避免在Select语句中使用HAVING子句

HAVING子句用于在选择所有行后过滤行,并像过滤器一样使用。请不要将HAVING子句用于任何其他目的。

例如:

SELECT Name, count (Name) FROM Investments WHERE Name!= ‘Test’ AND Name!=ValueGROUP BY Name;

改为:

SELECT Name, count (Name) FROM Investments GROUP BY Name HAVING Name!= ‘Test’ AND Name!=Value’ ;

尽量减少查询中的子查询块数

有时我们的主查询中可能会有多个子查询。我们应该尽量减少查询中的子查询块数。

例如:

SELECT Amount FROM Investments WHERE (Cash, Fixed) = (SELECT MAX (Cash), MAX (Fixed) FROM Retirements) AND Goal = 1; 

替换为:

SELECT Amount FROM Investments WHERE Cash = (SELECT MAX (Cash) FROM Retirements) AND Fixed = (SELECT MAX (Fixed) FROM Retirements) AND Goal = 1;

避免在SELECT列表中选择不必要的列和在连接条件中包含不必要的表

在Select查询中选择不必要的列会增加实际查询的开销,特别是如果不必要的列是LOB类型。 在连接条件中包含不必要的表会强制数据库引擎检索和获取不必要的数据,并增加查询执行时间。

不要在子查询中使用COUNT()聚合函数进行存在性检查

当您使用COUNT()时,SQL Server不知道您正在进行存在性检查。它会计算所有匹配的值,无论是通过表扫描还是通过扫描最小的非聚集索引来进行。 当您使用EXISTS时,SQL Server知道您正在进行存在性检查。当它找到第一个匹配的值时,它返回TRUE并停止查找。

尽量避免在两种类型的列之间进行连接

当在两个不同数据类型的列之间进行连接时,其中一列必须转换为另一列的类型。被转换的列是类型较低的那个。 如果您正在连接具有不兼容类型的表,则其中一个可以使用索引,但查询优化器无法选择要转换的列上的索引。

尽量不要使用COUNT(*)来获取表中的记录数

通常我们使用以下Select语句来获取表格的总行数:
SELECT COUNT(*) FROM [dbo].[PercentageForGoal]

这个查询将执行全表扫描以获取行数。以下查询不需要进行全表扫描。(请注意,这可能并不总是给出100%完美的结果,但仅在您不需要完美计数时很有用。)

SELECT rows FROM sysindexes
WHERE id = OBJECT_ID('[dbo].[PercentageForGoal]') AND indid< 2

尝试在查询中适当使用像EXISTS、IN和JOINS这样的运算符

  • 通常情况下,IN的性能最慢。
  • 只有在大多数选择的筛选条件放置在SQL语句的子查询中时,IN才是高效的。
  • 当大多数选择的筛选条件在SQL语句的主查询中时,EXISTS是高效的。

尽量避免使用动态SQL

除非真正需要,否则尽量避免使用动态SQL,因为: 动态SQL难以调试和排错。 如果用户提供动态SQL的输入,则有可能发生SQL注入攻击。

尽量避免使用临时表

除非真正需要,否则尽量避免使用临时表。而是使用表变量。 在99%的情况下,表变量驻留在内存中,因此速度更快。临时表驻留在TempDb数据库中。因此,在临时表上操作需要进行跨数据库通信,因此速度会更慢。

搜索文本数据时,使用全文本搜索而不是LIKE搜索

全文搜索始终优于LIKE搜索。全文搜索可以实现复杂的搜索条件,这些条件无法使用LIKE搜索实现,例如搜索单个词或短语(并可选地对结果集进行排名),搜索接近另一个词或短语的词或短语,或搜索特定单词的同义形式。实施全文搜索比LIKE搜索更容易(特别是在需要复杂搜索要求的情况下)。
尝试使用UNION来实现“OR”操作
不要在查询中使用“OR”,而是使用“UNION”将两个不同查询的结果集合并。这将提高查询性能。如果不需要区分结果,请使用“UNION ALL”。相对于“UNION”,“UNION ALL”更快,因为它不必对结果集进行排序以查找区分值。
为大型对象实施延迟加载策略
将大型对象列(如VARCHAR(MAX)、Image、Text等)存储在与主表不同的表中,并在主表中放置对大型对象的引用。检索查询中的所有主表数据,如果需要加载大型对象,则仅在需要时从大型对象表中检索大型对象数据。

在用户定义函数中实现以下良好实践

不要在存储过程、触发器、函数和批处理中重复调用函数。例如,您可能需要在过程的许多地方使用字符串变量的长度,但不要每次需要时都调用LEN函数;相反,调用LEN函数一次,并将结果存储在变量中以供以后使用。

在触发器中实现以下良好实践

  • 尽量避免使用触发器。触发触发器并执行触发事件是一个昂贵的过程。
  • 永远不要使用可以使用约束实现的触发器。
  • 不要为不同的触发事件(插入、更新和删除)使用相同的触发器。
  • 不要在触发器内使用事务性代码。触发器始终在触发触发器的代码的事务范围内运行。

1

我的建议是,在这种情况下,“过早优化是万恶之源”绝对是无稽之谈。

在我看来,这一切都与设计有关 - 当您正在设计数据架构时,需要考虑并发性、热点、索引、扩展和使用模式。

如果您不知道需要哪些索引以及它们需要如何配置,而不进行分析,那么您已经失败了。

有数百万种优化查询执行的方法,但归根结底,数据最终会落在您告诉它的地方。


1

对你的查询进行分析,不仅是那些显而易见的,还包括访问不同表格、视图等复杂查询和返回多个表格中的行数的查询。

这将准确告诉你应该关注哪些方面。


1

性能分析是关键,但在使用性能分析集时,必须确保它是准确的测试数据集,否则调整工具将无法为您提供所需的准确结果。

此外,2005年的管理对象和碎片化使用报告非常有帮助!


1

当然,您必须对查询进行分析并查看执行计划。但是,反复出现的两个主要问题是尽早尽可能地过滤掉尽可能多的内容,并尝试避免使用游标。

我曾经看到一个应用程序,其中某人将整个数据库表格的事件下载到客户端,然后逐行浏览每一行并根据某些条件进行过滤。将筛选条件传递给数据库并在where子句中应用查询条件会有巨大的性能提升。这对于与数据库工作的人来说是显而易见的,但我已经看到类似的事情出现。此外,有些人的查询存储了许多不需要的临时表中的行,然后在临时表的最终连接中消除了这些行。基本上,如果从填充临时表的查询中消除,则剩余查询的数据量较少,整个查询运行速度更快。

游标是显而易见的。如果你有一百万行数据,逐行处理将需要很长时间。经过一些测试,即使使用像Perl这样“慢”的动态语言连接到数据库并对数据集执行逐行操作,速度仍然比数据库中的游标快得多。如果使用Java/C/C++等编程语言进行操作,则速度差异更大。如果您可以在数据库代码中找到/消除游标,它将运行得更快...如果必须使用游标,则将该部分重写为任何编程语言并将其从数据库中取出,可能会产生巨大的性能提升。
关于游标的另一个注意事项是,要小心像SELECT @col1 = col1,@col2 = col2,@col3 = col3 where id = @currentid这样的代码,在循环中遍历ID,然后在每个列上执行语句。基本上这也是一个游标。不仅如此,使用真正的游标通常比这个更快,特别是静态和forward_only游标。如果您可以将操作更改为基于集合的操作,它将更快...尽管如此,游标在某些情况下还是有用的...但从性能角度来看,与基于集合的方法相比,使用它们会有一定的惩罚。

同时要注意执行计划。有时它估计需要几秒钟的操作非常昂贵,而需要几分钟的操作非常便宜。在查看执行计划时,请确保通过将一些SELECT '在这个区域'、GETDATE()插入您的代码来检查所有内容。


0

看起来你在谈论 MS SQL。

启动分析器并记录您在数据库上运行的最常见查询。 然后打开执行计划运行这些查询,您将看到什么(如果有)会减慢查询速度。然后您可以优化查询或在字段上添加更多索引。

SQL 书籍将为您提供有关分析和查询分析功能的良好概述。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接