在 Sort 上使用索引查询非常慢
Query with index incredibly slow on Sort
我有一个包含大约 350 万行的数据库 table。 table 持有合同数据记录,有金额、日期和一些与其他 table 相关的 ID(VendorId、AgencyId、StateId),这是数据库 table:
CREATE TABLE [dbo].[VendorContracts]
(
[Id] [uniqueidentifier] NOT NULL,
[ContractDate] [datetime2](7) NOT NULL,
[ContractAmount] [decimal](19, 4) NULL,
[VendorId] [uniqueidentifier] NOT NULL,
[AgencyId] [uniqueidentifier] NOT NULL,
[StateId] [uniqueidentifier] NOT NULL,
[CreatedBy] [nvarchar](max) NULL,
[CreatedDate] [datetime2](7) NOT NULL,
[LastModifiedBy] [nvarchar](max) NULL,
[LastModifiedDate] [datetime2](7) NULL,
[IsActive] [bit] NOT NULL,
CONSTRAINT [PK_VendorContracts]
PRIMARY KEY CLUSTERED ([Id] ASC)
WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
我的站点上有一个页面,用户可以在其中按 VendorId
和 ContractDate
过滤分页网格,并按 ContractAmount
或 ContractDate
排序。这是 EF Core 在按 ContractAmount
为拥有超过一百万条记录的特定供应商排序时生成的查询:
DECLARE @__vendorId_0 uniqueIdentifier = 'f39c7198-b05a-477e-b7bc-cb189c5944c0';
DECLARE @__startDate_1 datetime2 = '2017-01-01T07:00:00.0000000';
DECLARE @__endDate_2 datetime2 = '2018-01-02T06:59:59.0000000';
DECLARE @__p_3 int = 0;
DECLARE @__p_4 int = 50;
SELECT [v].[Id], [v].[AdminFee], [v].[ContractAmount], [v].[ContractDate], [v].[PONumber], [v].[PostalCode], [v].[AgencyId], [v].[StateId], [v].[VendorId]
FROM [VendorContracts] AS [v]
WHERE (([v].[VendorId] = @__vendorId_0) AND ([v].[ContractDate] >= @__startDate_1)) AND ([v].[ContractDate] <= @__endDate_2)
ORDER BY [v].[ContractAmount] ASC
OFFSET @__p_3 ROWS FETCH NEXT @__p_4 ROWS ONLY
我运行这个的时候,需要50s,不管是排序ASC
还是DESC
,还是按千位偏移,都是50s。
如果我查看我的执行计划,我发现它确实使用了我的索引,但排序成本是导致查询花费这么长时间的原因
这是我的索引:
CREATE NONCLUSTERED INDEX [IX_VendorContracts_VendorIdAndContractDate] ON [dbo].[VendorContracts]
(
[VendorId] ASC,
[ContractDate] DESC
)
INCLUDE([ContractAmount],[AdminFee],[PONumber],[PostalCode],[AgencyId],[StateId])
WITH (STATISTICS_NORECOMPUTE = OFF, DROP_EXISTING = OFF, ONLINE = OFF, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF)
奇怪的是,我有一个类似的按 ContractDate
排序的索引,而那个 returns 结果不到一秒钟,即使在拥有数百万条记录的供应商上也是如此。
我的索引有问题吗?还是按 decimal
数据类型排序非常密集?
您的索引允许
VendorId = @__vendorId_0 and ContractDate BETWEEN @__startDate_1 AND @__endDate_2
要精确查找的谓词。
SQL 服务器估计有 6,657 行将匹配此谓词并需要排序,因此它请求适合该行数的内存授予。
实际上,对于您看到问题的参数值,将近 50 万个已排序,内存授予不足,排序溢出到磁盘。
10,299 个溢出页面 50 秒听起来仍然出乎意料地慢,但我认为您可能在 Azure SQL 数据库中使用了一些非常低的 SKU?
解决该问题的一些可能的解决方案可能是
- 强制它使用为最大供应商和广泛日期范围的参数值编译的执行计划(例如
OPTIMIZE FOR
提示)。这将意味着对较小供应商的过多内存授予,但这可能意味着其他查询必须等待内存授予。
- 使用
OPTION (RECOMPILE)
以便为传递的特定参数值重新编译每个调用。这意味着理论上每次执行都将获得适当的内存授予,但代价是花费更多的编译时间。
- 完全不需要排序。如果您在
VendorId, ContractAmount INCLUDE (ContractDate)
上有一个索引,那么可以查找 VendorId = @__vendorId_0
部分并按 ContractAmount
顺序读取索引。一旦找到与 ContractDate BETWEEN @__startDate_1 AND @__endDate_2
谓词匹配的 50 行,查询就可以停止执行。 SQL 服务器可能不会在没有提示的情况下选择此执行计划。
我不确定通过 EF 应用查询提示有多简单,但如果您设法让所需的计划出现在那里,您可以考虑通过查询存储强制执行计划。
我有一个包含大约 350 万行的数据库 table。 table 持有合同数据记录,有金额、日期和一些与其他 table 相关的 ID(VendorId、AgencyId、StateId),这是数据库 table:
CREATE TABLE [dbo].[VendorContracts]
(
[Id] [uniqueidentifier] NOT NULL,
[ContractDate] [datetime2](7) NOT NULL,
[ContractAmount] [decimal](19, 4) NULL,
[VendorId] [uniqueidentifier] NOT NULL,
[AgencyId] [uniqueidentifier] NOT NULL,
[StateId] [uniqueidentifier] NOT NULL,
[CreatedBy] [nvarchar](max) NULL,
[CreatedDate] [datetime2](7) NOT NULL,
[LastModifiedBy] [nvarchar](max) NULL,
[LastModifiedDate] [datetime2](7) NULL,
[IsActive] [bit] NOT NULL,
CONSTRAINT [PK_VendorContracts]
PRIMARY KEY CLUSTERED ([Id] ASC)
WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
我的站点上有一个页面,用户可以在其中按 VendorId
和 ContractDate
过滤分页网格,并按 ContractAmount
或 ContractDate
排序。这是 EF Core 在按 ContractAmount
为拥有超过一百万条记录的特定供应商排序时生成的查询:
DECLARE @__vendorId_0 uniqueIdentifier = 'f39c7198-b05a-477e-b7bc-cb189c5944c0';
DECLARE @__startDate_1 datetime2 = '2017-01-01T07:00:00.0000000';
DECLARE @__endDate_2 datetime2 = '2018-01-02T06:59:59.0000000';
DECLARE @__p_3 int = 0;
DECLARE @__p_4 int = 50;
SELECT [v].[Id], [v].[AdminFee], [v].[ContractAmount], [v].[ContractDate], [v].[PONumber], [v].[PostalCode], [v].[AgencyId], [v].[StateId], [v].[VendorId]
FROM [VendorContracts] AS [v]
WHERE (([v].[VendorId] = @__vendorId_0) AND ([v].[ContractDate] >= @__startDate_1)) AND ([v].[ContractDate] <= @__endDate_2)
ORDER BY [v].[ContractAmount] ASC
OFFSET @__p_3 ROWS FETCH NEXT @__p_4 ROWS ONLY
我运行这个的时候,需要50s,不管是排序ASC
还是DESC
,还是按千位偏移,都是50s。
如果我查看我的执行计划,我发现它确实使用了我的索引,但排序成本是导致查询花费这么长时间的原因
这是我的索引:
CREATE NONCLUSTERED INDEX [IX_VendorContracts_VendorIdAndContractDate] ON [dbo].[VendorContracts]
(
[VendorId] ASC,
[ContractDate] DESC
)
INCLUDE([ContractAmount],[AdminFee],[PONumber],[PostalCode],[AgencyId],[StateId])
WITH (STATISTICS_NORECOMPUTE = OFF, DROP_EXISTING = OFF, ONLINE = OFF, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF)
奇怪的是,我有一个类似的按 ContractDate
排序的索引,而那个 returns 结果不到一秒钟,即使在拥有数百万条记录的供应商上也是如此。
我的索引有问题吗?还是按 decimal
数据类型排序非常密集?
您的索引允许
VendorId = @__vendorId_0 and ContractDate BETWEEN @__startDate_1 AND @__endDate_2
要精确查找的谓词。
SQL 服务器估计有 6,657 行将匹配此谓词并需要排序,因此它请求适合该行数的内存授予。
实际上,对于您看到问题的参数值,将近 50 万个已排序,内存授予不足,排序溢出到磁盘。
10,299 个溢出页面 50 秒听起来仍然出乎意料地慢,但我认为您可能在 Azure SQL 数据库中使用了一些非常低的 SKU?
解决该问题的一些可能的解决方案可能是
- 强制它使用为最大供应商和广泛日期范围的参数值编译的执行计划(例如
OPTIMIZE FOR
提示)。这将意味着对较小供应商的过多内存授予,但这可能意味着其他查询必须等待内存授予。 - 使用
OPTION (RECOMPILE)
以便为传递的特定参数值重新编译每个调用。这意味着理论上每次执行都将获得适当的内存授予,但代价是花费更多的编译时间。 - 完全不需要排序。如果您在
VendorId, ContractAmount INCLUDE (ContractDate)
上有一个索引,那么可以查找VendorId = @__vendorId_0
部分并按ContractAmount
顺序读取索引。一旦找到与ContractDate BETWEEN @__startDate_1 AND @__endDate_2
谓词匹配的 50 行,查询就可以停止执行。 SQL 服务器可能不会在没有提示的情况下选择此执行计划。
我不确定通过 EF 应用查询提示有多简单,但如果您设法让所需的计划出现在那里,您可以考虑通过查询存储强制执行计划。