У меня есть таблица базы данных с примерно 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
просто невероятно интенсивна?
Похоже на какой-то разлив. Что именно говорится в предупреждении? Он будет запрашивать память только для сортировки предполагаемых 6,6 тыс. строк, но на самом деле сортирует 475 тыс. строк.
Итак, запрос использует ORDER BY [v].[ContractAmount] ASC
... но в ключах индекса нет ContractAmount
? Включаемые столбцы не являются частью ключей индекса, поэтому он должен вручную сортировать промежуточный набор результатов в памяти.
@AlwaysLearning единственный способ, которым индекс поддерживал бы сортировку, был бы в том случае, если OP должен был отказаться от вторичного поиска в ContractDate и просто согласиться на поиск равенства для VendorId и остаточного предиката. В настоящее время поиск будет возвращать точные строки для поставщика в диапазоне дат, но добавление третьего ключевого столбца не позволит избежать сортировки, поскольку третичная сортировка будет применяться только к строкам с одинаковыми значениями VendorId,ContractDate.
@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>
Будет ли полезна какая-либо другая информация?
У вас есть индекс, который позволяет
VendorId = @__vendorId_0 and ContractDate BETWEEN @__startDate_1 AND @__endDate_2
предикат, который нужно искать точно.
По оценкам SQL Server, этому предикату будут соответствовать 6 657 строк, которые необходимо отсортировать, чтобы запросить выделение памяти, подходящее для этого количества строк.
На самом деле для значений параметров, где вы видите проблему, отсортировано почти полмиллиона, а выделение памяти недостаточно, и сортировка сливается на диск.
50 секунд для 10 299 пропущенных страниц все еще звучат неожиданно медленно, но я предполагаю, что вы вполне можете иметь очень низкий SKU в базе данных SQL Azure?
Некоторые возможные решения для решения проблемы могут заключаться в следующем:
OPTIMIZE FOR
). Это будет означать чрезмерное выделение памяти для небольших поставщиков, хотя это может означать, что другие запросы должны подвергаться ожиданиям выделения памяти.OPTION (RECOMPILE)
, чтобы каждый вызов перекомпилировался для переданных значений определенных параметров. Это означает, что теоретически каждое выполнение будет получать соответствующее выделение памяти за счет большего времени, затрачиваемого на компиляцию.VendorId, ContractAmount INCLUDE (ContractDate)
, то можно искать часть VendorId = @__vendorId_0
и читать индекс в порядке ContractAmount
. Как только будет найдено 50 строк, соответствующих предикату ContractDate BETWEEN @__startDate_1 AND @__endDate_2
, выполнение запроса может быть остановлено. Однако SQL Server может не выбрать этот план выполнения без подсказок.Я не уверен, насколько легко или иначе применять подсказки запросов через EF, но вы можете посмотреть на форсирование плана через хранилище запросов, если вам удастся получить там желаемый план.
Да, это было именно так, я был на самом низком уровне в Azure, я поднял его, и теперь он отлично работает.
Я полагаю, что вы, возможно, включили индекс для столбца даты, но не для столбца суммы. Или вы говорите, что сумма упоминается только как включение этого индекса?