变量会使查询性能变差

时间:2018-08-20 04:19:06

标签: sql sql-server tsql sql-server-2016

我有这个查询,当我声明变量时,性能会大大降低:

DECLARE @StartDate DATETIME,
        @EndDate   DATETIME

SET @StartDate = '2018-08-13'
SET @EndDate   = '2018-08-19'

SELECT *
FROM [DIME_WH].[dbo].[FactOrderLines2] FL (nolock)
WHERE DD_OrderDate >= @StartDate
  AND DD_OrderDate <= @EndDate

这比此SQL语句慢得多:

SELECT *
FROM [DIME_WH].[dbo].[FactOrderLines2] FL (nolock)
WHERE DD_OrderDate >= '2018-08-01'
  AND DD_OrderDate <= '2018-08-17'

两个查询最后都会返回相同的结果。

3 个答案:

答案 0 :(得分:3)

$mysqli = new mysqli("a", "a", "a", "a");

if (mysqli_connect_errno()) {
   printf("Connect failed: %s\n", mysqli_connect_error());
   exit();
}

$sql = "UPDATE laporan_gini SET id_provinsi='1' WHERE nama_item_vertical_variabel= 'INDONESIA'";
$sql = "UPDATE laporan_gini SET id_provinsi='61' WHERE nama_item_vertical_variabel= 'KALIMANTAN BARAT'";

if (!$mysqli->multi_query($sql)) {
    echo "Multi query failed: (" . $mysqli->errno . ") " . $mysqli->error;
}

do {
    if ($res = $mysqli->store_result()) {
        var_dump($res->fetch_all(MYSQLI_ASSOC));
        $res->free();
    }
} while ($mysqli->more_results() && $mysqli->next_result());

在参数中使用常量时,SELECT * FROM [DIME_WH].[dbo].[FactOrderLines2] FL (nolock) WHERE DD_OrderDate >= '2018-08-01' AND DD_OrderDate <= '2018-08-17' 为此查询创建Optimiser。因此,如果以相同的值执行相同的查询,则计划被重用,如果值被更改,则创建另一个计划。

所以具有恒定值的参数很快。

special plan

在参数中使用变量时。然后优化器为传递的第一个参数值创建执行计划。

例如,SELECT * FROM [DIME_WH].[dbo].[FactOrderLines2] FL (nolock) WHERE DD_OrderDate >= @StartDate AND DD_OrderDate <= @EndDate @StartDate='2018-08-01'的值是第一次通过。 然后,由优化程序创建最佳执行计划。这个计划足以满足这个价值。 下次通过@EndDate='2018-08-07'@StartDate='2018-08-01'值时,则使用相同的先前计划,这对于该参数可能不是最佳选择。

换句话说,对于第一个值而言最优的相同计划对于另一个值而言是次优的。

所以查询的执行效果可能会很差且很慢。这称为@EndDate='2018-08-31'

有几种方法可以解决此问题。

Parameter Sniffing

注意::在此主题中,我们仅关注为何变量性能较慢而其他因素保持不变的原因。

答案 1 :(得分:1)

这是因为SQL Server在优化时不知道变量的值-当它做出估计并且无法为其查找任何statistics时(作为一种可能性),因此它(很可能是) )仅扫描整个表格,而不是进行查找(搜索)。
如果在存储过程内使用或用sniffed参数化,则它们可以是“ sp_executesql

答案 2 :(得分:0)

问题可能是参数嗅探,也许不是。由于@KumarHarsh已经介绍了该主题,因此我将跳过该主题。这里最重要的问题是: FactOrderLines.DD_OrderDate是什么数据类型? 。出于性能原因以及正确性,这很重要。

性能第一。如果DD_OrderDate是DATE数据类型,而您的变量或参数是DATETIME,则优化器必须跳过多余的圈来利用您的索引,否则将被迫进行扫描而不是查找。请注意以下示例数据:

USE tempdb;
GO

IF OBJECT_ID('#FactOrderLines') IS NOT NULL DROP TABLE #FactOrderLines;
GO
CREATE TABLE #FactOrderLines(someId INT IDENTITY, DD_OrderDate DATETIME NOT NULL);

CREATE CLUSTERED INDEX nc_factOrderLines ON #FactOrderLines(DD_OrderDate);

INSERT #FactOrderLines(DD_OrderDate) 
SELECT TOP (10000) DATEADD(DAY,CHECKSUM(NEWID())%100, getdate())
FROM sys.all_columns;
GO

现在让我们比较以下查询的执行计划:

-- AS DATE
DECLARE @StartDate DATE = '2018-08-01',
        @EndDate   DATE = '2018-08-20';

SELECT * 
FROM #FactOrderLines
WHERE DD_OrderDate >= @StartDate
AND   DD_OrderDate <= @EndDate
OPTION (RECOMPILE)
GO

-- AS DATETIME
DECLARE @StartDate DATETIME = '2018-08-01',
        @EndDate   DATETIME = '2018-08-31';

SELECT * 
FROM #FactOrderLines
WHERE DD_OrderDate >= @StartDate
AND   DD_OrderDate <= @EndDate
OPTION (RECOMPILE);

执行计划:

enter image description here

由于这个原因-您要确保变量/参数使用的数据类型与它们要处理的列相同。

现在讲正确性;注意此查询:

DECLARE @StartDate DATE = '2018-08-01',
        @EndDate   DATE = '2018-08-20';

SELECT
  [getdate as datetime]  = GETDATE(),
  [@enddate as datetime] = CAST(@EndDate AS DATETIME),
  [getdate as date]      = CAST(GETDATE() AS DATE),
  [datetime equality]    = IIF(GETDATE() > @EndDate,'yep','nope'),
  [date equality]        = IIF(CAST(GETDATE() AS DATE) > @EndDate,'yep','nope');

结果:

getdate as datetime     @enddate as datetime    getdate as date datetime equality date equality
----------------------- ----------------------- --------------- ----------------- -------------
2018-08-20 13:52:46.247 2018-08-20 00:00:00.000 2018-08-20      yep               nope

日期格式的值转换为日期时间为0小时0秒...