在 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]

我的站点上有一个页面,用户可以在其中按 VendorIdContractDate 过滤分页网格,并按 ContractAmountContractDate 排序。这是 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?

解决该问题的一些可能的解决方案可能是

  1. 强制它使用为最大供应商和广泛日期范围的参数值编译的执行计划(例如 OPTIMIZE FOR 提示)。这将意味着对较小供应商的过多内存授予,但这可能意味着其他查询必须等待内存授予。
  2. 使用OPTION (RECOMPILE) 以便为传递的特定参数值重新编译每个调用。这意味着理论上每次执行都将获得适当的内存授予,但代价是花费更多的编译时间。
  3. 完全不需要排序。如果您在 VendorId, ContractAmount INCLUDE (ContractDate) 上有一个索引,那么可以查找 VendorId = @__vendorId_0 部分并按 ContractAmount 顺序读取索引。一旦找到与 ContractDate BETWEEN @__startDate_1 AND @__endDate_2 谓词匹配的 50 行,查询就可以停止执行。 SQL 服务器可能不会在没有提示的情况下选择此执行计划。

我不确定通过 EF 应用查询提示有多简单,但如果您设法让所需的计划出现在那里,您可以考虑通过查询存储强制执行计划。