SQL Server-聚焦什么时候用OPTION(COMPILE)呢?

摘要: 上一篇我们探讨了在静态语句中使用 WHERE Column=@Param OR @Param IS NULL的问题,有对OPTION(COMPILE)的评论,那这节我们来探讨OPTION(COMPILE)的问题。

上一篇我们探讨了在静态语句中使用

WHERE Column=@Param OR @Param IS NULL的问题,有对OPTION(COMPILE)的评论,那这节我们来探讨OPTION(COMPILE)的问题。

探讨OPTION(COMPILE)问题

在SQL SERVER中任何时候第一次调用存储过程时,此时存储过程将会被SQL SERVER优化且查询计划在内存中会被缓存。由于查询计划缓存,当运行相同的存储过程时,它都将使用相同的查询计划,从而无需每次运行时对同一存储过程进行优化和编译。因此,如果我们需要每天运行相同的存储过程若干次,那么可以节省大量的时间和硬件资源。

如果每次运行的存储过程中的在WHERE子句中具有相同的参数,则重复使用存储过程的相同查询计划是有意义的。但是,如果运行相同的存储过程,但是参数的值会改变呢?发生什么取决于参数的典型性。如果存储过程的参数的值从执行到执行相似,那么缓存的查询计划将正常工作,查询将按照执行最佳来。但是,如果参数不是典型的,那么被重用的缓存查询计划可能不是最优的,导致查询运行更慢,因为它使用的查询计划并不是真正为所使用的参数设计的。下面我们借助AdventureWorks2012实例数据库来用实例讲解上述所描述的情况。

DECLARE@AddressLine1NVARCHAR(60) =NULL,    @AddressLine2NVARCHAR(60) =NULL,    @CityNVARCHAR(30) =NULL,    @PostalCodeNVARCHAR(15) =NULL,    @StateProvinceIDINT=NULLSET@City ='Bothell'SET@PostalCode ='98011'SET@StateProvinceID =79DECLARE@SQLNVARCHAR(MAX),@ColumnNameVARCHAR(4000),@ParamDefinitionNVARCHAR(500)SET@ColumnName ='a.AddressID, a.AddressLine1, a.AddressLine2, a.City, a.StateProvinceID, a.PostalCode, a.rowguid'SET@SQL='SELECT '+ @ColumnName +' FROM Person.Address AS a WHERE 1 = 1'IF(@AddressLine1ISNOTNULL)SET@SQL= @SQL+' AND a.AddressLine1 LIKE ''%'' + @AddressLine1 + ''%'''IF(@AddressLine2ISNOTNULL)SET@SQL= @SQL+' AND a.AddressLine2 LIKE ''%'' + @AddressLine2 + ''%'''IF(@CityISNOTNULL)SET@SQL= @SQL+' AND a.City LIKE ''%'' + @City + ''%'''IF(@PostalCodeISNOTNULL)SET@SQL= @SQL+' AND a.PostalCode LIKE ''%'' + @PostalCode + ''%'''IF(@StateProvinceIDISNOTNULL)SET@SQL= @SQL+' AND a.StateProvinceID = @StateProvinceID'SET@ParamDefinition = N'@AddressLine1 NVARCHAR(60),

                        @AddressLine2 NVARCHAR(60),

                        @City NVARCHAR(30),

                        @PostalCode NVARCHAR(15),

                        @StateProvinceID INT'EXECUTEsp_executesql @SQL,@ParamDefinition,                          @AddressLine1  = @AddressLine1,                          @AddressLine2 = @AddressLine2,                          @City = @City,                          @PostalCode = @PostalCode,                          @StateProvinceID = @StateProvinceIDGO

我们运行上述查询1次,看到查询结果如下和计划缓存次数如下:

此时我们将外部变量StateProvinceID类型修改为SMALLINT,然后再来运行查询和缓存计划,此时会出现查询计划使用次数是为2,还是出现两条次数都为1呢?

此时我们再来将动态SQL中内部变量StateProvinceID类型修改为SMALLINT,此时会出现查询计划使用次数是为3,还是出现两条,次数分别为2和1呢?

由上可知,如果我们修改外部变量参数类型不会影响查询计划缓存即会达到重用目的,若修改动态SQL内部变量参数类型则不会重用查询计划缓存。

大多数情况下,我们可能不需要担心上述问题。但是,在某些情况下,假设从查询的执行到执行的参数变化很大,则会引起问题。

如果我们确定存储过程通常运行正常,但有时运行缓慢,则很可能会看到上述问题。在这种情况下,我们可以做的是改变存储过程,并添加WITH RECOMPILE选项。

添加此选项后,存储过程将始终重新编译自身,并在每次运行时创建一个新的查询计划。当然这会消除查询计划重用的好处,但确保了每次运行查询时都使用正确的查询计划。如果存储过程中有多个查询,那么它将重新编译存储过程中的所有查询,即使那些不受非典型参数影响的查询也是如此。

讲完OPTION(COMPILE),接下来我们讲讲如何创建高性能的存储过程。有些童鞋可能会创建如下存储过程。

CREATEPROC [dbo].[HighPerformanceExample](    @AddressLine1NVARCHAR(60) =NULL,    @AddressLine2NVARCHAR(60) =NULL,    @CityNVARCHAR(30) =NULL,    @PostalCodeNVARCHAR(15) =NULL,    @StateProvinceIDSMALLINT=NULL)ASSETNOCOUNTONSELECTa.AddressID, a.AddressLine1, a.AddressLine2, a.City, a.StateProvinceID, a.PostalCode, a.rowguidFROMPerson.AddressASaWHERE(a.AddressLine1 = @AddressLine1OR@AddressLine1ISNULL)AND(a.AddressLine2 = @AddressLine2OR@AddressLine2ISNULL)AND(a.City = @CityOR@CityISNULL)AND(a.PostalCode = @PostalCodeOR@PostalCodeISNULL)AND(a.StateProvinceID = @StateProvinceIDOR@StateProvinceIDISNULL)--或者SELECTa.AddressID, a.AddressLine1, a.AddressLine2, a.City, a.StateProvinceID, a.PostalCode, a.rowguidFROMPerson.AddressASaWHEREa.AddressLine1 =COALESCE(@AddressLine1, a.AddressLine1)ANDa.AddressLine2 =COALESCE(@AddressLine2, a.AddressLine2)ANDa.City =COALESCE(@City, a.City)ANDa.PostalCode =COALESCE(@PostalCode, a.PostalCode)ANDa.StateProvinceID =COALESCE(@StateProvinceID, a.StateProvinceID)--或者SELECTa.AddressID, a.AddressLine1, a.AddressLine2, a.City, a.StateProvinceID, a.PostalCode, a.rowguidFROMPerson.AddressASaWHEREa.AddressLine1 =CASEWHEN@AddressLine1ISNULLTHENa.AddressLine1ELSE@AddressLine1ENDANDa.AddressLine2 =CASEWHEN@AddressLine2ISNULLTHENa.AddressLine1ELSE@AddressLine2ENDANDa.City =CASEWHEN@CityISNULLTHENa.CityELSE@CityENDANDa.PostalCode =CASEWHEN@PostalCodeISNULLTHENa.PostalCodeELSE@PostalCodeENDANDa.StateProvinceID =CASEWHEN@StateProvinceIDISNULLTHENa.StateProvinceIDELSE@StateProvinceIDENDGOSETNOCOUNTOFF

上述无论怎样执行都将表现的非常糟糕。因为SQL SERVER不能将其很好地进行优化,如果这是由不同的参数组合产生,那么我们可能会得到一个绝对糟糕的计划。不难理解,当执行一个存储过程,并且还没有生成一个查询缓存计划。所以,管理员可能会更新统计信息或强制重新编译(或者,甚至重新启动SQL Server)来尝试解决此问题,但这些都不是最佳解决方案。OPTION(COMPILE)重新编译是个好东西,但是我们是不是像如下简单加上重新编译就可以了呢。

SELECT...FROM...WHERE...OPTION(RECOMPILE);

如果我们要使用重新编译,那么我们是否需要考虑以下两个问题呢?

如果我们知道一个特定的语句总是返回相同数量的行并使用相同的计划(并且我们已测试过并知道这一点),那么我们会正常创建存储过程并让计划得到缓存。

如果我们知道一个特定的语句从执行到执行是不一样的,最佳查询计划也会有所不同(我们也应该从执行多个测试样本中知道这一点),然后我们会如正常一样创建存储过程,然后使用OPTION(RECOMPILE)以确保语句的计划不会被存储过程缓存或保存。在每次执行时,存储过程将获得不同的参数,如此一来语句将在每次执行时得到一个新的计划。

上述已经描述的很明朗了,使用或者不使用重新编译的前提不过是需不需要查询计划缓存还是重新生成一个查询计划,但是我们怎么知道到底是否需要查询计划缓存呢?这就要看对创建的存储过程是否稳定了,如果稳定我们就从缓存中去取,否则使用重新编译查询。归根结底一句话概述:重新编译不稳定(可变)的计划,但为稳定(不可变)的计划,我们把它们放在缓存中重用。

为了实现这点,我们需要分析所查询的存储过程,例如在每个企业下有对应的用户,我们想象一下所呈现的UI界面,首先是所有用户,查询条件则是企业下拉框,然后是用户名或者员工工号等。当没有任何筛选条件时则走查询计划缓存,若选择企业,或者还选择了员工相关筛选条件则重新编译。类似如下存储过程。

CREATEPROC [dbo].[HighPerformanceExample](    @AddressLine1NVARCHAR(60) =NULL,    @AddressLine2NVARCHAR(60) =NULL,    @CityNVARCHAR(30) =NULL,    @PostalCodeNVARCHAR(15) =NULL,    @StateProvinceIDSMALLINT=NULL)ASSETNOCOUNTONDECLARE@SQLNVARCHAR(MAX),@ColumnNameVARCHAR(4000),@ParamDefinitionNVARCHAR(500),@RecompileBIT=1;SET@ColumnName ='a.AddressID, a.AddressLine1, a.AddressLine2, a.City, a.StateProvinceID, a.PostalCode, a.rowguid'SET@SQL='SELECT '+ @ColumnName +' FROM Person.Address AS a WHERE 1 = 1'IF(@StateProvinceIDISNOTNULL)SET@SQL= @SQL+' AND a.StateProvinceID = @StateProvinceID'IF(@AddressLine1ISNOTNULL)SET@SQL= @SQL+' AND a.AddressLine1 LIKE @AddressLine1'IF(@AddressLine2ISNOTNULL)SET@SQL= @SQL+' AND a.AddressLine2 LIKE @AddressLine2'IF(@CityISNOTNULL)SET@SQL= @SQL+' AND a.City LIKE @City'IF(@PostalCodeISNOTNULL)SET@SQL= @SQL+' AND a.PostalCode LIKE @PostalCode'IF(@StateProvinceIDISNOTNULL)SET@Recompile =0IF(PATINDEX('%[%_?]%',@AddressLine1) >=4ORPATINDEX('%[%_?]%', @AddressLine2) =0)AND(PATINDEX('%[%_?]%', @City) >=4ORPATINDEX('%[%_?]%', @PostalCode) =0)SET@Recompile =0IF@Recompile =1BEGINSET@SQL= @SQL+ N' OPTION(RECOMPILE)';END;SET@ParamDefinition = N'@AddressLine1 NVARCHAR(60),

                        @AddressLine2 NVARCHAR(60),

                        @City NVARCHAR(30),

                        @PostalCode NVARCHAR(15),

                        @StateProvinceID SMALLINT'EXECUTEsp_executesql @SQL,@ParamDefinition,                          @AddressLine1  = @AddressLine1,                          @AddressLine2 = @AddressLine2,                          @City = @City,                          @PostalCode = @PostalCode,                          @StateProvinceID = @StateProvinceIDGOSETNOCOUNTOFF

本节我们讲解了如何在存储过程中使用OPTION(COMPILE),并且使得存储过程性能达到最佳,我想这是根据实际场景分析存储过程相对来说首选和最佳的方法,以至于我们不必每次都重新编译。从而给我们长期更好的可扩展性。

版权声明:本文内容由互联网用户自发贡献,版权归作者所有,本社区不拥有所有权,也不承担相关法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:yqgroup@service.aliyun.com 进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。

原文链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容