当前位置:首页 > 问答 > 正文

聊聊SQL Server里执行SQL时那些性能调优的小细节和过程

聊聊SQL Server里执行SQL时那些性能调优的小细节和过程 整理自SQL Server官方文档、Brent Ozar博客、Redgate SQL Server Central社区讨论等来源)

咱们直接开门见山,当你的SQL Server数据库变慢,一个查询跑起来没完没了的时候,别急着加内存换CPU,很多时候问题就出在那条SQL语句本身和它执行的方式上,调优的过程,有点像侦探破案,得一步步找线索,下面我就聊聊这个过程和一些容易被忽略的小细节。

第一步:别猜,先看“病历”——使用执行计划

(来源:SQL Server官方文档)

当你说“这个查询慢”,第一件事不是去改代码,而是先搞清楚它到底在干什么,SQL Server提供了一个非常强大的工具,叫“执行计划”,你可以在SQL Server Management Studio (SSMS) 里,在要跑的SQL语句前加上 SET STATISTICS PROFILE ON,然后执行,或者直接点击工具栏那个“显示估计的执行计划”按钮(那个带箭头的图表)。

这个计划图看起来可能有点复杂,但你不用怕,先找最扎眼的东西:最贵的那个步骤,执行计划里每个步骤都会有一个“开销百分比”,你的目标就是找到那个占了绝大部分开销(比如80%、90%)的操作,常见的“罪魁祸首”有:

  • 表扫描/聚集索引扫描:这就像你要在一本没有目录的厚书里找一句话,得从头翻到尾,这说明你的表可能缺少合适的索引。
  • 键查找/ RID查找:这通常发生在索引查找之后,好比你先通过目录找到了章节页码(索引查找),但需要的那行具体内容不在目录里,你还得翻到具体页面去读(键查找),如果这种操作很多,说明你的索引可能“覆盖”的列不够全。
  • 排序:如果看到 Sort 操作开销很大,说明数据库正在内存或硬盘上对大量数据进行排序,这非常耗资源。

小细节1:留意执行计划里的“实际行数 vs 估计行数”

(来源:Brent Ozar博客)

这是个超级重要的细节!在执行计划里,每个操作符旁边会显示两个数字:“估计行数”和“实际行数”,如果这两个数相差巨大(比如估计100行,实际返回100万行),这就是一个强烈的危险信号,它意味着SQL Server的查询优化器基于错误的信息制定了一个糟糕的“作战计划”,为什么信息会错?通常是因为表上的统计信息过时了,统计信息就像是数据库的“数据分布调查报告”,报告不准,优化器自然就会迷路,解决办法很简单,定期或在对表做大量修改后,更新统计信息:UPDATE STATISTICS 你的表名

聊聊SQL Server里执行SQL时那些性能调优的小细节和过程

第二步:对症下药——从索引入手

找到了最耗时的操作,接下来就是解决它,十有八九,问题出在索引上。

  • 对付表扫描:考虑为WHERE子句、JOIN条件里的列创建索引。SELECT * FROM Orders WHERE CustomerID = 123,就该在 CustomerID 上建个索引。
  • 对付键查找:考虑创建“覆盖索引”,意思是,让你的索引不仅包含查找条件的列,还包含查询中需要返回的所有列,比如你的查询是 SELECT OrderID, OrderDate FROM Orders WHERE CustomerID = 123,那么创建一个在 CustomerID 上,并包含 OrderID, OrderDate 的索引,数据库只需要访问索引就能拿到所有数据,就不用再费力地去查主表了,速度会快很多。

小细节2:索引不是越多越好

(来源:Redgate SQL Server Central社区常见误区讨论)

这是个经典的误区,每个索引在加速查询的同时,也会拖慢数据的写入(INSERT/UPDATE/DELETE)速度,因为每次写数据时,数据库都要去更新所有相关的索引,索引要像调料一样,恰到好处才行,只创建那些对关键查询真正有用的索引,并定期清理那些很少用到的“僵尸索引”。

聊聊SQL Server里执行SQL时那些性能调优的小细节和过程

第三步:审视SQL语句本身——写法有讲究

问题不在于数据库,而在于我们写的SQL。

  • **避免 SELECT ***:* 老老实实地写出你需要的列名,`SELECT ` 会返回所有列,很可能导致无法使用覆盖索引,并且网络传输的数据量也更大。
  • 小心使用函数和计算:WHERE 子句里对列使用函数,会让索引失效。WHERE YEAR(OrderDate) = 2023,即使 OrderDate 有索引也用不上,应该写成 WHERE OrderDate >= '2023-01-01' AND OrderDate < '2024-01-01'
  • 注意连接(JOIN)的顺序: 虽然现代SQL Server的优化器通常会帮你优化连接顺序,但如果连接的表很多很复杂,有时候明确地先连接数据量小的表,或者过滤掉最多数据的表,可能会给优化器更好的提示。

小细节3:参数嗅探问题

(来源:各类SQL Server性能故障排查案例)

这是一个高级但常见的问题,当存储过程第一次被执行时,SQL Server会根据传入的参数值来生成一个执行计划并缓存起来,如果第一次传入的参数很特殊(比如返回了100万行数据),生成了一个适合大数据量的计划(比如全表扫描),但后续绝大多数调用参数都是只返回几行数据,这时用那个“大数据量”计划就非常低效了,反之亦然,解决这个问题的方法有很多,比如使用 OPTION (RECOMPILE) 让存储过程每次重新编译(适合每次参数差异都很大的情况),或者使用 OPTION (OPTIMIZE FOR...) 来指定一个更通用的参数值。

总结一下过程:

  1. 发现问题:哪个查询慢?
  2. 诊断病因:查看执行计划,找到开销最大的操作,并检查估计行数和实际行数是否匹配。
  3. 开具处方
    • 如果是索引问题(扫描、查找),就增删改索引。
    • 如果是统计信息过时,就更新统计信息。
    • 如果是SQL写法问题,就重写SQL。
    • 如果是参数嗅探,就考虑使用查询提示。
  4. 验证疗效:修改之后,再次运行查询并查看新的执行计划,确认性能问题是否解决。

性能调优是个持续的过程,需要耐心和细致的观察,从这些小事和细节入手,往往能用最小的代价解决最大的问题。