Медлительность при индексированном представлении для SQL 2005

Скажем, у меня есть очень длинная таблица (~ 35 миллионов строк) под названием TimeCard только с 5 столбцами (tableID, CompanyID, UserID, ProjectID, DailyHoursWorked, entryDate). Это довольно простая таблица, в которой записано количество отработанных часов сотрудников в день для каждого проекта в каждой компании.

Теперь мне нужно создать отчет, чтобы узнать общее количество отработанных часов сотрудников в месяц по проекту для любой данной компании. Вместо выполнения агрегирования, необходимого при запуске отчета, я хочу создать табличную структуру данных, в которой уже есть все данные о компании/проекте/пользователе, агрегированные по месяцам, поэтому при запуске отчета я могу просто запросить эту структуру данных напрямую. без выполнения какой-либо агрегации во время выполнения, поскольку ~ 35 миллионов записей может занять несколько минут.

Так что у меня есть 2 разных пути. Один создает дополнительную физическую таблицу с (CompanyID, UserID, ProjectID, MonthlyHoursWorked, Month) в качестве моих столбцов и просто использует триггер в таблице TimeCard для изменения значений в дополнительной таблице. Или я могу создать индексированное представление. Так что я попробовал оба. Сначала я попробовал индексированное представление со следующим кодом:

CREATE VIEW [dbo].[vw_myView] WITH SCHEMABINDING AS
SELECT 
 JobID,
 ProjectID,
 Sum(DailyHoursWorked) AS MonthTotal,
 DATEADD( Month, DATEDIFF( Month, 0, entryDate), 0 ) AS entryMonth,
 CompanyID,
 COUNT_BIG(*) AS Counter
FROM
 dbo.TimeCard 
Group By DATEADD( Month, DATEDIFF( Month, 0, entryDate ), 0 ), JobID, ProjectID, CompanyID

Go
CREATE UNIQUE CLUSTERED INDEX [IX_someIndex] ON [dbo].[vw_myView] 
(
 [CompanyID] ASC,
 [entryMonth] ASC,
 [UserID] ASC,
 [ProjectID] ASC
)

Индексированное представление создано правильно и содержит около 5 миллионов строк.

Однако каждый раз, когда я очищаю кеш SQL и запускаю следующий запрос: *select * from vw_myView, где companyID = 1*, это занимает почти 3 минуты. Если я выберу дополнительный маршрут таблицы, как я упоминал выше, с очищенным кешем, это займет около 4 секунд.

У меня возникает вопрос: является ли индексированный просмотр плохим выбором для этого конкретного сценария? В частности, мне интересно знать, пересчитывается/повторно агрегируется ли все индексированное представление каждый раз, когда изменяется базовая таблица (TimeCard) или когда к ней выполняется запрос?

Спасибо!


person TheYouth    schedule 10.03.2010    source источник
comment
Какой выпуск SQL Server 2005 вы используете?   -  person D'Arcy Rittich    schedule 10.03.2010
comment
Вместо того, чтобы иметь полную дату первого числа месяца в вашем entryMonth, не могли бы вы просто иметь MONTH(entryDate) и, возможно, YEAR(entryDate) как INT? Мне кажется намного проще (но опять же - я не знаю ваших точных требований)....   -  person marc_s    schedule 10.03.2010


Ответы (6)


Если вы не используете ни версию Enterprise, ни версию Developer, вам нужно использовать подсказку with (noexpand):

select * 
from vw_myView with (noexpand)
where companyID = 1

Когда базовые данные изменяются, представление будет обновлять только строки, связанные с измененными данными, а не всю таблицу. Это может отрицательно сказаться на базе данных OLTP с высокой степенью вставок, но при умеренном использовании не должно вызывать проблем с производительностью.

совет от Microsoft:

Как правило, любые модификации или обновления представления или базовых таблиц, лежащих в основе представления, следует по возможности выполнять пакетами, а не отдельными операциями. Это может снизить некоторые накладные расходы на обслуживание представления.

person D'Arcy Rittich    schedule 10.03.2010
comment
Чего я не понимаю, так это почему для простого запроса требуется 3 минуты, если я очищаю кеш и ни одно из значений базовой таблицы не изменилось. Действительно ли он снова выполняет всю агрегацию? - person TheYouth; 10.03.2010
comment
@TheYouth: ты пробовал подсказку with (noexpand)? - person D'Arcy Rittich; 10.03.2010
comment
+1, если вы забудете добавить WITH (NOEXPAND) в версиях SQL Server EXPRESS или DEVELOPER, оптимизатор не будет использовать индексированное представление, а будет выбирать из базовых таблиц. - person Steven de Salas; 07.02.2011

Я думаю, что вы на правильном пути с использованием представления индекса. Однако поместили ли вы индексы в таблицу, из которой вы запрашиваете, TimeCard для ваших совокупных столбцов. Вам нужно сделать индекс JobID, ProjectID, entryDate, CompanyID (1 индекс). Если вы используете 1 индекс для каждого столбца, это НЕ решит ваши проблемы, потому что Query должен будет использовать все 4 индекса вместе.

Я думаю, что использование триггера будет медленным, но по-другому. Это ускорит ваш запрос, но замедлит каждую вставку, которую вы делаете в TimeCard. Если вы решите пойти с триггером, я бы позаботился о том, чтобы индексировать и эту таблицу, или это может быть медленным, не медленным на 3 минуты, но все же медленным для сортировки и возврата данных.

person Ben Hoffman    schedule 10.03.2010
comment
Я не вижу, как помогут дополнительные индексы, запрос выбирает по CompanyID, который уже проиндексирован. - person D'Arcy Rittich; 10.03.2010
comment
TimeCard индексируется с помощью entryDate, companyID, userID, projectID, все в отдельных некластеризованных индексах. Чего я не понимаю, так это почему для простого запроса требуется 3 минуты, если я очищаю кеш. Действительно ли он снова выполняет всю агрегацию? - person TheYouth; 10.03.2010
comment
Я думаю, что это. Однако, я думаю, было бы лучше, если бы у вас был 1 некластерный индекс для entryDate, companyID, userID, projectID. Наличие 4 индексов, по одному для каждого из этих столбцов, на самом деле вам не поможет, потому что ваша группа использует все 4 вместе. Итак, вам нужен 1 индекс, в котором есть все 4 столбца. - person Ben Hoffman; 10.03.2010
comment
Я думаю, что здесь есть некоторая путаница — group by в приведенном выше DDL используется только при создании индексированного представления, а не при выборе данных из представления, поэтому размещение индексов в представлении на основе того, что находится в предложении group by, не имеет смысла. - person D'Arcy Rittich; 10.03.2010

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

person HLGEM    schedule 10.03.2010
comment
Представления часто медленнее, чем триггеры (особенно если вы накладываете их друг на друга), и триггер можно правильно написать так, чтобы он работал быстро. - person HLGEM; 11.03.2010

Я не думаю, что вам нужно индексированное представление (я не говорю, что индексированное представление — плохая/хорошая идея). Я думаю, вам нужен индекс по столбцам "CompanyID" и "EntryDate". После этого вы должны использовать условие where «ГДЕ CompanyID = @CompanyID AND EntryDate >= @StartDate AND EntryDate ‹= @EndDate».

Если таблица обрабатывается преимущественно с помощью «EntryDate», вы можете использовать индекс кластера в столбце «EntryDate».

После этого я думаю, что оператор select будет работать намного быстрее, чем сейчас.

person TcKs    schedule 10.03.2010
comment
кластеризованный индекс в представлении уже использует CompanyID в качестве первого столбца - это действительно должно помочь. Я не вижу никакой пользы в индексировании по (CompanyID, EntryDate) — кажется, нет никаких запросов, использующих EntryDate, так какой смысл его индексировать? - person marc_s; 10.03.2010
comment
TimeCard индексируется с помощью entryDate, companyID, userID, projectID, все в отдельных некластеризованных индексах. Чего я не понимаю, так это почему для простого запроса требуется 3 минуты, если я очищаю кеш. Действительно ли он снова выполняет всю агрегацию? - person TheYouth; 10.03.2010
comment
Вы должны отфильтровать его по EntryDate. Если вы знаете, что у вас уже есть сводная информация о январе 2010 г., вам не нужно снова выбирать ti из основной таблицы. Если вы используете индекс кластера в столбце EntryDate (без CompanyID), вы должны повысить производительность для выбора на основе этого столбца. - person TcKs; 10.03.2010

Вы рассматривали возможность разбиения таблицы. Вы можете придумать комбинацию списка и хеш-таблицы разделов.

person user284534    schedule 10.03.2010

Что ж, идея с индексированным представлением определенно хороша, а если на нем можно создать кластеризованный индекс — отлично. Это должно быть быстро - гораздо лучше, чем 3 минуты на запрос!

С другой стороны: если эти фрагменты информации только когда-либо обновляются, например. раз в месяц или раз в неделю (или даже каждую ночь), может быть лучше просто поместить их в отдельную таблицу DailyTimeCard, которая заполняется/обновляется, например. пакет SSIS регулярно.

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

Но ваше индексированное представление выполняет довольно тяжелую работу — суммирует, группирует и так далее. Поддержание ее в актуальном состоянии все время, пока ваша базовая таблица TimeCard изменяется и обновляется, вызовет некоторую нагрузку на вашу систему - трудно сказать, насколько сильно, но это может быть весьма заметно.

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

Возможно, это не то решение, которое вы ищете, но просто подумайте об этом немного. Это может - или не может - сработать для вас!

person marc_s    schedule 10.03.2010