Как можно улучшить оценки строк, чтобы уменьшить вероятность разливов в базу данных

11

Я замечаю, что когда происходят события разлива в базу данных tempdb (вызывающие медленные запросы), часто оценки строк оказываются не подходящими для конкретного соединения. Я видел события разлива с объединениями и хэш-соединениями, и они часто увеличивают время выполнения в 3 раза до 10 раз. Этот вопрос касается того, как улучшить оценку строк при условии, что это уменьшит вероятность разливов.

Фактическое количество рядов 40к.

Для этого запроса план показывает неверную оценку строки (11,3 строки):

select Value
  from Oav.ValueArray
 where ObjectId = (select convert(bigint, Value) NodeId
                     from Oav.ValueArray
                    where PropertyId = 3331  
                      and ObjectId = 3540233
                      and Sequence = 2)
   and PropertyId = 2840
option (recompile);

Для этого запроса план показывает хорошую оценку строк (56 тыс. Строк):

declare @a bigint = (select convert(bigint, Value) NodeId
                       from Oav.ValueArray
                      where PropertyId = 3331
                        and ObjectId = 3540233
                        and Sequence = 2);

select Value
  from Oav.ValueArray
 where ObjectId = @a               
   and PropertyId = 2840
option (recompile);

Можно ли добавить статистику или подсказки для улучшения оценок строк в первом случае? Я попытался добавить статистику с определенными значениями фильтра (свойство = 2840), но либо не смог получить правильную комбинацию, либо, возможно, он игнорируется, потому что ObjectId неизвестен во время компиляции и может выбирать среднее значение для всех ObjectIds.

Есть ли какой-нибудь режим, в котором он сначала выполняет тестовый запрос, а затем использует его для определения оценок строк, или он должен летать вслепую?

Это конкретное свойство имеет много значений (40 КБ) для нескольких объектов и ноль для подавляющего большинства. Я был бы счастлив с подсказкой, где можно указать максимальное ожидаемое количество строк для данного объединения. Это обычно преследующая проблема, потому что некоторые параметры могут быть определены динамически как часть объединения или будут лучше размещены в представлении (без поддержки переменных).

Есть ли какие-либо параметры, которые можно настроить, чтобы минимизировать вероятность разлива в базу данных (например, минимальное количество памяти на запрос)? Надежный план не оказал влияния на оценку.

Изменить 2013.11.06 : Ответ на комментарии и дополнительную информацию:

Вот изображения плана запроса. Предупреждения о предикате кардинальности / поиска с помощью convert ():

введите описание изображения здесь введите описание изображения здесь

Согласно комментарию @ Аарона Бертранда, я попытался заменить convert () в качестве теста:

create table Oav.SeekObject (
       LookupId bigint not null primary key,
       ObjectId bigint not null
);

insert into Oav.SeekObject (
   LookupId, ObjectId
) VALUES (
   1, 3540233
) 

select Value
  from Oav.ValueArray
 where ObjectId = (select ObjectId 
                     from Oav.SeekObject 
                    where LookupId = 1)
   and PropertyId = 2840
option (recompile);

введите описание изображения здесь

Как странный, но успешный интерес, он также позволил замкнуть поиск:

select Value
  from Oav.ValueArray
 where ObjectId = (select ObjectId 
                     from Oav.ValueArray
                    where PropertyId = 2840
                      and ObjectId = 3540233
                      and Sequence = 2)
   and PropertyId = 2840
option (recompile);

введите описание изображения здесь

Оба из них перечисляют правильный поиск ключа, но только первые перечисляют «Выход» ObjectId. Я думаю, это указывает на то, что второе действительно короткое замыкание?

Может ли кто-нибудь проверить, выполняются ли когда-нибудь однорядные исследования, чтобы помочь с оценками строк? Кажется неправильным ограничивать оптимизацию только оценками гистограммы, когда однострочный поиск PK может значительно повысить точность поиска в гистограмме (особенно, если есть вероятность разлива или история). Когда в реальном запросе есть 10 таких вложенных соединений, в идеале они должны выполняться параллельно.

Дополнительное замечание: поскольку sql_variant хранит свой базовый тип (SQL_VARIANT_PROPERTY = BaseType) в самом поле, я ожидаю, что convert () будет почти бесплатным, если он «непосредственно» конвертируем (например, не строка в десятичную, а скорее int в int или возможно int to bigint). Так как это не известно во время компиляции, но может быть известно пользователю, возможно, функция «AssumeType (type, ...)» для sql_variants позволила бы обрабатывать их более прозрачно.

crokusek
источник
1
Первое предположение заключается в том, что преобразование в bigint отбрасывает ваши оценки (план запроса будет содержать предупреждение об этом в SQL Server 2012), но, с другой стороны, ваш подзапрос никогда не сможет вернуть ничего, кроме 0 или 1 строки для успешного запроса. Было бы интересно увидеть планы запросов, которые у вас есть. Возможно, как ссылка на версию XML.
Микаэль Эрикссон
2
Что вы получаете, имея вложенный подзапрос? Я бы посоветовал вытащить его отдельно, что в целом яснее, и, поскольку это приводит к лучшим оценкам, почему бы просто не использовать этот метод?
Аарон Бертран
2
Какая версия SQL Server? Можете ли вы предоставить таблицы и индексы DDL и статистические двоичные объекты (одно- и многоколонные) для таблиц, чтобы мы могли видеть детали проблемы? Разделение запроса с использованием того, declare @a bigint = что вы сделали, кажется мне естественным решением, почему это недопустимо?
Пол Уайт 9
2
Я предполагаю, что ваш дизайн (очень упрощенный) дизайн EAV, который заставляет вас использовать CONVERT()в столбцах, а затем присоединиться к ним. Это, конечно, не эффективно в большинстве случаев. В этом конкретном случае необходимо преобразовать только одно значение, так что, вероятно, это не проблема, но какие индексы у вас в таблице? Проекты EAV обычно работают хорошо, только при правильной индексации (что означает много индексов в обычно узких таблицах).
ypercubeᵀᴹ
@ Пол Уайт, если разделиться ... это хорошо, как решение для этого случая. Но для более общего / сложного я в основном не хочу отказываться от распараллеливания и читабельности. Скажем, у меня есть 10 из них как подзапросы (некоторые из них более сложные) в запросе, но только 5 должны быть «созрели», прежде чем остальная часть запроса может начаться - хотелось бы избежать выяснения, какие 5 из них.
Crokusek

Ответы:

7

Я не буду комментировать разливы, tempdb или подсказки, потому что запрос кажется довольно простым и требует особого внимания. Я думаю, что оптимизатор SQL-сервера хорошо справится со своей задачей, если для запроса найдутся индексы.

И ваше разбиение на два запроса хорошо, поскольку оно показывает, какие индексы будут полезны. Первая часть:

(select convert(bigint, Value) NodeId
 from Oav.ValueArray
 where PropertyId = 3331  
   and ObjectId = 3540233
   and Sequence = 2)

нужен индекс на (PropertyId, ObjectId, Sequence)включение Value. Я бы сделал это, UNIQUEчтобы быть в безопасности. Запрос все равно выдаст ошибку во время выполнения, если будет возвращено более одной строки, поэтому хорошо заранее убедиться, что этого не произойдет, с уникальным индексом:

CREATE UNIQUE INDEX
    PropertyId_ObjectId_Sequence_UQ
  ON Oav.ValueArray
    (PropertyId, ObjectId, Sequence) INCLUDE (Value) ;

Вторая часть запроса:

select Value
  from Oav.ValueArray
 where ObjectId = @a               
   and PropertyId = 2840

нужен индекс на (PropertyId, ObjectId)включение Value:

CREATE INDEX
    PropertyId_ObjectId_IX
  ON Oav.ValueArray
    (PropertyId, ObjectId) INCLUDE (Value) ;

Если эффективность не улучшится, или эти индексы не использовались, или все еще появляются различия в оценках строк, то необходимо будет дополнительно изучить этот запрос.

В этом случае вероятной причиной является преобразование (необходимое из схемы EAV и хранение разных типов данных в одних столбцах), и ваше решение разбить (как @AAron Bertrand и @Paul White) запрос на две части кажется естественным и путь. Редизайн, чтобы иметь разные типы данных в соответствующих столбцах, может быть другим.

ypercubeᵀᴹ
источник
Таблицы имели закрывающие индексы - я должен был указать это в вопросе. Пример на самом деле представляет собой подсоединение, обеспечивающее более крупный запрос, поэтому существует много шума по поводу разливов базы данных tempdb.
Crokusek
5

Как частичный ответ на явный вопрос по улучшению статистики ...

Обратите внимание, что оценки строк даже для случая с разбивкой по-отдельности по-прежнему отклоняются в 10 раз (4 К против ожидаемых 40 К).

Гистограмма статистики, вероятно, была слишком тонкой для этого свойства, потому что это длинная (вертикальная) таблица из 3,5 млн. Строк, а это конкретное свойство крайне редкое.

Создайте дополнительную статистику (несколько избыточную со статистикой IX) для разреженного свойства:

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840

Оригиналы:

введите описание изображения здесь введите описание изображения здесь

С удаленным convert () (правильно):

введите описание изображения здесь

С удаленным convert () (короткое замыкание):

введите описание изображения здесь

Тем не менее, в ~ 2 раза вероятно, потому что> 99,9% объектов вообще не имеют свойства 2840, определенного на них. Фактически, только для этого тестового случая свойство существует только на 1 из 200 000 различных объектов таблицы строк размером 3,5 МБ. Удивительно, но так близко. Настраивая фильтр на меньшее количество ObjectIds,

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 and ObjectId >= 3540000 and ObjectId < 3541000

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 and ObjectId = 3540233;

Хм, без изменений ... Подчеркнул, что добавлено «с полной проверкой» в конце статистики (может быть, поэтому предыдущие два не сработали) и да:

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 with full scan;

введите описание изображения здесь

Ура. Таким образом, в сильно вертикальной таблице с широким охватом IX добавление дополнительной отфильтрованной статистики представляется большим улучшением (особенно для разреженных, но сильно изменяющихся комбинаций клавиш).

crokusek
источник
Ссылка на некоторые проблемные вопросы с многостолбцовой статистикой.
crokusek