当您有一个需要性能调整的查询或存储过程时,您首先尝试的是什么?
29 回答
这是我总是给问我优化问题的人提供的方便的花花公子清单。
我们主要使用 Sybase,但大多数建议将全面适用。
例如,SQL Server 带有许多性能监控/调整位,但如果你没有类似的东西(也许即使你有),那么我会考虑以下...
我见过的99% 的问题是由于在一个连接中放置了太多的表造成的。解决此问题的方法是执行一半的连接(与某些表)并将结果缓存在临时表中。然后在该临时表上执行其余的查询连接。
查询优化清单
- 在基础表上运行 UPDATE STATISTICS
- 许多系统将此作为计划的每周作业运行
- 从基础表中删除记录(可能归档已删除的记录)
- 考虑每天或每周自动执行一次。
- 重建索引
- 重建表(bcp 数据输出/输入)
- 转储/重新加载数据库(剧烈,但可能会修复损坏)
- 建立新的、更合适的索引
- 运行 DBCC 以查看数据库中是否可能存在损坏
- 锁/死锁
- 确保没有其他进程在数据库中运行
- 特别是 DBCC
- 您使用的是行级还是页级锁定?
- 在开始查询之前以独占方式锁定表
- 检查所有进程是否以相同的顺序访问表
- 确保没有其他进程在数据库中运行
- 是否正确使用了索引?
- 仅当两个表达式的数据类型完全相同时,连接才会使用索引
- 仅当索引上的第一个字段在查询中匹配时才会使用索引
- 是否在适当的地方使用了聚集索引?
- 范围数据
- value1 和 value2 之间的 WHERE 字段
- 小连接是很好的连接
- 默认情况下,优化器一次只考虑 4 个表。
- 这意味着在与超过 4 个表的连接中,它很有可能选择非最佳查询计划
- 分解加入
- 你能打破连接吗?
- 将外键预选到临时表中
- 做一半的连接并将结果放在临时表中
- 您是否使用了正确的临时表?
#temp
@table
表的性能可能比具有大量(数千行)的变量好得多。
- 维护汇总表
- 在基础表上使用触发器构建
- 每天/每小时/等构建。
- 构建即席
- 增量构建或拆卸/重建
- 使用 SET SHOWPLAN ON 查看查询计划是什么
- 使用 SET STATS IO ON 查看实际发生的情况
- 使用 pragma 强制索引: (index: myindex)
- 使用 SET FORCEPLAN ON 强制表顺序
- 参数嗅探:
- 将存储过程分成 2
- 从 proc1 调用 proc2
- 如果 @parameter 已被 proc1 更改,则允许优化器在 proc2 中选择索引
- 你能改进你的硬件吗?
- 你几点跑?有没有更安静的时间?
- Replication Server(或其他不间断进程)是否正在运行?可以暂停吗?运行它,例如。每小时?
- 对在脑海中运行查询的最佳路径有一个很好的了解。
- 检查查询计划 - 总是。
- 打开 STATS,这样您就可以检查 IO 和 CPU 性能。专注于降低这些数字,不一定是查询时间(因为这可能会受到其他活动、缓存等的影响)。
- 寻找进入运算符的大量行,但出来的行数很少。通常,索引会通过限制进入的行数来帮助(这可以节省磁盘读取)。
- 首先关注最大成本子树。更改该子树通常会更改整个查询计划。
- 我见过的常见问题是:
- 如果连接很多,有时 Sql Server 会选择扩展连接,然后应用 WHERE 子句。您通常可以通过将 WHERE 条件移动到 JOIN 子句或内联条件的派生表中来解决此问题。视图可能会导致同样的问题。
- 次优连接(LOOP vs HASH vs MERGE)。我的经验法则是,当与底部相比,顶行的行数很少时使用 LOOP 连接,当集合大致相等且有序时使用 MERGE,其他所有内容使用 HASH。添加连接提示可以让您测试您的理论。
- 参数嗅探。如果您首先使用不切实际的值运行存储过程(例如,为了测试),那么缓存的查询计划对于您的生产值可能不是最佳的。再次运行 WITH RECOMPILE 应该可以验证这一点。对于一些存储过程,尤其是那些处理不同大小范围的过程(例如,今天和昨天之间的所有日期 - 这将需要一个 INDEX SEEK - 或者,去年和今年之间的所有日期 - 使用 INDEX SCAN 会更好) 您可能必须每次都使用 RECOMPILE 运行它。
- 缩进不好...好的,所以 Sql Server 对此没有问题 - 但我确实发现在修复格式之前无法理解查询。
稍微偏离主题,但如果你能控制这些问题……
高水平和高影响力。
- 对于高 IO 环境,请确保您的磁盘用于 RAID 10 或 RAID 0+1 或 RAID 1 和 RAID 0 的某些嵌套实现。
- 不要使用小于 1500K 的驱动器。
- 确保您的磁盘仅用于您的数据库。IE 没有日志记录没有操作系统。
- 关闭自动增长或类似功能。让数据库使用所有预期的存储。不一定是当前使用的。
- 为类型查询设计架构和索引。
- 如果它是一个日志类型表(仅插入)并且必须在数据库中,请不要对其进行索引。
- 如果您正在分配报告(具有许多连接的复杂选择),那么您应该考虑使用星形或雪花模式创建数据仓库。
- 不要害怕复制数据来换取性能!
CREATE INDEX
确保有可用于您的WHERE
andJOIN
子句的索引。这将大大加快数据访问速度。
如果您的环境是数据集市或仓库,那么几乎所有可以想到的查询都应该有大量索引。
在事务环境中,索引的数量应该更少,它们的定义更具战略性,以便索引维护不会拖累资源。(索引维护是指必须更改索引的叶子以反映基础表中的更改,就像INSERT, UPDATE,
和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)
这是因为与颜色选择相比,能够满足价格点的汽车更少,因此查询引擎要分析的数据要少得多。
众所周知,我有两个非常相似的索引,仅在字段顺序上有所不同,以加快查询(名字,姓氏)的速度和(姓氏,名字)的速度。
我最近学到的一个技巧是 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
假设这里是 MySQL,使用 EXPLAIN 来找出查询发生了什么,确保尽可能有效地使用索引并尝试消除文件排序。High Performance MySQL: Optimization, Backups, Replication, and More 和MySQL Performance Blog一样是一本关于这个主题的好书。
@Terrapin isnull 和 coalesce 之间还有一些其他值得一提的区别(除了符合ANSI,这对我来说很重要)。
有时在 SQL Server 中,如果在 where 子句中使用 OR,它确实会提升性能。而不是使用 OR 只是做两个选择并将它们联合在一起。您以 1000 倍的速度获得相同的结果。
查看 where 子句 - 验证索引的使用/验证没有做傻事
where SomeComplicatedFunctionOf(table.Column) = @param --silly
我通常会从连接开始 - 我将一次将它们中的每一个从查询中剔除,然后重新运行查询以了解是否存在我遇到问题的特定连接。
在我所有的临时表上,我喜欢添加唯一约束(在适当的情况下)来创建索引和主键(几乎总是)。
declare @temp table(
RowID int not null identity(1,1) primary key,
SomeUniqueColumn varchar(25) not null,
SomeNotUniqueColumn varchar(50) null,
unique(SomeUniqueColumn)
)
如果可能,将 NOT IN 查询转换为 LEFT OUTER JOINS。例如,如果您想查找 Table1 中未被 Table2 中的外键使用的所有行,您可以这样做:
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
@大卫M
假设这里是 MySQL,使用 EXPLAIN 找出查询发生了什么,确保索引被尽可能有效地使用......
在 SQL Server 中,执行计划为您提供相同的功能 - 它告诉您正在命中哪些索引等。
不一定是 SQL 性能技巧本身,但肯定相关:
一个好主意是尽可能使用 memcached,因为直接从内存中获取预编译数据而不是从数据库中获取它会更快。还有一种内置了 memcached 的 MySQL(第三方)。
确保您的索引长度尽可能小。这允许数据库一次从文件系统中读取更多键,从而加快您的连接。我认为这适用于所有数据库,但我知道这是对 MySQL 的具体建议。
第一步:查看查询执行计划!
TableScan -> 错误的
NestedLoop -> 在 NestedLoop 后面的 meh 警告
TableScan -> DOOM!
设置统计信息 IO 开启
设置统计时间开启
我已经养成了始终使用绑定变量的习惯。如果 RDBMS 不缓存 SQL 语句,绑定变量可能无济于事。但是如果您不使用绑定变量,RDBMS 就没有机会重用查询执行计划和解析的 SQL 语句。节省的费用可能是巨大的:http ://www.akadia.com/services/ora_bind_variables.html 。我主要使用 Oracle,但 Microsoft SQL Server 的工作方式几乎相同。
根据我的经验,如果您不知道您是否使用绑定变量,那么您可能没有。如果您的应用程序语言不支持它们,请找到支持的语言。有时您可以通过对查询 B 使用绑定变量来修复查询 A。
之后,我与我们的 DBA 交谈,以找出导致 RDBMS 最痛苦的原因。请注意,您不应该问“为什么这个查询很慢?” 这就像要求您的医生取出您的阑尾一样。当然,您的查询可能是问题所在,但也有可能出现其他问题。作为开发人员,我们倾向于根据代码行来思考。如果线路很慢,请修复该线路。但是 RDBMS 是一个非常复杂的系统,您的缓慢查询可能是更大问题的征兆。
太多的 SQL 调优技巧是货物崇拜的偶像。大多数情况下,问题与您使用的语法无关或很少相关,因此通常最好使用最简洁的语法。然后,您可以开始寻找调整数据库(而不是查询)的方法。仅在失败时调整语法。
像任何性能调整一样,始终收集有意义的统计数据。不要使用挂钟时间,除非它是您正在调整的用户体验。相反,请查看 CPU 时间、获取的行和从磁盘读取的块等内容。人们经常为错误的事物进行优化。
使用 WITH (NoLock) 运行查询几乎是我的标准操作。任何人在没有它的情况下在数十 GB 的表上运行查询都会被取出并射击。
在 SQL Server 中,使用 nolock 指令。它允许选择命令完成而无需等待——通常是其他事务完成。
SELECT * FROM Orders (nolock) where UserName = 'momma'
SET NOCOUNT ON
通常是我的存储过程中的第一行,除非我真的需要使用@@ROWCOUNT
.
在不需要的地方删除游标。
按您过滤的 clm 索引表
- 使用 dbo 为所有表添加前缀。以防止重新编译。
- 查看查询计划并寻找表/索引扫描。
- 2005 年,对缺失索引的管理视图进行搜索。
我喜欢用
isnull(SomeColThatMayBeNull, '')
超过
coalesce(SomeColThatMayBeNull, '')
当我不需要合并为您提供的多参数支持时。
http://blog.falafel.com/2006/04/05/SQLServerArcanaISNULLVsCOALESCE.aspx
我留意:
- 展开任何 CURSOR 循环并转换为基于集合的 UPDATE / INSERT 语句。
- 寻找任何应用程序代码:
- 调用返回大量记录的 SP,
- 然后在应用程序中,遍历每条记录并调用带有参数的 SP 来更新记录。
- 将其转换为在一个事务中完成所有工作的 SP。
- 任何进行大量字符串操作的 SP。有证据表明数据的结构不正确/标准化。
- 任何重新发明轮子的SP。
- 任何我无法理解它在一分钟内试图做什么的 SP!
删除 Sprocs 中的函数调用,其中很多行将调用该函数。
我的同事使用函数调用(例如从 userid 获取 lastlogindate)来返回非常宽的记录集。
负责优化,我用函数代码替换了存储过程中的函数调用:我将许多存储过程的运行时间从 > 20 秒降低到 < 1。
我总是先去 SQL Profiler(如果它是一个有很多嵌套级别的存储过程)或查询执行计划器(如果它是一些没有嵌套的 SQL 语句)。90% 的情况下,您可以使用这两种工具之一立即发现问题。
set transaction isolation level read uncommitted
Prevents dead locks where transactional integrity isn't absolutely necessary (which is usually true)
不要在存储过程名称前加上“sp_”,因为系统过程都以“sp_”开头,当 SQL Server 被调用时,它必须更加努力地搜索才能找到您的过程。