存储过程执行时间超过 30 秒
Stored procedure takes more than 30 seconds to execute
我正在优化存储过程,其中我在一个 table 变量中获取超过 10K 用户 ID 的列表作为参数。如果 table 变量中有任何数据,我将设置标志 @ContainsUserIds。在主查询中,如果在 table 变量中找不到数据,则选择 table 变量中存在的所有用户或所有用户(where 子句中有更多条件)。
这里的问题是where子句中有OR条件的语句耗时超过30秒。如果我删除第一个条件以检查 @ContainsUserIds = 0,那么它将在一秒钟内执行。有人可以帮我优化这个查询吗?
此存储过程是从不同位置调用的,因此用户 ID 可能无法通过。
CREATE PROCEDURE [core].[spGetUsersByFilter]
(
@ClientId nvarchar(38) = NULL,
@UserIds [UserIdList] readonly
)
AS
BEGIN
SET NOCOUNT ON
DECLARE @ContainsUserIds BIT = 0,
SET @ContainsUserIds = CASE
WHEN EXISTS(SELECT TOP 1 1 FROM @UserIds)
THEN 1
ELSE 0
END;
SELECT DISTINCT ES.Id
FROM [db].[Users] E
JOIN [db].[UserDetails] ES ON ES.UserId = E.Id
WHERE E.[Active] = 1
AND (@ContainsUserIds = 0 OR E.UserId IN(SELECT Item FROM @UserIds))
AND ES.ClientId = @ClientId
END
SQL 众所周知不喜欢 OR
,优化器有时会绕过它,但在大多数情况下,将事物转换为 UNION [ALL]
语法是个好主意。如果只有 1 个 OR
通常很容易做到,如果有多个则很快爆炸。
无论如何,您可以将存储过程转换为:
CREATE PROCEDURE [core].[spGetUsersByFilter]
(
@ClientId nvarchar(38) = NULL,
@UserIds [UserIdList] readonly
)
AS
BEGIN
SET NOCOUNT ON
DECLARE @ContainsUserIds BIT = 0,
SET @ContainsUserIds = CASE
WHEN EXISTS(SELECT * FROM @UserIds)
THEN 1
ELSE 0
END;
SELECT DISTINCT ES.Id
FROM [db].[Users] E
JOIN [db].[UserDetails] ES ON ES.UserId = E.Id
WHERE E.[Active] = 1
AND (@ContainsUserIds = 0)
AND ES.ClientId = @ClientId
UNION ALL
SELECT DISTINCT ES.Id
FROM [db].[Users] E
JOIN [db].[UserDetails] ES ON ES.UserId = E.Id
WHERE E.[Active] = 1
AND (@ContainsUserIds = 1)
AND E.UserId IN (SELECT Item FROM @UserIds))
AND ES.ClientId = @ClientId
END
就是说,您也可以在此处拆分并执行 2 个单独的 SELECT
。此外,如果 table-variable 包含多个记录,您可能更喜欢使用 temp-table,因为后者允许索引,最重要的是具有统计处理功能,这将产生更好的执行计划。我也更喜欢 JOIN
而不是 IN ()
,只要确保 JOIN
ing 时没有加倍的值即可。
CREATE PROCEDURE [core].[spGetUsersByFilter]
(
@ClientId nvarchar(38) = NULL,
@UserIds [UserIdList] readonly
)
AS
BEGIN
SET NOCOUNT ON
SELECT DISTINCT Item
INTO #UserIds
FROM @UserIds
IF @@ROWCOUNT = 0
BEGIN
SELECT DISTINCT ES.Id
FROM [db].[Users] E
JOIN [db].[UserDetails] ES ON ES.UserId = E.Id
WHERE E.[Active] = 1
AND ES.ClientId = @ClientId;
END
ELSE
BEGIN
CREATE UNIQUE INDEX uq0_UserIds ON #UserIds ( Item ) WITH (FILLFACTOR = 100);
SELECT DISTINCT ES.Id
FROM [db].[Users] E
JOIN [db].[UserDetails] ES ON ES.UserId = E.Id
JOIN #UserIds T ON T.Item = E.UserId
WHERE E.[Active] = 1
AND ES.ClientId = @ClientId
END
END
PS: 以上都是用记事本写的,未经测试,可能需要一些汇编=)
我正在优化存储过程,其中我在一个 table 变量中获取超过 10K 用户 ID 的列表作为参数。如果 table 变量中有任何数据,我将设置标志 @ContainsUserIds。在主查询中,如果在 table 变量中找不到数据,则选择 table 变量中存在的所有用户或所有用户(where 子句中有更多条件)。
这里的问题是where子句中有OR条件的语句耗时超过30秒。如果我删除第一个条件以检查 @ContainsUserIds = 0,那么它将在一秒钟内执行。有人可以帮我优化这个查询吗?
此存储过程是从不同位置调用的,因此用户 ID 可能无法通过。
CREATE PROCEDURE [core].[spGetUsersByFilter]
(
@ClientId nvarchar(38) = NULL,
@UserIds [UserIdList] readonly
)
AS
BEGIN
SET NOCOUNT ON
DECLARE @ContainsUserIds BIT = 0,
SET @ContainsUserIds = CASE
WHEN EXISTS(SELECT TOP 1 1 FROM @UserIds)
THEN 1
ELSE 0
END;
SELECT DISTINCT ES.Id
FROM [db].[Users] E
JOIN [db].[UserDetails] ES ON ES.UserId = E.Id
WHERE E.[Active] = 1
AND (@ContainsUserIds = 0 OR E.UserId IN(SELECT Item FROM @UserIds))
AND ES.ClientId = @ClientId
END
SQL 众所周知不喜欢 OR
,优化器有时会绕过它,但在大多数情况下,将事物转换为 UNION [ALL]
语法是个好主意。如果只有 1 个 OR
通常很容易做到,如果有多个则很快爆炸。
无论如何,您可以将存储过程转换为:
CREATE PROCEDURE [core].[spGetUsersByFilter]
(
@ClientId nvarchar(38) = NULL,
@UserIds [UserIdList] readonly
)
AS
BEGIN
SET NOCOUNT ON
DECLARE @ContainsUserIds BIT = 0,
SET @ContainsUserIds = CASE
WHEN EXISTS(SELECT * FROM @UserIds)
THEN 1
ELSE 0
END;
SELECT DISTINCT ES.Id
FROM [db].[Users] E
JOIN [db].[UserDetails] ES ON ES.UserId = E.Id
WHERE E.[Active] = 1
AND (@ContainsUserIds = 0)
AND ES.ClientId = @ClientId
UNION ALL
SELECT DISTINCT ES.Id
FROM [db].[Users] E
JOIN [db].[UserDetails] ES ON ES.UserId = E.Id
WHERE E.[Active] = 1
AND (@ContainsUserIds = 1)
AND E.UserId IN (SELECT Item FROM @UserIds))
AND ES.ClientId = @ClientId
END
就是说,您也可以在此处拆分并执行 2 个单独的 SELECT
。此外,如果 table-variable 包含多个记录,您可能更喜欢使用 temp-table,因为后者允许索引,最重要的是具有统计处理功能,这将产生更好的执行计划。我也更喜欢 JOIN
而不是 IN ()
,只要确保 JOIN
ing 时没有加倍的值即可。
CREATE PROCEDURE [core].[spGetUsersByFilter]
(
@ClientId nvarchar(38) = NULL,
@UserIds [UserIdList] readonly
)
AS
BEGIN
SET NOCOUNT ON
SELECT DISTINCT Item
INTO #UserIds
FROM @UserIds
IF @@ROWCOUNT = 0
BEGIN
SELECT DISTINCT ES.Id
FROM [db].[Users] E
JOIN [db].[UserDetails] ES ON ES.UserId = E.Id
WHERE E.[Active] = 1
AND ES.ClientId = @ClientId;
END
ELSE
BEGIN
CREATE UNIQUE INDEX uq0_UserIds ON #UserIds ( Item ) WITH (FILLFACTOR = 100);
SELECT DISTINCT ES.Id
FROM [db].[Users] E
JOIN [db].[UserDetails] ES ON ES.UserId = E.Id
JOIN #UserIds T ON T.Item = E.UserId
WHERE E.[Active] = 1
AND ES.ClientId = @ClientId
END
END
PS: 以上都是用记事本写的,未经测试,可能需要一些汇编=)