MSSQL 参数消耗更多时间

Posted

技术标签:

【中文标题】MSSQL 参数消耗更多时间【英文标题】:MSSQL Parameter consumes more time 【发布时间】:2013-08-29 08:09:22 【问题描述】:

我有一个数据库,其中包含大约 2.500.000 条记录的表。我正在获取大约 150.000 条记录,需要测试 2 个不同的查询。第一个返回 30 秒到 1 分钟之间的结果。但是第二个在 3 到 4 分钟之间做出响应,这很奇怪。唯一的变化是第一个不使用参数,但第二个不使用。我都从 C# 运行。对于安全问题,我想使用带参数的,但我不明白为什么要花这么多时间。任何帮助将不胜感激。

第一个查询:

        DECLARE @page INT=3
        DECLARE @pagesize INT=300
            string sql = "SELECT  Col1,Col2,Col3 FROM 
    (SELECT ROW_NUMBER() OVER(ORDER BY Col1) AS rownumber,Col1,Col2,Col3";
            sql += " FROM my_table WHERE  Col1 LIKE '" + letter + "%')  as somex 
WHERE  rownumber >= (@page-1)*(@pagesize)";
            sql += "AND rownumber <=(@page)*@pagesize; 
SELECT COUNT(*) FROM my_table WHERE col1 LIKE '" + letter + "%'";

第二次查询:

    DECLARE @page INT=3
    DECLARE @pagesize INT=300
    DECLARE @starting VARCHAR(10)='be'
        string sql = "SELECT  Col1,Col2,Col3FROM   
(SELECT ROW_NUMBER() OVER(ORDER BY Col1) AS rownumber,Col1,Col2,Col3";
        sql += " FROM my_table WHERE  Col1 LIKE @letter+'%')  as somex  
WHERE  rownumber >= (@page-1)*(@pagesize)";
        sql += "AND rownumber <=(@page)*@pagesize; SELECT COUNT(*) 
FROM my_table WHERE col1 LIKE @letter+'%'";

我的服务器是 16GB 内存,4 个真正的 4 个虚拟 CPU,Sata 磁盘。

编辑: Col1 是聚集和非聚集索引。

进度:事实证明,这些查询在另一台服务器上运行良好。但这让我更加困惑。会不会是 SQL Server 的一些设置?

【问题讨论】:

听起来像parameter sniffing。 除了 GarethD 之外,您还应该: 1- 确保 col1 上有索引 2- 执行 count(col1) 而不是 count(*) 3- 更新表统计信息 我编辑了这个问题。 col1 上有聚集索引和非聚集索引。我将 count(*) 更改为 count(col1) 但它根本没有影响。 【参考方案1】:

正如我在评论中所说,这听起来像 parameter sniffing,但为了提供帮助,我想我会对此进行扩展。网上有很多文章说 比我更详细地介绍,但参数嗅探的长短是 SQL-Server 已经缓存了一个基于参数值的执行计划,该参数不会产生当前值的最佳执行计划。

假设Col1 有一个非聚集索引,但不包括col2col3 作为非键列然后 SQL-Server 有两个选项,它可以对My_Table 进行聚集索引扫描以获取Col1 LIKE @letter+'%' 上的所有行,或者它可以搜索Col1 上的索引然后对聚集索引进行书签查找以获取价值 对于索引返回的每一行。我不太记得 SQL-Server 在什么时候根据估计的行数在两者之间切换,它的百分比很低,所以我很确定如果你返回 150,000 条记录 在 2,500,000 中,优化器将进行聚集索引扫描。但是,如果您只返回几百行,那么最好使用书签查找。

当您不使用参数时,SQL-Server 将在每次执行时创建一个新的执行计划,并为该参数生成最佳执行计划(假设您的统计信息是最新的),当您使用参数时他们第一次运行查询时 sql-server 创建一个计划 基于该特定参数值,并存储该计划以供以后使用。每次后续运行查询时,sql-server 都会识别出该查询是相同的,因此不会重新编译它。这意味着虽然如果第一次运行查询,它是 返回低行数的参数,则将存储书签查找计划。然后,如果下次运行查询时,它会传递一个返回大量行的值,其中最佳计划是聚集索引扫描,那么查询仍然使用次优书签查找执行,并且 将导致更长的执行时间。这当然也可以反过来成立。有很多方法可以绕过参数嗅探,但是由于您的查询不是很复杂,因此编译时间不会很重要,尤其是与您说此查询花费的 30 秒相比 以最好的状态运行,所以我会使用 OPTION RECOMPILE Query hint:

SELECT  Col1, Col2, Col3
FROM    (   SELECT  ROW_NUMBER() OVER(ORDER BY Col1) AS rownumber,Col1,Col2,Col3
            FROM    my_table 
            WHERE   Col1 LIKE @letter+'%'
        )  as somex  
WHERE   rownumber >= (@page-1)*(@pagesize)
AND     rownumber <= (@page) * @pagesize
OPTION (RECOMPILE); 

SELECT  COUNT(*) 
FROM    my_table 
WHERE   Col1 LIKE @letter+'%'
OPTION (RECOMPILE);

当您在新服务器上尝试执行此操作时,它执行良好的原因是它第一次在新服务器上运行时必须编译参数化查询,并且生成的计划适合提供的参数值.

最后一点,如果您使用的是 SQL_Server 2012,那么您可以使用 OFFSET/FETCH 进行分页:

SELECT  Col1, Col2, Col3
FROM    My_table
WHERE   Col1 LIKE @letter+'%'
ORDER BY Col1 OFFSET (@page-1) * (@pagesize) ROWS FETCH NEXT @pagesize ROWS ONLY;

【讨论】:

感谢您的回答并花时间解释:) 但问题不在于索引。问题是,在C#中,当我们直接在sql命令中声明参数时,它比使用command.AddWithValue(...)添加参数时更快。 你误会了,我不是说索引有问题。当您使用参数 SQL-Server 正在重用缓存的执行计划时,这可能不是您所传递参数的实际值的最佳执行计划。如果不使用该参数,则 SQL-Server 无法使用缓存计划,因为它每次都是新查询。这就是您看到差异的原因。 在another answer 我已经完成了参数嗅探我已经发布了示例查询,您可以测试并查看相同查询的不同执行计划。即使您没有使用存储过程,同样的原则也适用。您从 c# 调用查询的事实无关紧要。

以上是关于MSSQL 参数消耗更多时间的主要内容,如果未能解决你的问题,请参考以下文章

为啥使用超过 2 个线程会消耗更多时间?

为啥布尔值比字符消耗更多的内存?

创建函数是不是消耗更多内存

为啥 STRAIGHT_JOIN 会消耗更多 CPU?

如果分配更多内核,单个 Spark 任务会在计算上消耗更多时间

如何分析java,eclipse,junit中哪个方法消耗的时间更多? [复制]