Запрос с индексом невероятно медленный при сортировке

У меня есть таблица базы данных с примерно 3,5 миллионами строк. Таблица содержит записи данных контракта с суммой, датой и некоторыми идентификаторами, связанными с другими таблицами (VendorId, AgencyId, StateId), это таблица базы данных:

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

Когда я запускаю это, требуется 50 секунд, будь то сортировка ASC или DESC или смещение на тысячи, это всегда 50 секунд.

Если я посмотрю на свой план выполнения, я увижу, что он использует мой индекс, но стоимость сортировки — это то, что делает запрос таким долгим.

Запрос с индексом невероятно медленный при сортировке

Это мой индекс:

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, и он возвращает результаты менее чем за секунду, даже на поставщике, у которого миллионы записей.

Что-то не так с моим индексом? Или сортировка по типу данных decimal просто невероятно интенсивна?

Я полагаю, что вы, возможно, включили индекс для столбца даты, но не для столбца суммы. Или вы говорите, что сумма упоминается только как включение этого индекса?

Paul Maxwell 18.03.2022 23:50

Похоже на какой-то разлив. Что именно говорится в предупреждении? Он будет запрашивать память только для сортировки предполагаемых 6,6 тыс. строк, но на самом деле сортирует 475 тыс. строк.

Martin Smith 18.03.2022 23:52

Итак, запрос использует ORDER BY [v].[ContractAmount] ASC ... но в ключах индекса нет ContractAmount? Включаемые столбцы не являются частью ключей индекса, поэтому он должен вручную сортировать промежуточный набор результатов в памяти.

AlwaysLearning 19.03.2022 00:36

@AlwaysLearning единственный способ, которым индекс поддерживал бы сортировку, был бы в том случае, если OP должен был отказаться от вторичного поиска в ContractDate и просто согласиться на поиск равенства для VendorId и остаточного предиката. В настоящее время поиск будет возвращать точные строки для поставщика в диапазоне дат, но добавление третьего ключевого столбца не позволит избежать сортировки, поскольку третичная сортировка будет применяться только к строкам с одинаковыми значениями VendorId,ContractDate.

Martin Smith 19.03.2022 00:50

@MartinSmith - я думаю, вы правы насчет сброса сортировки, в предупреждении говорится: «Оператор использовал tempdb для сброса данных во время выполнения с уровнем сброса 1 и потоками 1, Sort записал 10299 страниц и прочитал 10299 страниц из tempdb с помощью предоставлено памяти 67088 КБ и использовано памяти 67088 КБ" Вот предупреждение в xml: <Warnings><SpillToTempDb SpillLevel = "1" SpilledThreadCount = "1" /><SortSpillDetails GrantedMemoryKb = "67088" UsedMemoryKb = "67088" WritesToTempDb = "10299" ReadsFromTempDb = "10299" /></Warnings> Будет ли полезна какая-либо другая информация?

Steven 19.03.2022 01:18
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
1
5
57
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

У вас есть индекс, который позволяет

VendorId = @__vendorId_0 and ContractDate BETWEEN @__startDate_1 AND @__endDate_2

предикат, который нужно искать точно.

По оценкам SQL Server, этому предикату будут соответствовать 6 657 строк, которые необходимо отсортировать, чтобы запросить выделение памяти, подходящее для этого количества строк.

На самом деле для значений параметров, где вы видите проблему, отсортировано почти полмиллиона, а выделение памяти недостаточно, и сортировка сливается на диск.

50 секунд для 10 299 пропущенных страниц все еще звучат неожиданно медленно, но я предполагаю, что вы вполне можете иметь очень низкий SKU в базе данных SQL Azure?

Некоторые возможные решения для решения проблемы могут заключаться в следующем:

  1. Заставьте его использовать план выполнения, скомпилированный для значений параметров с вашим крупнейшим поставщиком и широким диапазоном дат (например, с подсказкой OPTIMIZE FOR). Это будет означать чрезмерное выделение памяти для небольших поставщиков, хотя это может означать, что другие запросы должны подвергаться ожиданиям выделения памяти.
  2. Используйте OPTION (RECOMPILE), чтобы каждый вызов перекомпилировался для переданных значений определенных параметров. Это означает, что теоретически каждое выполнение будет получать соответствующее выделение памяти за счет большего времени, затрачиваемого на компиляцию.
  3. Уберите необходимость в сортировке вообще. Если у вас есть индекс на VendorId, ContractAmount INCLUDE (ContractDate), то можно искать часть VendorId = @__vendorId_0 и читать индекс в порядке ContractAmount. Как только будет найдено 50 строк, соответствующих предикату ContractDate BETWEEN @__startDate_1 AND @__endDate_2, выполнение запроса может быть остановлено. Однако SQL Server может не выбрать этот план выполнения без подсказок.

Я не уверен, насколько легко или иначе применять подсказки запросов через EF, но вы можете посмотреть на форсирование плана через хранилище запросов, если вам удастся получить там желаемый план.

Да, это было именно так, я был на самом низком уровне в Azure, я поднял его, и теперь он отлично работает.

Steven 19.03.2022 22:08

Другие вопросы по теме