我有许多连接的表,并且最大行数约为 400 万条记录。我们正在存储过程中搜索此表,并且有一个默认值为 NULL 的可选参数,下面是我们正在运行的编辑示例,连接中涉及更多表,但只有 1 个字段具有 WHERE 子句;
DECLARE @OwnerId VARCHAR (50)=NULL
SET @OwnerId = 'A123456'
SELECT DISTINCT
t1.Id,
t2.OwnerId,
FROM
table1 t1
INNER JOIN [table2] t2 m ON t1.Id = t2.id
WHERE
t2.OwnerId = @OwnerId
Run Code Online (Sandbox Code Playgroud)
OwnerId 上有一个索引。运行上述查询在不到 1 秒的时间内返回结果 (600)。但是,正如所指出的,该参数是可选的(以及其他参数),只要我稍微更改它以包含该值(如果不是 NULL)(我相信这是这样做的),相同的搜索就会超过 10 秒。
SET @OwnerId = 'A123456'
SELECT DISTINCT
t1.Id,
t2.OwnerId,
FROM
table1 t1
INNER JOIN [table2] t2 ON t1.Id = t2.id
WHERE
(@OwnerId IS null OR (t2.OwnerId = @OwnerId))
Run Code Online (Sandbox Code Playgroud)
我无法在生产服务器上运行执行计划,但在开发服务器上运行时可以看到略有不同(这没有 400 万行),所以有些事情正在发生变化,但不确定是什么。
像这样的查询可能会受到查询计划缓存的极大影响。当您有一个带有类似子句的 SP 时WHERE Column = @Param OR @Param IS NULL,SQL Server 将在 SP 首次运行时缓存该计划。如果@Param有一个非NULL值,那么该计划在它具有 value 时可能会很糟糕NULL,因为行估计将严重低于它实际返回的值。
此外,具有非NULL值的计划可能涉及索引查找(我希望您有良好的索引),而跨整个表的查找对于性能来说将非常差(可能比扫描更差)。
对于像这样简单的事情,您可能只需添加OPTION (RECOMPILE); 这将强制 DBMS 在您每次运行查询时重新创建计划。但是,这确实有开销,对于复杂的查询,您可能应该使用动态 SQL 来生成动态WHERE子句:
SELECT DISTINCT
t1.Id,
t2.OwnerId,
FROM
table1 t1
INNER JOIN [table2] t2 ON t1.Id = t2.id
WHERE
(@OwnerId IS null OR (t2.OwnerId = @OwnerId))
OPTION (RECOMPILE);
Run Code Online (Sandbox Code Playgroud)
DISTINCT, 在一个有 400 万行的表上,要求每一行,无论如何都会有问题;因为这对服务器来说是很多开销,因为它需要对所有行进行排序。如果您在t1.Idand上有索引t2.OwnerId,那么这将有助于 RDBMS;但如果你DISTINCT在这么大的数据集上做一个,在我看来,这确实会推断出设计缺陷。
Gail Shaw 和 Aaron Bertrand 也分别写了一些关于他们称之为Catch-All Queries和Kitchen Sink Queries 的文章,这些文章可能值得一读。