在WHERE子句中使用嵌套循环时,我遇到了严重的性能问题。
当我按原样运行以下代码时,需要几分钟时间。诀窍是如果report_id
为NULL,我正在使用WHERE子句来提取 ALL 数据,但如果我在参数字符串中设置它们,则仅使用某些report_id
。
函数[fn_Parse_List]
将VARCHAR字符串(例如'123,456,789'
)转换为表格,其中每一行都是整数形式的数字,然后在IN子句中使用。
当我使用report_id = '456'
(虚线部分)运行下面的代码时,代码需要几秒钟,但传递临时表并使用WHERE子句中的SELECT语句会将其杀死。
alter procedure dbo.p_revenue
(@report_id varchar(max) = NULL)
as
select cast(value as int) Report_ID
into #report_ID_Temp
from [fn_Parse_List] (@report_id)
SELECT *
FROM BIGTABLE
where @report_id is null
or a.report_id in (select Report_ID from #report_ID_Temp)
--Where @report_id is null or a.report_id in (456)
exec p_revenue @report_id = '456'
有没有办法优化这个?我尝试使用表#report_ID_Temp
进行JOIN,但是当report_id
为NULL时,它仍然需要很长时间并且不起作用。
答案 0 :(得分:2)
你违反了三条不同的规则。
OR
不会为您提供两个查询计划。 IF
确实如此。ALTER TABLE
语句来添加主键聚簇索引。或者您可以CREATE TABLE
首先声明结构。fn_Parse_List
是个好主意,那么您的阅读时间不够Sommarskog 答案 1 :(得分:1)
如果我要为您的案例编写存储过程,我会使用Table Valued Parameter
(TVP)而不是将多个值作为逗号分隔的字符串传递。
如下所示:
-- Create a type for the TVP
CREATE TYPE REPORT_IDS_PAR AS TABLE(
report_id INT
);
GO
-- Use the TVP type instead of VARCHAR
CREATE PROCEDURE dbo.revenue
@report_ids REPORT_IDS_PAR READONLY
AS
BEGIN
SET NOCOUNT ON;
IF NOT EXISTS(SELECT 1 FROM @report_ids)
SELECT
*
FROM
BIGTABLE;
ELSE
SELECT
*
FROM
@report_ids AS ids
INNER JOIN BIGTABLE AS bt ON
bt.report_id=ids.report_id;
-- OPTION(RECOMPILE) -- see remark below
END
GO
-- Execute the Stored Procedure
DECLARE @ids REPORT_IDS_PAR;
-- Empty table for all rows:
EXEC dbo.revenue @ids;
-- Specific report_id's for specific rows:
INSERT INTO @ids(report_id)VALUES(123),(456),(789);
EXEC dbo.revenue @ids;
GO
如果您使用包含大量行或行数变化很大的TVP运行此过程,建议您在查询中添加选项OPTION(RECOMPILE)
。
答案 2 :(得分:0)
我看到有两件可能有助于提高绩效的事情。取决于哪个部分花费的时间最长。首先,SELECT INTO是SQL Server 2014之前的单线程操作。如果这需要很长时间,请使用CREATE TABLE创建显式定义的临时表。其次,根据插入临时表的记录数,您可能需要Report_ID列的索引。这一切都可以在存储过程的主体中完成。如果你最终使用显式定义的临时表,我会在加载数据后创建索引。
如果这没有帮助,请首先检查BIGTABLE上的report_id列是否已编入索引。然后尝试将select分成2并与UNION ALL组合,如下所示:
ALTER PROCEDURE dbo.p_revenue
(
@report_id VARCHAR(MAX) = NULL
)
AS
SELECT CAST(value AS INT) Report_ID
INTO #report_ID_Temp
FROM fn_Parse_List(@report_id);
SELECT *
FROM BIGTABLE
WHERE @report_id IS NULL
UNION ALL
SELECT *
FROM BIGTABLE
WHERE a.report_id IN ( SELECT Report_ID
FROM #report_ID_Temp );
GO
EXEC p_revenue @report_id = '456';
答案 3 :(得分:0)
- 您是说我应该有两个查询,一个是在report_id不存在的情况下提取的,另一个是有一个report_ids列表?
醇>
是的,是的,是的。事实上,当你直接输入数字时,它会以某种方式运作,从而分散你的核心问题。当@report_id为null时需要进行表扫描,而当不是@report_id时需要进行索引查找,并且在一个执行计划中不能同时进行。表演不可避免地会受到各种各样的影响。
我不愿意,因为我从中抽出的桌子实际上是一个 查看800行,其他参数未在上面显示。
我看不出问题出在哪里,SELECT * FROM BIGTABLE
和SELECT * FROM BIGVIEW
似乎相同。如果需要参数您可以使用内联表值函数。如果你有更多具有变量选择性的参数,例如@report_id
,我想你最迟还是会以动态sql结束。
UNION ALL
会有所帮助,但即使没有必要,也会执行其中一个子查询。
作为快速补丁您可以将OPTION(RECOMPILE)
附加到SELECT
并进行一次表扫描,另一次搜索索引,但每次重新编译会产生非常重要的开销。