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

说说SQL Server里那些Hints,怎么用其实挺有讲究的

主要参考自IT技术社区博客园中名为“桦仔”的博主发表的系列文章,以及微软官方MSDN文档中关于查询提示(Query Hints)和表提示(Table Hints)的章节)

在我们平时使用SQL Server数据库的时候,可能会遇到一种情况:你写了一个查询语句,自己觉得已经挺完美的了,但是运行起来就是特别慢,你可能会去请教更有经验的同事或者DBA(数据库管理员),他们有时候会告诉你:“试试加个Hint吧。” 这个“Hint”,中文翻译过来就是“提示”,但它不是给我们程序员看的提示,而是给SQL Server数据库引擎本身的一个“建议”或者说是“指令”。

为什么需要给数据库引擎下指令呢?这得从SQL Server的核心组件——查询优化器说起,当你提交一个SQL查询时,优化器会像一位非常聪明的“参谋长”,它要分析你的查询意图,看看涉及哪些表,表里有多少数据,有哪些索引可以用,然后它会想出来好几种甚至几十种不同的执行方案(也就是执行计划),并从中估算出一个它认为成本最低、速度最快的方案来执行,绝大多数情况下,这位“参谋长”都非常靠谱,它能选出最优解。

智者千虑,必有一失,优化器依赖的是数据库定期收集的统计信息来估算成本,如果统计信息过时了,或者查询特别复杂,优化器就可能“判断失误”,选择一个实际上很糟糕的执行计划,本该用索引快速查找一小部分数据,它却选择了全表扫描,导致性能急剧下降,这时候,Hints就派上用场了,我们可以通过Hints来“纠正”或者“引导”优化器,告诉它:“别用那个方案,用这个!”

SQL Server里的Hints种类不少,我们可以把它们分成几大类来看,每一类的用法和讲究都不同。

第一大类是表提示(Table Hints),这类提示是紧跟在SQL语句中的表名后面的,用WITH关键字括起来,最常见的表提示之一就是WITH (NOLOCK),这个提示可能很多人都用过,它的作用是告诉数据库:“我读这个表的时候,不要加锁,也别管别人有没有在修改数据,直接读就行。” 这听起来很美好,因为它可以减少等待锁的时间,提高查询速度,尤其是在一些对数据实时性要求不高的报表查询场景,它的“讲究”就在于副作用很大,因为不加锁,你可能会读到别人正在修改但还没提交的数据,也就是“脏读”,你可能会读到一些根本不存在的“幽灵数据”,或者读到的数据顺序是乱的,这是一个“危险”的提示,不能滥用,只有在你能明确接受数据暂时不一致的场景下才能谨慎使用。

另一个常用的表提示是WITH (INDEX),当优化器没有选择你认为最合适的那个索引时,你可以用这个提示强制它使用指定的索引,你有一个专门为某个查询建立的高效索引,但优化器偏偏不用,这时强制一下可能效果立竿见影,但这里的“讲究”是,数据是动态变化的,今天这个索引是最优的,明天数据量变了,可能就不是了,如果你用Hint把它固定死,就失去了优化器根据数据变化自动调整的灵活性,将来可能反而成为性能瓶颈。

第二大类是查询提示(Query Hints),这类提示是通过在SQL语句末尾加上OPTION子句来指定的,它影响的是整个查询的执行方式,这里面有几个非常有名的提示。

比如OPTION (MAXDOP 1),DOP是“并行度”的意思,SQL Server为了加快大查询的速度,可能会动用多个CPU核心来同时处理一个任务,但这不总是好事,因为并行操作本身也有开销,而且在系统繁忙时,过多的并行查询会耗尽CPU资源,导致大家都很慢,用MAXDOP 1就是强制这个查询只用一个CPU核心来“单线程”执行,这反而能让查询更稳定、更快,尤其是对于那些本来就不大、并行反而添乱的查询,这里的“讲究”在于,你需要判断什么时候该用,不能一刀切地给所有查询都加上。

再比如OPTION (RECOMPILE),这个提示非常强大,它告诉优化器:“不要用缓存里旧的执行计划,就根据当前的数据分布和传入的参数值,现场给我编译一个新的、最贴切的计划。” 这对于那些参数值波动很大(比如有时查热门数据,有时查冷门数据)的查询特别有效,可以避免“参数嗅探”问题(即优化器用了第一次编译计划时的参数值来代表所有后续调用,导致计划不优),但“讲究”在于,每次执行都要重新编译计划,这会增加CPU的负担,如果是一个每秒执行成千上万次的高频查询,用这个提示可能会把CPU压垮,所以它是一把双刃剑,适用于执行频率不高、但每次执行都要求最优计划的场景。

还有像OPTION (MERGE JOIN)OPTION (HASH JOIN)OPTION (LOOP JOIN) 这样的连接提示,用来强制优化器使用特定的表连接算法,通常优化器自己选得挺好,但在某些特殊情况下,你可能明确知道某种连接方式更适合你的数据,就可以用这个来强制。

第三大类是连接提示(Join Hints),比如在写JOIN关键字时直接写 INNER LOOP JOIN,它也是强制连接算法的一种方式,和上面在OPTION里指定的效果类似。

说了这么多Hints的用法,那最核心的“讲究”到底是什么呢?其实就是一句话:Hints是最后的手段,而非首选工具。

你应该把使用Hints看作是一种“外科手术式”的精准干预,而不是“大力丸”,在考虑使用Hint之前,首先应该检查的是:

  1. 你的SQL语句本身写得是否最优?有没有不必要的子查询?连接条件是否准确?
  2. 数据库的统计信息是不是最新的?过时的统计信息是优化器犯错的首要原因,试试更新统计信息可能问题就解决了。
  3. 相关的索引设计是否合理?缺少索引往往比优化器选错索引更常见。

滥用Hints会带来很多长期问题:它让SQL代码变得僵硬,剥夺了优化器的自我优化能力;当数据库升级、数据结构变化、数据量剧增或骤减时,原来有效的Hint可能会瞬间变成性能杀手,而且因为代码里写死了,排查起来还特别困难。

一位优秀的开发者或DBA,会把Hints当作工具箱里一件强大但危险的工具,只在经过充分测试和论证,确认是优化器本身的问题,且没有其他更优解时,才谨慎地、有明确文档记录地使用它,这才是对待SQL Server Hints真正有讲究的态度。

说说SQL Server里那些Hints,怎么用其实挺有讲究的