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

本文探讨了SQLServer中使用OPTION(COMPILE)提高存储过程性能的方法。通过具体示例介绍了如何根据参数变化决定是否启用重新编译以获取最佳查询计划。
原文: SQL Server-聚焦什么时候用OPTION(COMPILE)呢?

前言

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

探讨OPTION(COMPILE)问题

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

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

DECLARE
    @AddressLine1 NVARCHAR(60) = NULL,
    @AddressLine2 NVARCHAR(60) = NULL,
    @City NVARCHAR(30) = NULL,
    @PostalCode NVARCHAR(15) = NULL,
    @StateProvinceID INT = NULL 

SET @City = 'Bothell'
SET @PostalCode = '98011'
SET @StateProvinceID = 79

DECLARE @SQL NVARCHAR(MAX),@ColumnName VARCHAR(4000),@ParamDefinition NVARCHAR(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 (@AddressLine1 IS NOT NULL)
    SET @SQL = @SQL + ' AND a.AddressLine1 LIKE ''%'' + @AddressLine1 + ''%'''

IF (@AddressLine2 IS NOT NULL)
    SET @SQL = @SQL + ' AND a.AddressLine2 LIKE ''%'' + @AddressLine2 + ''%'''

IF (@City IS NOT NULL)
    SET @SQL = @SQL + ' AND a.City LIKE ''%'' + @City + ''%'''

IF (@PostalCode IS NOT NULL)
    SET @SQL = @SQL + ' AND a.PostalCode LIKE ''%'' + @PostalCode + ''%'''

IF (@StateProvinceID IS NOT NULL)
    SET @SQL = @SQL + ' AND a.StateProvinceID = @StateProvinceID' 

SET @ParamDefinition = N'@AddressLine1 NVARCHAR(60),
                         @AddressLine2 NVARCHAR(60),
                         @City NVARCHAR(30),
                         @PostalCode NVARCHAR(15),
                         @StateProvinceID INT'

EXECUTE sp_executesql @SQL,@ParamDefinition,
                           @AddressLine1  = @AddressLine1,
                           @AddressLine2 = @AddressLine2,
                           @City = @City,
                           @PostalCode = @PostalCode,
                           @StateProvinceID = @StateProvinceID

GO

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

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

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

img_5cb56e0a6dc2e97e345d09e93c2b3f6d.png

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

大多数情况下,我们可能不需要担心上述问题。但是,在某些情况下,假设从查询的执行到执行的参数变化很大,则会引起问题。如果我们确定存储过程通常运行正常,但有时运行缓慢,则很可能会看到上述问题。在这种情况下,我们可以做的是改变存储过程,并添加OPTION(RECOMPILE)选项。

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

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

CREATE PROC [dbo].[HighPerformanceExample]
(
    @AddressLine1 NVARCHAR(60) = NULL,
    @AddressLine2 NVARCHAR(60) = NULL,
    @City NVARCHAR(30) = NULL,
    @PostalCode NVARCHAR(15) = NULL,
    @StateProvinceID SMALLINT = NULL 
)
AS 
SET NOCOUNT ON

SELECT a.AddressID, a.AddressLine1, a.AddressLine2, a.City, a.StateProvinceID, a.PostalCode, a.rowguid 
FROM Person.Address AS a
WHERE (a.AddressLine1 = @AddressLine1 OR @AddressLine1 IS NULL) AND
      (a.AddressLine2 = @AddressLine2 OR @AddressLine2 IS NULL) AND
      (a.City = @City OR @City IS NULL) AND
      (a.PostalCode = @PostalCode OR @PostalCode IS NULL) AND
      (a.StateProvinceID = @StateProvinceID OR @StateProvinceID IS NULL)

--或者
SELECT a.AddressID, a.AddressLine1, a.AddressLine2, a.City, a.StateProvinceID, a.PostalCode, a.rowguid 
FROM Person.Address AS a
WHERE a.AddressLine1 = COALESCE(@AddressLine1, a.AddressLine1) AND
      a.AddressLine2 = COALESCE(@AddressLine2, a.AddressLine2) AND
      a.City = COALESCE(@City, a.City) AND
      a.PostalCode = COALESCE(@PostalCode, a.PostalCode) AND
      a.StateProvinceID = COALESCE(@StateProvinceID, a.StateProvinceID) 

--或者
SELECT a.AddressID, a.AddressLine1, a.AddressLine2, a.City, a.StateProvinceID, a.PostalCode, a.rowguid 
FROM Person.Address AS a
WHERE a.AddressLine1 = CASE WHEN @AddressLine1 IS NULL THEN a.AddressLine1 ELSE @AddressLine1 END
 AND  a.AddressLine2 = CASE WHEN @AddressLine2 IS NULL THEN a.AddressLine1 ELSE @AddressLine2 END
 AND  a.City = CASE WHEN @City IS NULL THEN a.City ELSE @City END
 AND  a.PostalCode = CASE WHEN @PostalCode IS NULL THEN a.PostalCode ELSE @PostalCode END
 AND  a.StateProvinceID = CASE WHEN @StateProvinceID IS NULL THEN a.StateProvinceID ELSE @StateProvinceID END 


GO

SET NOCOUNT OFF

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

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

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

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

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

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

CREATE PROC [dbo].[HighPerformanceExample]
(
    @AddressLine1 NVARCHAR(60) = NULL,
    @AddressLine2 NVARCHAR(60) = NULL,
    @City NVARCHAR(30) = NULL,
    @PostalCode NVARCHAR(15) = NULL,
    @StateProvinceID SMALLINT = NULL 
)
AS 
SET NOCOUNT ON

DECLARE @SQL NVARCHAR(MAX),@ColumnName VARCHAR(4000),@ParamDefinition NVARCHAR(500),@Recompile  BIT = 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 (@StateProvinceID IS NOT NULL)
    SET @SQL = @SQL + ' AND a.StateProvinceID = @StateProvinceID' 

IF (@AddressLine1 IS NOT NULL)
    SET @SQL = @SQL + ' AND a.AddressLine1 LIKE @AddressLine1'

IF (@AddressLine2 IS NOT NULL)
    SET @SQL = @SQL + ' AND a.AddressLine2 LIKE @AddressLine2'

IF (@City IS NOT NULL)
    SET @SQL = @SQL + ' AND a.City LIKE @City'

IF (@PostalCode IS NOT NULL)
    SET @SQL = @SQL + ' AND a.PostalCode LIKE @PostalCode'


IF (@StateProvinceID IS NOT NULL)
    SET @Recompile = 0

IF (PATINDEX('%[%_?]%',@AddressLine1) >= 4
        OR PATINDEX('%[%_?]%', @AddressLine2) = 0)
    AND (PATINDEX('%[%_?]%', @City) >= 4
        OR PATINDEX('%[%_?]%', @PostalCode) = 0)
    SET @Recompile = 0

IF @Recompile = 1
BEGIN
    SET @SQL = @SQL + N' OPTION(RECOMPILE)';
END;

SET @ParamDefinition = N'@AddressLine1 NVARCHAR(60),
                         @AddressLine2 NVARCHAR(60),
                         @City NVARCHAR(30),
                         @PostalCode NVARCHAR(15),
                         @StateProvinceID SMALLINT'

EXECUTE sp_executesql @SQL,@ParamDefinition,
                           @AddressLine1  = @AddressLine1,
                           @AddressLine2 = @AddressLine2,
                           @City = @City,
                           @PostalCode = @PostalCode,
                           @StateProvinceID = @StateProvinceID

GO

SET NOCOUNT OFF

总结

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

INFO Starting development server... 98% after emitting CopyPlugin ERROR Failed to compile with 1 error 14:44:09 error in ./src/App.vue?vue&type=template&id=7ba5bd90&scoped=true Syntax Error: Error: Cannot find module 'cache-loader' Require stack: - D:\ruoyi-vue-sqlserver\RuoYi-Vue-SQLServer-C\ruoyi-ui\node_modules\vue-loader\lib\loaders\pitcher.js - D:\ruoyi-vue-sqlserver\RuoYi-Vue-SQLServer-C\ruoyi-ui\node_modules\loader-runner\lib\loadLoader.js - D:\ruoyi-vue-sqlserver\RuoYi-Vue-SQLServer-C\ruoyi-ui\node_modules\loader-runner\lib\LoaderRunner.js - D:\ruoyi-vue-sqlserver\RuoYi-Vue-SQLServer-C\ruoyi-ui\node_modules\webpack\lib\NormalModule.js - D:\ruoyi-vue-sqlserver\RuoYi-Vue-SQLServer-C\ruoyi-ui\node_modules\webpack\lib\NormalModuleFactory.js - D:\ruoyi-vue-sqlserver\RuoYi-Vue-SQLServer-C\ruoyi-ui\node_modules\webpack\lib\Compiler.js - D:\ruoyi-vue-sqlserver\RuoYi-Vue-SQLServer-C\ruoyi-ui\node_modules\webpack\lib\webpack.js - D:\ruoyi-vue-sqlserver\RuoYi-Vue-SQLServer-C\ruoyi-ui\node_modules\compression-webpack-plugin\dist\index.js - D:\ruoyi-vue-sqlserver\RuoYi-Vue-SQLServer-C\ruoyi-ui\node_modules\compression-webpack-plugin\dist\cjs.js - D:\ruoyi-vue-sqlserver\RuoYi-Vue-SQLServer-C\ruoyi-ui\vue.config.js - D:\ruoyi-vue-sqlserver\RuoYi-Vue-SQLServer-C\ruoyi-ui\package.json @ ./src/App.vue 1:0-93 11:2-8 12:2-17 31:4-36:6 31:77-36:5 33:16-22 34:25-40 @ ./src/main.js @ multi (webpack)-dev-server/client?http://172.20.10.2:81&sockPath=/sockjs-node (webpack)/hot/dev-server.js ./src/main.js 还是这个问题
最新发布
03-26
<think>好的,我现在需要解决用户在Vue项目中遇到的编译错误:“Syntax Error: Cannot find module 'cache-loader'”。根据用户提供的引用信息,这个问题通常和模块缺失、node版本兼容性以及路径引用有关。首先,我应该按照引用中的类似问题处理方式,逐步排查可能的原因。 首先,用户提到缺少'cache-loader'模块,这和之前的node-sass问题类似[^1][^3]。通常,这类错误可能是因为模块未正确安装或者权限问题导致安装失败。所以第一步建议用户尝试安装该模块。使用npm或yarn安装cache-loader,并检查是否安装成功。如果安装过程中有权限问题,可能需要使用管理员权限或者sudo命令。 接下来,考虑node版本兼容性问题。引用[3]指出node-sass需要特定版本的nodejs,同理,cache-loader可能也有版本要求。用户需要确认当前node版本是否与cache-loader兼容。可以通过查看cache-loader的官方文档或者npm页面上的引擎要求。如果版本不匹配,用户需要调整node版本,使用nvm管理工具切换版本,确保兼容性。 然后,检查项目中的依赖是否正确安装。有时候,删除node_modules和package-lock.json(或yarn.lock)后重新安装依赖可以解决模块缺失的问题。引用[1]和[2]中提到删除并重新安装模块的方法,可能同样适用于此情况。建议用户执行清理并重新安装的步骤。 另外,路径引用错误也可能导致模块无法找到,如引用[4]中的图片路径问题。虽然这里不是静态资源,但需要确认项目配置中是否正确引用了cache-loader。例如,在webpack配置中是否配置了cache-loader,或者vue.config.js是否有相关设置。如果配置路径有误,可能需要修正路径或者重新配置loader。 还有可能的是缓存问题,有时候旧的缓存可能导致模块载失败。清除npm或yarn的缓存,再重新安装依赖可能会有帮助。用户可以使用npm cache clean --force或者yarn cache clean来清理缓存。 如果以上步骤都无效,可能需要检查项目的配置文件,如package.json中的依赖项是否包含cache-loader,或者是否有拼写错误。另外,查看vue-loader的版本是否与当前Vue项目兼容,可能需要升级或降级vue-loader和相关loader的版本。 最后,建议用户查阅官方文档或社区讨论,寻找类似问题的解决方案。例如,Vue CLI的GitHub issues中可能有其他开发者遇到并解决了相同的问题。同时,确保所有相关依赖如webpack、vue-loader、cache-loader的版本相互兼容,避免版本冲突导致的模块载失败。</think>### 解决Vue编译错误“Cannot find module 'cache-loader'”的步骤 1. **安装`cache-loader`模块** 首先尝试手动安装`cache-loader`模块。在项目根目录下执行以下命令: ```bash npm install cache-loader --save-dev # 或使用yarn yarn add cache-loader -D ``` 如果安装失败,可能是权限问题(如全局安装时),可尝试以管理员权限运行命令[^1]。 2. **检查Node.js版本兼容性** `cache-loader`对Node.js版本可能有特定要求。使用以下命令查看当前Node版本: ```bash node -v ``` 若版本过低或过高,可通过`nvm`切换版本(参考引用[3]): ```bash nvm install 14.17.0 # 安装指定版本 nvm use 14.17.0 # 切换到该版本 ``` 3. **清理并重新安装依赖** 删除`node_modules`和`package-lock.json`(或`yarn.lock`),然后重新安装: ```bash rm -rf node_modules package-lock.json npm install # 或 yarn install ``` 此步骤可解决因依赖损坏或冲突导致的问题[^2]。 4. **验证Webpack/Vue配置** 确认`vue.config.js`或Webpack配置中是否显式引用了`cache-loader`。例如: ```javascript // webpack.config.js module.exports = { module: { rules: [ { test: /\.vue$/, use: ['cache-loader', 'vue-loader'] } ] } } ``` 若配置有误,需调整路径或载顺序。 5. **清除npm/yarn缓存** 若安装时缓存异常,可尝试清理缓存后重试: ```bash npm cache clean --force # 或 yarn cache clean ``` 6. **检查依赖版本兼容性** 确保`vue-loader`、`webpack`与`cache-loader`版本兼容。例如: - `vue-loader@15.x`需要`webpack@4.x` - `cache-loader@4.x`支持Node.js 12+ 在`package.json`中锁定版本(如使用`^`或`~`符号),或参考官方文档的版本匹配建议。 7. **参考社区解决方案** 搜索类似问题(如GitHub Issues、Stack Overflow),例如: - 关键词:“vue-loader cache-loader missing” - 已知案例:升级`@vue/cli-service`可能自动修复依赖问题[^2]。 --- ### 相关问题 1. **如何排查Vue项目中其他模块缺失问题?** - 例如:`Cannot find module 'sass-loader'`的解决方法是否类似?[^1] 2. **Node.js版本管理工具(如nvm)的具体使用场景有哪些?** - 如何通过nvm快速切换不同项目的Node版本? 3. **Webpack载器(如cache-loader)的作用是什么?** - `cache-loader`如何优化构建性能?是否需要与其他载器配合使用? 4. **Vue CLI创建的项目中,如何自定义Webpack配置?** - 修改`vue.config.js`时需要注意哪些兼容性问题?[^4] --- 通过以上步骤,可系统性解决因`cache-loader`缺失导致的编译错误,同时提升对Vue项目依赖管理的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值