4

我正在构建一个复杂的查询以在 Web 视图中显示一些统计结果。根据用户的选择,视图可以有几个不同的过滤器。此外,还可以使用通配符。

我正在使用 SqlParameters 在 c# 中以编程方式构建此查询。所以查询看起来像这样:

sc.CommandText = "SELECT * FROM table 
                  WHERE field1 = @filter1 
                  AND field2 LIKE @filter2"; //...and more parameters

sc.SqlParameters.Add(
   new SqlParameter("@filter1", SqlDbType.Int, 32) { Value = 1});

sc.SqlParameters.Add(
   new SqlParameter("@filter2", SqlDbType.VarChar, 446) { Value = "whatever%"});

这是一个非常简化的版本,但查询本身并不是重点。请记住,它可以有不同的可选参数(我认为这是很常见的情况)。

当我在 Sql Manager 中运行此查询时,我意识到使用参数时速度非常慢。因此,以下两个查询应该是相同的,它们使用不同的执行计划,这使得参数化的查询运行速度慢很多:

DECLARE @filter1 INT
DECLARE @filter2 VARCHAR 446
SET @filter1 = 1
SET @filter2 = "whatever%"

SELECT * FROM table WHERE field1 = @filter1 AND field2 LIKE @filter2

快速版本:

SELECT * FROM table WHERE field1 = 1 AND field2 LIKE 'whatever%'

这是另一个有同样问题的人的例子:

为什么参数化查询产生比非参数化查询慢得多的查询计划

似乎有一种叫做参数嗅探的东西,它可能会使参数化查询运行得更慢,但它不适用于我的情况,因为这不是存储过程。

提出的解决方案之一是使用 OPTION(RECOMPILE) 或 OPTION(OPTIMIZE FOR)。我不能这样做,因为我有大约 10 个可选参数,它们可能在过滤器中或不在过滤器中,并且在使用LIKE.

所以,我觉得我陷入了死胡同,我正在考虑摆脱参数并在代码上构建动态文字查询。但随后 Sql Injection 出现在游戏中。

那么,您对如何解决此问题还有其他建议吗?或者你知道一种安全的方法来逃避参数吗?

编辑:在这里,您可以使用一个参数查看查询的执行计划LIKE

编辑:更简化的代表性查询执行计划:

4

2 回答 2

2

查看执行计划中的“估计行数”属性。使用您的慢版本(带参数),SQL Server 无法对查询将返回的行进行良好估计,因为它不会在编译时评估变量的实际值。它只会利用统计信息来估计您用作过滤器的那些字段的基数,并根据它创建一个执行计划。

我对此类问题的解决方案是创建一个存储过程,其参数与您想要的过滤器一样多:

CREATE PROCEDURE your_sp @filter1 INT, @filter2 VARCHAR(446) AS
 SELECT * FROM table 
 WHERE field1 = @filter1 
 AND field2 LIKE @filter2

sc.CommandText = "your_sp";
sc.CommandType = CommandType.StoredProcedure;

sc.SqlParameters.Add(new SqlParameter("@filter1", SqlDbType.Int, 32) { Value = 1});

sc.SqlParameters.Add(new SqlParameter("@filter2", SqlDbType.VarChar, 446) { Value = "whatever%"});

connection.Open();
SqlDataReader reader = command.ExecuteReader();
于 2011-01-21T16:50:53.117 回答
1

有时需要过滤索引的查询可能会导致问题。

我刚刚遇到了类似的查询

orders.Where(x => x.Cancelled == options.isCancelled)

options.isCancelled动态布尔值在哪里。这在使用 EFCore 的 SQL 查询中被参数化为类似SELECT ... FROM Orders WHERE cancelled = @param_cancelled. 数据库不能使用过滤索引,因为它事先不知道该值是什么。

我的解决方案是:

if (options.isCancelled) 
{
   orders = orders.Where(o => o.Cancelled == true);
}
else 
{
   orders = orders.Where(o => o.Cancelled == false);
}

这使查询优化器能够使用我创建的过滤索引:

`(WHERE IsCancelled = 1)`. 

这个指标大大提高了性能。

TBH 一些可疑的事情仍在继续,因为即使没有索引,它在 SSMS 中也能正常工作,但在 C# 中完全超时。这个技巧强制执行两个不同的不同查询,SQL Server 需要为其找到两个独立的查询计划。所以这至少让我有信心继续前进,即使我仍然不能 100% 确定发生了什么。

于 2021-02-24T00:14:26.967 回答