SQL Server 2014:存储过程执行时间慢
SQL Server 2014 : slow stored procedure execution time
我有以下 table 结构:
AuditUserMethods
:
+---------------+---------------+----------+
| ColumnName | DataType | Nullable |
+---------------+---------------+----------+
| Id | INT | NOT NULL |
| CreatedDate | DATETIME | NOT NULL |
| ApiMethodName | NVARCHAR(MAX) | NOT NULL |
| Request | NVARCHAR(MAX) | NOT NULL |
| Result | NVARCHAR(MAX) | NOT NULL |
| Method_Id | INT | NOT NULL |
| User_Id | INT | NULL |
+---------------+---------------+----------+
AuditUserMethodErrorCodes
:
+--------------------+----------+----------+
| ColumnName | DataType | Nullable |
+--------------------+----------+----------+
| Id | INT | NOT NULL |
| AuditUserMethod_Id | INT | NOT NULL |
| ErrorCode | INT | NOT NULL |
+--------------------+----------+----------+
ID
是两个 table 中的 PK。存在一对多的关系。一个 AuditUserMethod
可以有多个 AuditUserMethodErrorCodes
。因此 FK AuditUserMethod_Id
.
在 AuditUserMethods
table 中的 AuditUserMethod_Id
和 CreatedDate
上都有两个非聚集索引。
该程序的目的是return一个基于过滤器的分页结果集。 @PageSize
确定要 return 的行数,@PageIndex
确定要 return 的页面。所有其他变量用于过滤。
return 编辑了三个结果集。
- 包含
AuditUserMethods
详细信息
- 包含
AuditUserMethodErrorCodes
详细信息
- 包含找到的总行数(即,如果页面大小为 1000,并且有 5000 行符合所有条件,这将是 return 5000)。
存储过程:
CREATE PROCEDURE [api].[Audit_V1_GetAuditDetails]
(
@Users XML = NULL,
@Methods XML = NULL,
@ErrorCodes XML = NULL,
@FromDate DATETIME = NULL,
@ToDate DATETIME = NULL,
@PageSize INT = 5,
@PageIndex INT = 0
)
AS
BEGIN
DECLARE @UserIds TABLE (Id INT)
DECLARE @MethodNames TABLE (Name NVARCHAR(256))
DECLARE @ErrorCodeIds TABLE (Id INT)
DECLARE @FilterUsers BIT = 0
DECLARE @FilterMethods BIT = 0
DECLARE @FilterErrorCodes BIT = 0
INSERT @UserIds
SELECT
x.y.value('.', 'int')
FROM
@Users.nodes('Ids/x/@i') AS x (y)
INSERT @MethodNames
SELECT
x.y.value('.', 'NVARCHAR(256)')
FROM
@Methods.nodes('ArrayOfString/string') AS x (y)
INSERT @ErrorCodeIds
SELECT
x.y.value('.', 'int')
FROM
@ErrorCodes.nodes('Ids/x/@i') AS x (y)
IF EXISTS (SELECT TOP 1 0 FROM @UserIds)
SET @FilterUsers = 1
IF EXISTS (SELECT TOP 1 0 FROM @MethodNames)
SET @FilterMethods = 1
IF EXISTS (SELECT TOP 1 0 FROM @ErrorCodeIds)
SET @FilterErrorCodes = 1
DECLARE @StartRow INT = @PageIndex * @Pagesize
DECLARE @PageDataResults TABLE (Id INT,
CreatedDate DATETIME,
ApiMethodName NVARCHAR(256),
Request NVARCHAR(MAX),
Result NVARCHAR(MAX),
MethodId INT,
UserId INT,
TotalRows INT);
WITH PageData AS
(
SELECT
id AS id
, createddate AS createddate
, apimethodname AS apimethodname
, request AS request
, result AS result
, method_id AS method_id
, user_id AS user_id
, ROW_NUMBER() OVER (ORDER BY createddate DESC, id DESC) AS row_number
, COUNT(*) OVER() as TotalRows
FROM
dbo.AuditUserMethods AS aum
WHERE
(@FromDate IS NULL OR
(@FromDate IS NOT NULL AND aum.createddate > @FromDate))
AND (@ToDate IS NULL OR
(@ToDate IS NOT NULL AND aum.createddate < @ToDate))
AND (@FilterUsers = 0 OR
(@FilterUsers = 1 AND aum.user_id IN (SELECT Id FROM @UserIds)))
AND (@FilterMethods = 0 OR
(@FilterMethods = 1 AND aum.ApiMethodName IN (SELECT Name FROM @MethodNames)))
AND (@FiltererRorCodes = 0 OR
(@FiltererRorCodes = 1
AND EXISTS (SELECT 1
FROM AuditUserMethodErrorCodes e
WHERE e.AuditUserMethod_Id = aum.Id
AND e.ErrorCode IN (SELECT Id FROM @ErrorCodeIds)
)
)
)
)
INSERT @PageDataResults
SELECT TOP (@Pagesize)
PageData.id AS id
, PageData.createddate AS createddate
, PageData.apimethodname AS apimethodname
, PageData.request AS request
, PageData.result AS result
, PageData.method_id AS method_id
, PageData.user_id AS user_id
, PageData.TotalRows AS totalrows
FROM
PageData
WHERE
PageData.row_number > @StartRow
ORDER BY
PageData.createddate DESC
SELECT
Id, CreatedDate, ApiMethodName, Request, Result, MethodId, UserId
FROM
@PageDataResults
SELECT
aumec.AuditUserMethod_Id, aumec.ErrorCode
FROM
@PageDataResults ps
INNER JOIN
AuditUserMethodErrorCodes aumec ON ps.Id = aumec.AuditUserMethod_Id
SELECT TOP 1
TotalRowsNumberOfReturnedAuditEntries
FROM @PageDataResults
END
AuditUserMethods
table 包含 500,000 行,AuditUserMethodErrorCodes
包含 67843 行。
我正在使用以下参数执行该过程:
EXEC [api].[Audit_V1_GetAuditDetails] @Users = N'<Ids><x i="1" /></Ids>'
,@Methods = NULL
,@ErrorCodes = N'<Ids />'
,@FromDate = '2015-02-15 07:18:59.613'
,@ToDate = '2015-07-02 08:18:59.613'
,@Pagesize = 5000
,@PageIndex = 0
存储过程执行仅需 2 秒多一点,return 5000 行。我需要这个存储过程 运行 快得多,但我不确定如何改进它。
以实际执行计划为准。相对于该批次,CTE 占了 99%。在 CTE 中,排序占了 95% 的成本:
您可以首先为您的 CTE 提供某种索引,这可以通过以下方式完成 - 请参阅 /**/ 以获取 "changed lines":
WITH PageData AS
(
SELECT
/**/ TOP 100 PERCENT
id AS id
,createddate AS createddate
,apimethodname AS apimethodname
,request AS request
,result AS result
,method_id AS method_id
,user_id AS user_id
,ROW_NUMBER() OVER (ORDER BY createddate DESC, id DESC) AS row_number
,COUNT(*) OVER() as TotalRows
FROM dbo.AuditUserMethods AS aum
WHERE (@FromDate IS NULL OR (@FromDate IS NOT NULL AND aum.createddate > @FromDate))
AND (@ToDate IS NULL OR (@ToDate IS NOT NULL AND aum.createddate < @ToDate))
AND (@FilterUsers = 0 OR (@FilterUsers = 1 AND aum.user_id IN (SELECT Id FROM @UserIds)))
AND (@FilterMethods = 0 OR (@FilterMethods = 1 AND aum.ApiMethodName IN (SELECT Name FROM @MethodNames)))
AND
(
@FiltererRorCodes = 0 OR
(
@FiltererRorCodes = 1 AND EXISTS
(
SELECT 1
FROM AuditUserMethodErrorCodes e
WHERE e.AuditUserMethod_Id = aum.Id
AND e.ErrorCode IN (SELECT Id FROM @ErrorCodeIds)
)
)
)
/**/ORDER BY
/**/ PageData.createddate
/**/ ,PageData.row_number
)
我还会尝试在 createddate 和 row_number 之间更改 CTE 上的顺序 "order",然后是 row_number,然后是 createddate。
然后您将 CTE 交给下一个流程,它已经按照预期的顺序进行。它可能会加快速度。 ORDER BY 需要 TOP 100 PERCENT。
我首先声明几个 table 参数类型。
CREATE TYPE [api].[IdSet] AS TABLE
(
[Id] INT NOT NULL
);
并且
CREATE TYPE [api].[StringSet] AS TABLE
(
[Value] NVARCHAR(256) NOT NULL
);
然后我会更改存储过程的签名以使用它们。
注意我也return总计数作为输出参数而不是单独的结果集。
CREATE PROCEDURE [api].[Audit_V2_GetAuditDetails]
(
@userIds [api].[IdSet] READONLY,
@methodNames [api].[StringSet] READONLY,
@errorCodeIds [api].[IdSet] READONLY,
@fromDate DATETIME = NULL,
@toDate DATETIME = NULL,
@pageSize INT = 5,
@pageIndex INT = 0,
@totalCount BIGINT OUTPUT
)
我知道您可能仍需要进行 XML 提取,但如果您在 SP 之外进行提取将有助于查询规划器。
现在,在 SP 中,我不会使用 @PageDataResults
我只会获取页面的 ID。我也不会使用 CTE,这在这种情况下没有帮助。
我会简化查询并 运行 它一次来汇总总计数,然后如果它大于 0,运行 再次对相同的查询进行 return id 页面。查询的主体将由服务器内部缓存。
此外,Id' 使用 OFFSET
和 FETCH
分页对 ORDER BY
、
进行分页
我在下面概述了一些逻辑上的简化,
CREATE PROCEDURE [api].[Audit_V2_GetAuditDetails]
(
@userIds [api].[IdSet] READONLY,
@methodNames [api].[StringSet] READONLY,
@errorCodeIds [api].[IdSet] READONLY,
@fromDate DATETIME = NULL,
@toDate DATETIME = NULL,
@pageSize INT = 5,
@pageIndex INT = 0,
@totalCount BIGINT OUTPUT
)
AS
DECLARE @offset INT = @pageSize * @pageIndex;
DECLARE @filterUsers BIT = 0;
DECLARE @filterMethods BIT = 0;
DECLARE @filterErrorCodes BIT = 0;
IF EXISTS (SELECT 0 FROM @userIds)
SET @filterUsers = 1;
IF EXISTS (SELECT 0 FROM @methodNames)
SET @filterMethods = 1;
IF EXISTS (SELECT 0 FROM @errorCodeIds)
SET @filterErrorCodes = 1;
SELECT
@totalCount = COUNT_BIG(*)
FROM
[dbo].[AuditUserMethods] [aum]
LEFT JOIN
@userIds [U]
ON [U].[Id] = [aum].[user_id]
LEFT JOIN
@methodName [M]
ON [M].[Value] = [aum].[ApiMethodName]
WHERE
(
@fromDate IS NULL
OR
[aum].[createddate] > @fromDate
)
AND
(
@toDate IS NULL
OR
[aum].[createddate] < @toDate
)
AND
(
@filterUsers = 0
OR
[U].[Id] IS NOT NULL
(
AND
(
@filterMethods = 0
OR
[M].[Value] IS NOT NULL
(
AND
(
@filterErrorCodes = 0
OR
(
EXISTS(
SELECT
1
FROM
[dbo].[AuditUserMethodErrorCodes] [e]
JOIN
@errorCodeIds [ec]
ON [ec].[Id] = [e].[ErrorCode]
WHERE
[e].[AuditUserMethod_Id] = [aum].[Id])
);
DECLARE @pageIds [api].[IdSet];
IF @totalCount > 0
INSERT @pageIds
SELECT
[aum].[id]
FROM
[dbo].[AuditUserMethods] [aum]
LEFT JOIN
@userIds [U]
ON [U].[Id] = [aum].[user_id]
LEFT JOIN
@methodName [M]
ON [M].[Value] = [aum].[ApiMethodName]
WHERE
(
@fromDate IS NULL
OR
[aum].[createddate] > @fromDate
)
AND
(
@toDate IS NULL
OR
[aum].[createddate] < @toDate
)
AND
(
@filterUsers = 0
OR
[U].[Id] IS NOT NULL
(
AND
(
@filterMethods = 0
OR
[M].[Value] IS NOT NULL
(
AND
(
@filterErrorCodes = 0
OR
(
EXISTS(
SELECT
1
FROM
[dbo].[AuditUserMethodErrorCodes] [e]
JOIN
@errorCodeIds [ec]
ON [ec].[Id] = [e].[ErrorCode]
WHERE
[e].[AuditUserMethod_Id] = [aum].[Id])
)
ORDER BY
[aum].[createddate] DESC,
[aum].[id] DESC
OFFSET @offset ROWS
FETCH NEXT @pageSize ROWS ONLY;
SELECT
[aum].[Id],
[aum].[CreatedDate],
[aum].[ApiMethodName],
[aum].[Request],
[aum].[Result],
[aum].[MethodId],
[aum].[UserId]
FROM
[dbo].[AuditUserMethods] [aum]
JOIN
@pageIds [i]
ON [i].[Id] = [aum].[id]
ORDER BY
[aum].[createddate] DESC,
[aum].[id] DESC;
SELECT
[aumec].[AuditUserMethod_Id],
[aumec].[ErrorCode]
FROM
[dbo].[AuditUserMethodErrorCodes] [aumec]
JOIN
@pageIds [i]
ON [i].[Id] = [aumec].[AuditUserMethod_Id];
/* The total count is an output parameter */
RETURN 0;
如果这不足以改善情况,您将需要查看查询计划并考虑最佳索引。
警告 所有代码都是即兴编写的,因此,虽然想法是正确的,但语法可能并不完美。
(@FromDate IS NULL OR
(@FromDate IS NOT NULL AND aum.createddate > @FromDate))
与
相同
(@FromDate IS NULL OR aum.createddate > @FromDate)
尝试这样的事情
CREATE PROCEDURE [api].[Audit_V1_GetAuditDetails]
(
@Users XML = NULL,
@Methods XML = NULL,
@ErrorCodes XML = NULL,
@FromDate DATETIME = NULL,
@ToDate DATETIME = NULL,
@PageSize INT = 5,
@PageIndex INT = 0
)
AS
BEGIN
DECLARE @UserIds TABLE (Id INT)
DECLARE @MethodNames TABLE (Name NVARCHAR(256))
DECLARE @ErrorCodeIds TABLE (Id INT)
INSERT @UserIds
SELECT
x.y.value('.', 'int')
FROM
@Users.nodes('Ids/x/@i') AS x (y)
INSERT @MethodNames
SELECT
x.y.value('.', 'NVARCHAR(256)')
FROM
@Methods.nodes('ArrayOfString/string') AS x (y)
INSERT @ErrorCodeIds
SELECT
x.y.value('.', 'int')
FROM
@ErrorCodes.nodes('Ids/x/@i') AS x (y)
IF NOT EXISTS (SELECT TOP 1 0 FROM @UserIds)
INSERT INTO @UserIds values (-1)
IF NOT EXISTS (SELECT TOP 1 0 FROM @MethodNames)
INSERT INTO @MethodNames values ('empty')
IF NOT EXISTS (SELECT TOP 1 0 FROM @ErrorCodeIds)
INSERT INTO @ErrorCodeIds values (-1)
IF @FromDate is null
@FromDate = '1/1/1900'
IF @ToDate is null
@ToDate = '1/1/2079'
DECLARE @StartRow INT = @PageIndex * @Pagesize
DECLARE @PageDataResults TABLE (Id INT,
CreatedDate DATETIME,
ApiMethodName NVARCHAR(256),
Request NVARCHAR(MAX),
Result NVARCHAR(MAX),
MethodId INT,
UserId INT,
TotalRows INT);
WITH PageData AS
(
SELECT
id AS id
, createddate AS createddate
, apimethodname AS apimethodname
, request AS request
, result AS result
, method_id AS method_id
, user_id AS user_id
, ROW_NUMBER() OVER (ORDER BY createddate DESC, id DESC) AS row_number
, COUNT(*) OVER() as TotalRows
FROM
dbo.AuditUserMethods AS aum
JOIN @UserIds
ON (aum.user_id = @UserIds.ID OR @UserIds.ID = -1)
AND aum.createddate > @FromDate
AND aum.createddate < @ToDate
JOIN @MethodNames
ON aum.ApiMethodName = @MethodNames.Name
OR @MethodNames.Name = 'empty'
JOIN AuditUserMethodErrorCodes e
on e.AuditUserMethod_Id = aum.Id
JOIN @ErrorCodeIds
ON e.ErrorCode = @ErrorCodeIds.ID
OR @ErrorCodeIds.ID = -1
)
我有以下 table 结构:
AuditUserMethods
:
+---------------+---------------+----------+
| ColumnName | DataType | Nullable |
+---------------+---------------+----------+
| Id | INT | NOT NULL |
| CreatedDate | DATETIME | NOT NULL |
| ApiMethodName | NVARCHAR(MAX) | NOT NULL |
| Request | NVARCHAR(MAX) | NOT NULL |
| Result | NVARCHAR(MAX) | NOT NULL |
| Method_Id | INT | NOT NULL |
| User_Id | INT | NULL |
+---------------+---------------+----------+
AuditUserMethodErrorCodes
:
+--------------------+----------+----------+
| ColumnName | DataType | Nullable |
+--------------------+----------+----------+
| Id | INT | NOT NULL |
| AuditUserMethod_Id | INT | NOT NULL |
| ErrorCode | INT | NOT NULL |
+--------------------+----------+----------+
ID
是两个 table 中的 PK。存在一对多的关系。一个 AuditUserMethod
可以有多个 AuditUserMethodErrorCodes
。因此 FK AuditUserMethod_Id
.
在 AuditUserMethods
table 中的 AuditUserMethod_Id
和 CreatedDate
上都有两个非聚集索引。
该程序的目的是return一个基于过滤器的分页结果集。 @PageSize
确定要 return 的行数,@PageIndex
确定要 return 的页面。所有其他变量用于过滤。
return 编辑了三个结果集。
- 包含
AuditUserMethods
详细信息 - 包含
AuditUserMethodErrorCodes
详细信息 - 包含找到的总行数(即,如果页面大小为 1000,并且有 5000 行符合所有条件,这将是 return 5000)。
存储过程:
CREATE PROCEDURE [api].[Audit_V1_GetAuditDetails]
(
@Users XML = NULL,
@Methods XML = NULL,
@ErrorCodes XML = NULL,
@FromDate DATETIME = NULL,
@ToDate DATETIME = NULL,
@PageSize INT = 5,
@PageIndex INT = 0
)
AS
BEGIN
DECLARE @UserIds TABLE (Id INT)
DECLARE @MethodNames TABLE (Name NVARCHAR(256))
DECLARE @ErrorCodeIds TABLE (Id INT)
DECLARE @FilterUsers BIT = 0
DECLARE @FilterMethods BIT = 0
DECLARE @FilterErrorCodes BIT = 0
INSERT @UserIds
SELECT
x.y.value('.', 'int')
FROM
@Users.nodes('Ids/x/@i') AS x (y)
INSERT @MethodNames
SELECT
x.y.value('.', 'NVARCHAR(256)')
FROM
@Methods.nodes('ArrayOfString/string') AS x (y)
INSERT @ErrorCodeIds
SELECT
x.y.value('.', 'int')
FROM
@ErrorCodes.nodes('Ids/x/@i') AS x (y)
IF EXISTS (SELECT TOP 1 0 FROM @UserIds)
SET @FilterUsers = 1
IF EXISTS (SELECT TOP 1 0 FROM @MethodNames)
SET @FilterMethods = 1
IF EXISTS (SELECT TOP 1 0 FROM @ErrorCodeIds)
SET @FilterErrorCodes = 1
DECLARE @StartRow INT = @PageIndex * @Pagesize
DECLARE @PageDataResults TABLE (Id INT,
CreatedDate DATETIME,
ApiMethodName NVARCHAR(256),
Request NVARCHAR(MAX),
Result NVARCHAR(MAX),
MethodId INT,
UserId INT,
TotalRows INT);
WITH PageData AS
(
SELECT
id AS id
, createddate AS createddate
, apimethodname AS apimethodname
, request AS request
, result AS result
, method_id AS method_id
, user_id AS user_id
, ROW_NUMBER() OVER (ORDER BY createddate DESC, id DESC) AS row_number
, COUNT(*) OVER() as TotalRows
FROM
dbo.AuditUserMethods AS aum
WHERE
(@FromDate IS NULL OR
(@FromDate IS NOT NULL AND aum.createddate > @FromDate))
AND (@ToDate IS NULL OR
(@ToDate IS NOT NULL AND aum.createddate < @ToDate))
AND (@FilterUsers = 0 OR
(@FilterUsers = 1 AND aum.user_id IN (SELECT Id FROM @UserIds)))
AND (@FilterMethods = 0 OR
(@FilterMethods = 1 AND aum.ApiMethodName IN (SELECT Name FROM @MethodNames)))
AND (@FiltererRorCodes = 0 OR
(@FiltererRorCodes = 1
AND EXISTS (SELECT 1
FROM AuditUserMethodErrorCodes e
WHERE e.AuditUserMethod_Id = aum.Id
AND e.ErrorCode IN (SELECT Id FROM @ErrorCodeIds)
)
)
)
)
INSERT @PageDataResults
SELECT TOP (@Pagesize)
PageData.id AS id
, PageData.createddate AS createddate
, PageData.apimethodname AS apimethodname
, PageData.request AS request
, PageData.result AS result
, PageData.method_id AS method_id
, PageData.user_id AS user_id
, PageData.TotalRows AS totalrows
FROM
PageData
WHERE
PageData.row_number > @StartRow
ORDER BY
PageData.createddate DESC
SELECT
Id, CreatedDate, ApiMethodName, Request, Result, MethodId, UserId
FROM
@PageDataResults
SELECT
aumec.AuditUserMethod_Id, aumec.ErrorCode
FROM
@PageDataResults ps
INNER JOIN
AuditUserMethodErrorCodes aumec ON ps.Id = aumec.AuditUserMethod_Id
SELECT TOP 1
TotalRowsNumberOfReturnedAuditEntries
FROM @PageDataResults
END
AuditUserMethods
table 包含 500,000 行,AuditUserMethodErrorCodes
包含 67843 行。
我正在使用以下参数执行该过程:
EXEC [api].[Audit_V1_GetAuditDetails] @Users = N'<Ids><x i="1" /></Ids>'
,@Methods = NULL
,@ErrorCodes = N'<Ids />'
,@FromDate = '2015-02-15 07:18:59.613'
,@ToDate = '2015-07-02 08:18:59.613'
,@Pagesize = 5000
,@PageIndex = 0
存储过程执行仅需 2 秒多一点,return 5000 行。我需要这个存储过程 运行 快得多,但我不确定如何改进它。
以实际执行计划为准。相对于该批次,CTE 占了 99%。在 CTE 中,排序占了 95% 的成本:
您可以首先为您的 CTE 提供某种索引,这可以通过以下方式完成 - 请参阅 /**/ 以获取 "changed lines":
WITH PageData AS
(
SELECT
/**/ TOP 100 PERCENT
id AS id
,createddate AS createddate
,apimethodname AS apimethodname
,request AS request
,result AS result
,method_id AS method_id
,user_id AS user_id
,ROW_NUMBER() OVER (ORDER BY createddate DESC, id DESC) AS row_number
,COUNT(*) OVER() as TotalRows
FROM dbo.AuditUserMethods AS aum
WHERE (@FromDate IS NULL OR (@FromDate IS NOT NULL AND aum.createddate > @FromDate))
AND (@ToDate IS NULL OR (@ToDate IS NOT NULL AND aum.createddate < @ToDate))
AND (@FilterUsers = 0 OR (@FilterUsers = 1 AND aum.user_id IN (SELECT Id FROM @UserIds)))
AND (@FilterMethods = 0 OR (@FilterMethods = 1 AND aum.ApiMethodName IN (SELECT Name FROM @MethodNames)))
AND
(
@FiltererRorCodes = 0 OR
(
@FiltererRorCodes = 1 AND EXISTS
(
SELECT 1
FROM AuditUserMethodErrorCodes e
WHERE e.AuditUserMethod_Id = aum.Id
AND e.ErrorCode IN (SELECT Id FROM @ErrorCodeIds)
)
)
)
/**/ORDER BY
/**/ PageData.createddate
/**/ ,PageData.row_number
)
我还会尝试在 createddate 和 row_number 之间更改 CTE 上的顺序 "order",然后是 row_number,然后是 createddate。
然后您将 CTE 交给下一个流程,它已经按照预期的顺序进行。它可能会加快速度。 ORDER BY 需要 TOP 100 PERCENT。
我首先声明几个 table 参数类型。
CREATE TYPE [api].[IdSet] AS TABLE
(
[Id] INT NOT NULL
);
并且
CREATE TYPE [api].[StringSet] AS TABLE
(
[Value] NVARCHAR(256) NOT NULL
);
然后我会更改存储过程的签名以使用它们。
注意我也return总计数作为输出参数而不是单独的结果集。
CREATE PROCEDURE [api].[Audit_V2_GetAuditDetails]
(
@userIds [api].[IdSet] READONLY,
@methodNames [api].[StringSet] READONLY,
@errorCodeIds [api].[IdSet] READONLY,
@fromDate DATETIME = NULL,
@toDate DATETIME = NULL,
@pageSize INT = 5,
@pageIndex INT = 0,
@totalCount BIGINT OUTPUT
)
我知道您可能仍需要进行 XML 提取,但如果您在 SP 之外进行提取将有助于查询规划器。
现在,在 SP 中,我不会使用 @PageDataResults
我只会获取页面的 ID。我也不会使用 CTE,这在这种情况下没有帮助。
我会简化查询并 运行 它一次来汇总总计数,然后如果它大于 0,运行 再次对相同的查询进行 return id 页面。查询的主体将由服务器内部缓存。
此外,Id' 使用 OFFSET
和 FETCH
分页对 ORDER BY
、
我在下面概述了一些逻辑上的简化,
CREATE PROCEDURE [api].[Audit_V2_GetAuditDetails]
(
@userIds [api].[IdSet] READONLY,
@methodNames [api].[StringSet] READONLY,
@errorCodeIds [api].[IdSet] READONLY,
@fromDate DATETIME = NULL,
@toDate DATETIME = NULL,
@pageSize INT = 5,
@pageIndex INT = 0,
@totalCount BIGINT OUTPUT
)
AS
DECLARE @offset INT = @pageSize * @pageIndex;
DECLARE @filterUsers BIT = 0;
DECLARE @filterMethods BIT = 0;
DECLARE @filterErrorCodes BIT = 0;
IF EXISTS (SELECT 0 FROM @userIds)
SET @filterUsers = 1;
IF EXISTS (SELECT 0 FROM @methodNames)
SET @filterMethods = 1;
IF EXISTS (SELECT 0 FROM @errorCodeIds)
SET @filterErrorCodes = 1;
SELECT
@totalCount = COUNT_BIG(*)
FROM
[dbo].[AuditUserMethods] [aum]
LEFT JOIN
@userIds [U]
ON [U].[Id] = [aum].[user_id]
LEFT JOIN
@methodName [M]
ON [M].[Value] = [aum].[ApiMethodName]
WHERE
(
@fromDate IS NULL
OR
[aum].[createddate] > @fromDate
)
AND
(
@toDate IS NULL
OR
[aum].[createddate] < @toDate
)
AND
(
@filterUsers = 0
OR
[U].[Id] IS NOT NULL
(
AND
(
@filterMethods = 0
OR
[M].[Value] IS NOT NULL
(
AND
(
@filterErrorCodes = 0
OR
(
EXISTS(
SELECT
1
FROM
[dbo].[AuditUserMethodErrorCodes] [e]
JOIN
@errorCodeIds [ec]
ON [ec].[Id] = [e].[ErrorCode]
WHERE
[e].[AuditUserMethod_Id] = [aum].[Id])
);
DECLARE @pageIds [api].[IdSet];
IF @totalCount > 0
INSERT @pageIds
SELECT
[aum].[id]
FROM
[dbo].[AuditUserMethods] [aum]
LEFT JOIN
@userIds [U]
ON [U].[Id] = [aum].[user_id]
LEFT JOIN
@methodName [M]
ON [M].[Value] = [aum].[ApiMethodName]
WHERE
(
@fromDate IS NULL
OR
[aum].[createddate] > @fromDate
)
AND
(
@toDate IS NULL
OR
[aum].[createddate] < @toDate
)
AND
(
@filterUsers = 0
OR
[U].[Id] IS NOT NULL
(
AND
(
@filterMethods = 0
OR
[M].[Value] IS NOT NULL
(
AND
(
@filterErrorCodes = 0
OR
(
EXISTS(
SELECT
1
FROM
[dbo].[AuditUserMethodErrorCodes] [e]
JOIN
@errorCodeIds [ec]
ON [ec].[Id] = [e].[ErrorCode]
WHERE
[e].[AuditUserMethod_Id] = [aum].[Id])
)
ORDER BY
[aum].[createddate] DESC,
[aum].[id] DESC
OFFSET @offset ROWS
FETCH NEXT @pageSize ROWS ONLY;
SELECT
[aum].[Id],
[aum].[CreatedDate],
[aum].[ApiMethodName],
[aum].[Request],
[aum].[Result],
[aum].[MethodId],
[aum].[UserId]
FROM
[dbo].[AuditUserMethods] [aum]
JOIN
@pageIds [i]
ON [i].[Id] = [aum].[id]
ORDER BY
[aum].[createddate] DESC,
[aum].[id] DESC;
SELECT
[aumec].[AuditUserMethod_Id],
[aumec].[ErrorCode]
FROM
[dbo].[AuditUserMethodErrorCodes] [aumec]
JOIN
@pageIds [i]
ON [i].[Id] = [aumec].[AuditUserMethod_Id];
/* The total count is an output parameter */
RETURN 0;
如果这不足以改善情况,您将需要查看查询计划并考虑最佳索引。
警告 所有代码都是即兴编写的,因此,虽然想法是正确的,但语法可能并不完美。
(@FromDate IS NULL OR
(@FromDate IS NOT NULL AND aum.createddate > @FromDate))
与
相同(@FromDate IS NULL OR aum.createddate > @FromDate)
尝试这样的事情
CREATE PROCEDURE [api].[Audit_V1_GetAuditDetails]
(
@Users XML = NULL,
@Methods XML = NULL,
@ErrorCodes XML = NULL,
@FromDate DATETIME = NULL,
@ToDate DATETIME = NULL,
@PageSize INT = 5,
@PageIndex INT = 0
)
AS
BEGIN
DECLARE @UserIds TABLE (Id INT)
DECLARE @MethodNames TABLE (Name NVARCHAR(256))
DECLARE @ErrorCodeIds TABLE (Id INT)
INSERT @UserIds
SELECT
x.y.value('.', 'int')
FROM
@Users.nodes('Ids/x/@i') AS x (y)
INSERT @MethodNames
SELECT
x.y.value('.', 'NVARCHAR(256)')
FROM
@Methods.nodes('ArrayOfString/string') AS x (y)
INSERT @ErrorCodeIds
SELECT
x.y.value('.', 'int')
FROM
@ErrorCodes.nodes('Ids/x/@i') AS x (y)
IF NOT EXISTS (SELECT TOP 1 0 FROM @UserIds)
INSERT INTO @UserIds values (-1)
IF NOT EXISTS (SELECT TOP 1 0 FROM @MethodNames)
INSERT INTO @MethodNames values ('empty')
IF NOT EXISTS (SELECT TOP 1 0 FROM @ErrorCodeIds)
INSERT INTO @ErrorCodeIds values (-1)
IF @FromDate is null
@FromDate = '1/1/1900'
IF @ToDate is null
@ToDate = '1/1/2079'
DECLARE @StartRow INT = @PageIndex * @Pagesize
DECLARE @PageDataResults TABLE (Id INT,
CreatedDate DATETIME,
ApiMethodName NVARCHAR(256),
Request NVARCHAR(MAX),
Result NVARCHAR(MAX),
MethodId INT,
UserId INT,
TotalRows INT);
WITH PageData AS
(
SELECT
id AS id
, createddate AS createddate
, apimethodname AS apimethodname
, request AS request
, result AS result
, method_id AS method_id
, user_id AS user_id
, ROW_NUMBER() OVER (ORDER BY createddate DESC, id DESC) AS row_number
, COUNT(*) OVER() as TotalRows
FROM
dbo.AuditUserMethods AS aum
JOIN @UserIds
ON (aum.user_id = @UserIds.ID OR @UserIds.ID = -1)
AND aum.createddate > @FromDate
AND aum.createddate < @ToDate
JOIN @MethodNames
ON aum.ApiMethodName = @MethodNames.Name
OR @MethodNames.Name = 'empty'
JOIN AuditUserMethodErrorCodes e
on e.AuditUserMethod_Id = aum.Id
JOIN @ErrorCodeIds
ON e.ErrorCode = @ErrorCodeIds.ID
OR @ErrorCodeIds.ID = -1
)