У меня проблема с производительностью с оператором select TOP (1) (или EXISTS) при объединении двух таблиц.Производительность TOP (1) выбрать на нескольких таблицах
Я использую SQL Server 2008 R2.
У меня есть 2 таблицы:
CREATE TABLE Records(
Id PRIMARY KEY INT NOT NULL,
User INT NOT NULL,
RecordType INT NOT NULL)
CREATE TABLE Values(
Id PRIMARY KEY BIGINT NOT NULL,
RecordId INT NOT NULL,
Field INT NOT NULL,
Value NVARCHAR(400) NOT NULL,
CONSTRAINT FK_Values_Record FOREIGN KEY(RecordId) REFERENCES Records(Id))
с индексами:
CREATE NONCLUSTERED INDEX IDX_Records ON Records(User ASC, RecordType ASC) INCLUDE(Id)
CREATE NONCLUSTERED INDEX IDX_Values ON Values(RecordId ASC, Field ASC) INCLUDE(Value)
CREATE NONCLUSTERED INDEX IDX_ValuesByVal ON Values(Field ASC, Value ASC) INCLUDE(RecordId)
Таблицы содержат много данных, около 100 миллионов записей в отчетах и 150 миллионов в ценности, и они по-прежнему растет. У некоторых пользователей много данных, некоторые - лишь небольшая сумма.
Для некоторых комбинаций пользователей и полей у нас не может быть записей в таблице значений, но для некоторых других пользователей/полей у нас почти столько записей в таблице значений, как в таблице записей этого пользователя.
Я хочу написать тестирование запроса, если у меня есть данные для комбинации пользователей и полей. Моя первая попытка была такова:
SELECT TOP(1) V.Field
FROM Records R
INNER JOIN Values V ON V.RecordId = R.Id
WHERE R.User = @User
AND R.RecordType = @RecordType
AND V.Field = @Field
Проблема с этим запросом было, что если план выполнения не в кэше сервера и первый пользователь не имеет много данных, сервер будет ставить план выполнения для этого запроса, который не очень хорошо работает для пользователя с большим количеством данных, что приводит к таймауту (более 15 секунд). Такая же проблема возникла и для RecordTypes или Fields. Поэтому мне пришлось жестко указывать идентификатор в запросе вместо использования переменных.
SELECT TOP(1) V.Field
FROM Records R
INNER JOIN Values V ON V.RecordId = R.Id
WHERE R.User = 123
AND R.RecordType = 45
AND V.Field = 67
Но даже тогда сервер когда-нибудь выполнит сканирование таблицы вместо использования доступных индексов, что также приведет к таймаутам. Таким образом, я должен был добавить FORCESEEK к запросу:
SELECT TOP(1) V.Field
FROM Records R WITH (FORCESEEK)
INNER JOIN Values V WITH (FORCESEEK) ON V.RecordId = R.Id
WHERE R.User = 123
AND R.RecordType = 45
AND V.Field = 67
Но даже сейчас, сервер иногда первый ищет в таблице рекордов, а затем в таблице значений, вместо первых поисков в таблице значений, а затем в отчетах таблицу, что также приводит к таймаутам. Я не знаю, почему это приводит к таймауту, но это так. Поскольку поля связаны с RecordType в моей модели, я мог бы удалить пункт RECORDTYPE, заставляя сервер первых поисков в таблице Значения
SELECT TOP(1) V.Field
FROM Records R WITH (FORCESEEK)
INNER JOIN Values V WITH (FORCESEEK) ON V.RecordId = R.Id
WHERE R.User = 123
AND V.Field = 67
С последним изменением я больше не имею какие-либо тайм-аутов, но по-прежнему запрос занимает от 1 до 2 секунд, иногда даже от 5 до 7 секунд.
Я до сих пор не понимаю, почему это занимает много времени.
Есть ли у кого-нибудь идеи, как улучшить этот запрос, чтобы избежать этих длительных запросов?
Сначала вам нужно сделать это имеет смысл. TOP без ORDER BY собирается возвратить некоторую неопределяемую строку, поэтому сначала вы должны решить это и ввести это в запрос. – LoztInSpace
Я использую TOP (1) как EXISTS (оба имеют один и тот же план выполнения), мне все равно, какая строка будет возвращена, любая строка будет делать. Добавление ORDER BY заставляет сервер выбирать все (тысячи) строки, соответствующие предложению WHERE, и сортировать их перед выполнением оператора TOP. – Marc
Вы можете использовать 'OPTION (RECOMPILE)' для повторной компиляции запроса для передаваемых значений. –