SQLSRV 参数化查询永远在 GROUP BY 子句中

SQLSRV Parametrized query taking forever in a GROUP BY clause

我将 SQLSRV 4.3 PDO 与 PHP 7.1 一起使用。我已经在 PHP 5.6 的 SQLSRV 3.2 上对其进行了测试。所以,我有以下查询:

$sql = "select [ID], [Year], [Month], sum(Value) as Value, 
MIN(FirstDateTime) as FirstDateTime, MAX(SecondDateTime) as 
SecondDateTime from [ValuesTable] where [FirstDateTime] >= ? and 
[SecondDateTime] <= ? and [Year] in (?) GROUP BY [ID], 
[Year], [Month] order by [ID] offset 0 rows fetch next 30 rows only";

及其参数:

$param1 = '2017-10-01 00:00:00';
$param2 = '2017-10-31 23:59:59';
$param3 = '2017';

当我使用以下代码执行查询时:

$result = $db->prepare($sql)
$result->bindParam(1, $param1);
$result->bindParam(2, $param2);
$result->bindParam(3, $param3);
$result->execute();

需要很长时间才能完成。相反,如果我执行以下不安全的查询:

$sql = "select [ID], [Year], [Month], sum(Value) as Value, 
MIN(FirstDateTime) as FirstDateTime, MAX(SecondDateTime) as 
SecondDateTime from [ValuesTable] where [FirstDateTime] >= '2017-10-01 00:00:00' and 
[SecondDateTime] <= '2017-10-31 23:59:59' and [Year] in (2017) GROUP BY [ID], 
[Year], [Month] order by [ID] offset 0 rows fetch next 30 rows only";

很快就可以完成。 我已经测试了很多并研究了很多,但我找不到问题的答案。

我已经为 PDO 设置了属性,例如

$db->setAttribute( \PDO::SQLSRV_ATTR_ENCODING, \PDO::SQLSRV_ENCODING_SYSTEM );
$db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);

此外,我将参数的类型放入 bindParam() 函数中,如

$result->bindParam(1, $param1, PDO::PARAM_STR);
$result->bindParam(2, $param2, PDO::PARAM_STR);
$result->bindParam(3, $param3, PDO::PARAM_INT);

我也修改了游标类型,代码如下

$result = $db->prepare($sql, array(
   \PDO::ATTR_CURSOR => \PDO::CURSOR_SCROLL, 
   \PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => \PDO::SQLSRV_CURSOR_BUFFERED
   )
);

参数化查询仍在持续,没有任何改变。 此外,进行以下测试只是为了防止任何 sql 服务器查询内存缓存或其他内容:

  1. 重新启动Sql服务器
  2. 执行了参数化查询,它花了很长时间才完成
  3. 重新启动Sql服务器
  4. 执行了不安全的查询并立即执行

有人猜到我做错了什么吗?

哦,忘了说一件事: "ValuesTable" table 是分区视图,每年都有一个 table 。像 ValuesTable2016、ValuesTable2017。每个 table 都有一个检查约束,因此查询优化器将知道要在查询中包含哪些 table。 该视图是使用以下脚本创建的:

CREATE View [dbo].[ValuesTable] as 

    SELECT * FROM ValuesTable2016 UNION ALL 

    SELECT * FROM ValuesTable2017 UNION ALL 

    SELECT * FROM ValuesTable2018

如果我使用 TABLE 而不是分区视图执行参数化查询,例如

$sql = "select [ID], [Year], [Month], sum(Value) as Value, 
    MIN(FirstDateTime) as FirstDateTime, MAX(SecondDateTime) as 
    SecondDateTime from [ValuesTable2017] where [FirstDateTime] >= ? and 
    [SecondDateTime] <= ? and [Year] in (?) GROUP BY [ID], 
    [Year], [Month] order by [ID] offset 0 rows fetch next 30 rows only";

与不安全查询一样,查询运行速度很快。但是,显然,我需要使用分区视图,所以这不是一个选项。

不安全的查询总是运行得很快,使用分区视图或table。 所以建议我改变它的结构是没有用的。问题是,未参数化的查询运行得很快,而参数化的查询却要花很长时间。

还有一件事:我已经在没有 pdo 的情况下测试了它,使用带有绑定参数的 sqlsrv_prepare 并使用 sqlsrv_execute 执行它。它也运行得很慢。

我想出了一个解决方法:如果我执行以下查询,包括 union all ONLY 在包含数据的表上(而不是在所有表上使用 union all 的视图),它的执行速度与未参数化查询:

$sql = "select [ID], [Year], [Month], sum(Value) as Value, 
MIN(FirstDateTime) as FirstDateTime, MAX(SecondDateTime) as 
SecondDateTime from 
(select * FROM [ValuesTable2016] UNION ALL select * FROM [ValuesTable2017]) 
as ValuesTable
where [FirstDateTime] >= ? and 
[SecondDateTime] <= ? and [Year] in (?, ?) GROUP BY [ID], 
[Year], [Month] order by [ID] offset 0 rows fetch next 30 rows only";

带参数

$param1 = '2016-10-01 00:00:00';
$param2 = '2017-01-01 23:59:59';
$param3 = '2016';
$param3 = '2017';

$result->bindParam(1, $param1);
$result->bindParam(2, $param2);
$result->bindParam(3, $param3);
$result->bindParam(4, $param4);

所以我猜想,出于某种原因,当我使用 group by clausule 和 union all 在所有具有检查约束的相关表上执行参数化查询时,它需要永远才能完成。如果我在没有 group by clausule 的情况下这样做,它会很快。如果我只对正在查询的表执行此操作,速度会很快。如果我使用查询中的参数不安全地执行它,它会很快。

所以,这是一种解决方法,而且有效。但如果有人能解释为什么会发生这样的事情,我将不胜感激。我知道这是一个很少有人会注意到的特殊性,也许如果它真的是参数化查询的问题,人们也不会费心去修复它,因为我并不那么重要。此外,命运总是让我面对这种不常见的问题,从而使我的生活更加艰难。但是,如果有人有时间和善意,我真的很想知道发生了什么。