[MSSQL] Как оптимизировать запрос?
От: Ocenochka  
Дата: 18.12.10 09:30
Оценка:
Есть БД. Сразу приведу ее схему:

Есть два похожих запроса, под которые нужно оптимизировать скорость выборки.
Под один получается оптимизировать, под второй — нет.
Вот собственно эти запросы:

1. Выполняется примерно за 10мс, что меня полностью устраивает.
Для него построен кластерный индекс.

set statistics time on;
SELECT TOP (15) *
FROM (select comment.*,
             sourceComment.source_id as source_id,
             sourceComment.source_status_id as source_status_id,
             sourceComment.source_votes as source_votes,
             ROW_NUMBER() OVER (ORDER BY [votesDisplay] DESC, added DESC) as sortColumn 
      from SourceComment sourceComment 
        inner join Comment comment 
        on sourceComment.id=comment.Id 
      where comment.erased=0 
      and comment.added>='2008-08-01 00:00:00' 
      and comment.added<'2010-12-04 00:00:00' 
      and (1=0 or comment.category=17)) as query /* эта запись означает, что категории может не быть */
      WHERE query.sortColumn > 3500 
      ORDER BY query.sortColumn
set statistics time off




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

set statistics time on;
SELECT TOP (15) author, 
        sumVotes
FROM (select author, 
         SUM(votesDisplay) as sumVotes,
         ROW_NUMBER() OVER(ORDER BY SUM(votesDisplay) DESC) as sortColumn 
      from Comment comment
      where Erased = 0
      and author != 'Аноним'
      and Added >= '2008-08-18 00:00:00'
      and Added < '2010-12-05 00:00:00'
      group by author) as query 
WHERE query.sortColumn > 3500 
ORDER BY query.sortColumn
set statistics time off








Cкрипт для создания таблиц и индексов.

/* -------- Основная таблица. ~700k записей. */
CREATE TABLE [Comment](
    [id] [bigint] IDENTITY(1,1) NOT NULL,
    [erased] [bit] NOT NULL,
    [author] [nvarchar](128) NOT NULL,
    [text] [nvarchar](410) NOT NULL,
    [added] [datetime] NOT NULL,
    [category] [tinyint] NOT NULL,
    [votes] [int] NOT NULL,
    [votesDisplay] [int] NOT NULL,
    [obscene] [bit] NOT NULL,
 CONSTRAINT [PK__STATUS] PRIMARY KEY NONCLUSTERED 
(
    [id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

/* -------- Вторая таблица */
CREATE TABLE [SourceComment](
    [id] [bigint] NOT NULL,
    [source_status_id] [bigint] NOT NULL,
    [source_id] [bigint] NOT NULL,
    [source_votes] [int] NOT NULL,
 CONSTRAINT [PK__SOURCESTATUS] PRIMARY KEY CLUSTERED 
(
    [id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO


/* -------- Foreign Key со второй таблицы на основную */
ALTER TABLE [SourceComment]  WITH CHECK ADD  CONSTRAINT [FK__SOURCES_REFERENCE__STATUS] FOREIGN KEY([id])
REFERENCES [Comment] ([id])
GO
ALTER TABLE [SourceComment] CHECK CONSTRAINT [FK__SOURCES_REFERENCE__STATUS]
GO


/* --  ИНДЕКСЫ -- */

/* -------- Таблица [Comment] - кластерный индекс. */
CREATE CLUSTERED INDEX [erased_category_votesDisplay_added_author] ON [Comment] 
(
    [erased] ASC,
    [votesDisplay] DESC,
    [added] DESC,
    [author] DESC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
GO

/* -------- Таблица [Comment] - не кластерный индекс. Нужен для запроса, который здесь не упоминается, но для полноты картины решил его привести */
CREATE NONCLUSTERED INDEX [added] ON [Comment] 
(
    [added] DESC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
GO

/* -------- Таблица [SourceComment] - не кластерный индекс. */
CREATE UNIQUE NONCLUSTERED INDEX [id_in_source__and__source_id_index] ON [SourceComment] 
(
    [source_status_id] DESC,
    [source_id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
GO
Люблю ставить оценки.
Re: [MSSQL] Как оптимизировать запрос?
От: Снег  
Дата: 18.12.10 10:56
Оценка:
Я бы для начала посмотрел на план подзапроса, предварительно выкинув из него ROW_NUMBER.
http://vishnyasoft.com/
Re[2]: [MSSQL] Как оптимизировать запрос?
От: Ocenochka  
Дата: 18.12.10 11:07
Оценка:
Здравствуйте, Снег, Вы писали:

С>Я бы для начала посмотрел на план подзапроса, предварительно выкинув из него ROW_NUMBER.


Да, забыл сказать — у меня что-то сломалось в SQL Server'е и при попытке построить план выполнения (предполагаемый или актуальный)
вместо плана я вижу текст с ошибкой:

ROW_NUMBER нужен, т.к. запрос используется для получения страниц данных.
Может можно как-то без ROW_NUMBER, но я не знаю как.
Люблю ставить оценки.
Re[3]: [MSSQL] Как оптимизировать запрос?
От: Ocenochka  
Дата: 18.12.10 11:10
Оценка:
Здравствуйте, Ocenochka, Вы писали:

O> Да, забыл сказать — у меня что-то сломалось в SQL Server'е и при попытке построить план выполнения (предполагаемый или актуальный)

O> вместо плана я вижу текст с ошибкой:
Не могу найти сборку XXXX.dll.

где xxxx.dll — это сгенерированное имя.
Люблю ставить оценки.
Re: [MSSQL] Как оптимизировать запрос?
От: Sergei MO Россия  
Дата: 18.12.10 11:30
Оценка: 2 (1)
Здравствуйте, Ocenochka, Вы писали:

O>1. Выполняется примерно за 10мс, что меня полностью устраивает.

O> Для него построен кластерный индекс.

Есть подозрение, что этот запрос будет выполняться медленно в следующих случаях:
1) Когда требуемый номер начальной записи очень большой (например, query.sortColumn > 600000).
2) Когда в диапазон дат попадает очень мало записей либо sourceComment содержит мало записей, и значения поля votesDisplay в этих записях равномерно распределены по всему диапазону.

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


O>2. Выполняется несколько секунд. Пробовал разные индексы, но это лучшее чего я смог добиться.


Индексы практически не помогут, так как для вычисления этого запроса требуется обращение ко всем записям таблицы. Максимум, что можно сделать, это вынести поле author в отдельную таблицу, а в основной таблице оставить ссылку на автора через author_id, и сделать индекс по (erased, author_id, votesDisplay, added). Но сильного улучшения это не даст.

Если бы не было условия, ограничивающего выбор записей по дате, тогда можно было бы существенно ускорить этот запрос, используя материализованные представления.
Re[4]: [MSSQL] Как оптимизировать запрос?
От: Снег  
Дата: 18.12.10 11:34
Оценка:
Ну тогда хотя бы скажите, сколько строк отдают подзапросы query в обоих случаях.
И сколько времени каждый из них работает.
http://vishnyasoft.com/
Re[2]: [MSSQL] Как оптимизировать запрос?
От: Ocenochka  
Дата: 18.12.10 11:48
Оценка:
Здравствуйте, Sergei MO, Вы писали:

SM>Здравствуйте, Ocenochka, Вы писали:


O>>1. Выполняется примерно за 10мс, что меня полностью устраивает.

O>> Для него построен кластерный индекс.

SM>Есть подозрение, что этот запрос будет выполняться медленно в следующих случаях:

SM>1) Когда требуемый номер начальной записи очень большой (например, query.sortColumn > 600000).

Да, есть такое. В моем случае sortColumn > 600000 CPU time = 2246 ms.

SM>2) Когда в диапазон дат попадает очень мало записей либо sourceComment содержит мало записей, и значения поля votesDisplay в этих записях равномерно распределены по всему диапазону.


Это не смог воспроизвести.

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


База сейчас со всеми индексами ~400 МБ. Сильно расти не планирует, так что пока можно считать что она вся в оперативке.
Но если можно как-то это дело исправить без потери производительности, то с удовольствием попробую.

O>>2. Выполняется несколько секунд. Пробовал разные индексы, но это лучшее чего я смог добиться.


SM>Индексы практически не помогут, так как для вычисления этого запроса требуется обращение ко всем записям таблицы. Максимум, что можно сделать, это вынести поле author в отдельную таблицу, а в основной таблице оставить ссылку на автора через author_id, и сделать индекс по (erased, author_id, votesDisplay, added). Но сильного улучшения это не даст.

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

Значит ничего нельзя сделать?
Вообще, в моем случае число диапазонов фиксированное (за все время, по годам, по месяцам, по дням), то за три года = 365*3 + 12*3 + 3 + 1 ~= 1100 диапазонов, но
как я понимаю это не сильно облегчает дело, т.к. на все диапазоны индексы не построить.
Люблю ставить оценки.
Re[5]: [MSSQL] Как оптимизировать запрос?
От: Ocenochka  
Дата: 18.12.10 12:04
Оценка:
Здравствуйте, Снег, Вы писали:

С>Ну тогда хотя бы скажите, сколько строк отдают подзапросы query в обоих случаях.

С>И сколько времени каждый из них работает.

В зависимости от указанного диапазона дат.
Если брать за весь диапазон (не ограничивать по дате) то:
Подзапрос первого запроса работает 22 секунды, возвращает все записи (~700k)
второго — работает около 1 секунды, возвращает ~100k.
(ограничивал оперативку до 128 МБ)
Люблю ставить оценки.
Re: [MSSQL] Как оптимизировать запрос?
От: Аноним  
Дата: 18.12.10 12:13
Оценка:
Здравствуйте, Ocenochka, Вы писали:

O>2. Выполняется несколько секунд. Пробовал разные индексы, но это лучшее чего я смог добиться.


O>
O>set statistics time on;
O>SELECT TOP (15) author, 
O>        sumVotes
O>FROM (select author, 
O>         SUM(votesDisplay) as sumVotes,
O>         ROW_NUMBER() OVER(ORDER BY SUM(votesDisplay) DESC) as sortColumn 
O>      from Comment comment
O>      where Erased = 0
O>      and author != 'Аноним'
O>      and Added >= '2008-08-18 00:00:00'
O>      and Added < '2010-12-05 00:00:00'
O>      group by author) as query 
O>WHERE query.sortColumn > 3500 
O>ORDER BY query.sortColumn
O>set statistics time off
O>


set statistics time on;
SELECT author, 
       sumVotes
FROM (select author, 
         SUM(votesDisplay) as sumVotes,
         ROW_NUMBER() OVER(ORDER BY SUM(votesDisplay) DESC) as sortColumn 
      from Comment comment
      where Erased = 0
      and author != 'Аноним'
      and Added >= '2008-08-18 00:00:00'
      and Added < '2010-12-05 00:00:00'
      group by author) as query 
WHERE query.sortColumn > 3500 and query.sortColumn < 3516
set statistics time off
Re[6]: [MSSQL] Как оптимизировать запрос?
От: Снег  
Дата: 18.12.10 12:21
Оценка: 2 (1)
O> В зависимости от указанного диапазона дат.
O> Если брать за весь диапазон (не ограничивать по дате) то:
Отбор по дате лучше оставить, если именно такой вариант используется в "боевой" системе.
Я так понимаю, проблема в переборе всех строк таблицы при отборе по дате вставки.
Я бы попробовал следущее:
1) создать индекс по id и added
2) написать подзапрос вида
select id from Comment where Added >= '2008-08-18 00:00:00' and Added < '2010-12-05 00:00:00'

выборка данных должна происходить из индекса (и работать быстро)
3) если такой подзапрос работает быстро, то остается переписать подзапрос query, заменив "and Added >= and Added < " на подзапрос из п.2
http://vishnyasoft.com/
Re[3]: [MSSQL] Как оптимизировать запрос?
От: Sergei MO Россия  
Дата: 18.12.10 12:38
Оценка: 2 (1)
Здравствуйте, Ocenochka, Вы писали:

O> Но если можно как-то это дело исправить без потери производительности, то с удовольствием попробую.


В первом запросе лучше вынести join из подзапроса во внешний запрос, тогда подзапрос будет работать только по одному индексу.

И обязательно что-нибудь придумать с просмотром планов. Ну хотя бы использовать SET STATISTICS PROFILE ON.


O> Значит ничего нельзя сделать?

O> Вообще, в моем случае число диапазонов фиксированное (за все время, по годам, по месяцам, по дням), то за три года = 365*3 + 12*3 + 3 + 1 ~= 1100 диапазонов, но
O> как я понимаю это не сильно облегчает дело, т.к. на все диапазоны индексы не построить.

Можно использовать разные запросы, в зависимости от диапазона дат. Для одиночных дней количество записей будет очень ограниченным, поэтому можно построить запрос так, чтобы он сначала выбирал все записи за нужный день, а потом выполнял их анализ. Для месяцев, годов и всего времени построить соответствующее материализованное представление. Для 40 диапазонов это будет вполне нормально.

Я так понимаю, что различных авторов не очень много — порядка нескольких тысяч, плюс Аноним.
Re[4]: [MSSQL] Как оптимизировать запрос?
От: Ocenochka  
Дата: 18.12.10 13:56
Оценка:
O>> Но если можно как-то это дело исправить без потери производительности, то с удовольствием попробую.
SM>В первом запросе лучше вынести join из подзапроса во внешний запрос, тогда подзапрос будет работать только по одному индексу.

Помогло. При запросе с sortColumn > 600000 стало быстрее в три раза.

SM>И обязательно что-нибудь придумать с просмотром планов. Ну хотя бы использовать SET STATISTICS PROFILE ON.


Спасибо, не знал про это. Только пока знаний не хватает использовать эту информацию. Надо будет поэкспериментировать.

SM>Можно использовать разные запросы, в зависимости от диапазона дат. Для одиночных дней количество записей будет очень ограниченным, поэтому можно построить запрос так, чтобы он сначала выбирал все записи за нужный день, а потом выполнял их анализ. Для месяцев, годов и всего времени построить соответствующее материализованное представление. Для 40 диапазонов это будет вполне нормально.


Ясно, спасибо, буду пробовать.

SM>Я так понимаю, что различных авторов не очень много — порядка нескольких тысяч, плюс Аноним.


) сотня тысяч уникальных авторов.
Люблю ставить оценки.
Re[2]: [MSSQL] Как оптимизировать запрос?
От: Ocenochka  
Дата: 18.12.10 14:02
Оценка:
Здравствуйте, Аноним, Вы писали:

O>>2. Выполняется несколько секунд. Пробовал разные индексы, но это лучшее чего я смог добиться.


А>
А>set statistics time on;
А>SELECT author, 
А>       sumVotes
А>FROM (select author, 
А>         SUM(votesDisplay) as sumVotes,
А>         ROW_NUMBER() OVER(ORDER BY SUM(votesDisplay) DESC) as sortColumn 
А>      from Comment comment
А>      where Erased = 0
А>      and author != 'Аноним'
А>      and Added >= '2008-08-18 00:00:00'
А>      and Added < '2010-12-05 00:00:00'
А>      group by author) as query 
А>WHERE query.sortColumn > 3500 and query.sortColumn < 3516
А>set statistics time off
А>


Спасибо! Так действительно стало в три раза быстрее.
Люблю ставить оценки.
Re[7]: [MSSQL] Как оптимизировать запрос?
От: Ocenochka  
Дата: 18.12.10 14:29
Оценка:
Здравствуйте, Снег, Вы писали:

O>> В зависимости от указанного диапазона дат.

O>> Если брать за весь диапазон (не ограничивать по дате) то:
С>Отбор по дате лучше оставить, если именно такой вариант используется в "боевой" системе.
С>Я так понимаю, проблема в переборе всех строк таблицы при отборе по дате вставки.
С>Я бы попробовал следущее:
С>1) создать индекс по id и added

Т.е. один индекс по двум полям в указанном порядке?
Уже есть два отдельных индекса на id и на added.

С>2) написать подзапрос вида

С>
С>select id from Comment where Added >= '2008-08-18 00:00:00' and Added < '2010-12-05 00:00:00'
С>

С>выборка данных должна происходить из индекса (и работать быстро)

Разве имеющихся индексов:
1. id
2. added
3. erased_votesDisplay_added_author
не достаточно?

С>3) если такой подзапрос работает быстро, то остается переписать подзапрос query, заменив "and Added >= and Added < " на подзапрос из п.2


Честно говоря, не понял идею. Не могли бы Вы объяснить по шагам что мне нужно сделать?
Люблю ставить оценки.
Re[3]: [MSSQL] Как оптимизировать запрос?
От: Spi  
Дата: 18.12.10 22:47
Оценка: 2 (1)
Здравствуйте, Ocenochka, Вы писали:

O>Здравствуйте, Аноним, Вы писали:


O>>>2. Выполняется несколько секунд. Пробовал разные индексы, но это лучшее чего я смог добиться.


А>>
А>>set statistics time on;
А>>SELECT author, 
А>>       sumVotes
А>>FROM (select author, 
А>>         SUM(votesDisplay) as sumVotes,
А>>         ROW_NUMBER() OVER(ORDER BY SUM(votesDisplay) DESC) as sortColumn 
А>>      from Comment comment
А>>      where Erased = 0
А>>      and author != 'Аноним'
А>>      and Added >= '2008-08-18 00:00:00'
А>>      and Added < '2010-12-05 00:00:00'
А>>      group by author) as query 
А>>WHERE query.sortColumn > 3500 and query.sortColumn < 3516
А>>set statistics time off
А>>


O> Спасибо! Так действительно стало в три раза быстрее.


Да не за что. Правда есть НО: в таком запросе порядок возвращаемых записей может быть разным. Если важно чтоб эти 15 записей возвращались отсортированными по sortColumn то надо добавить order by. Для 15 записей он не должен привести к заметному уменьшению времени запроса.
Re[8]: [MSSQL] Как оптимизировать запрос?
От: Снег  
Дата: 18.12.10 22:50
Оценка:
O> Т.е. один индекс по двум полям в указанном порядке?
O> Уже есть два отдельных индекса на id и на added.
Отдельные индексы не помогут. Поиск по индексу (колонки индекса покрывают запрос) выполняется без обращения к самой таблице, что в ряде случаев сокращает время выполнения.

O> Честно говоря, не понял идею. Не могли бы Вы объяснить по шагам что мне нужно сделать?

А надо? Как я понял, задча уже решена.
http://vishnyasoft.com/
Re[9]: [MSSQL] Как оптимизировать запрос?
От: Ocenochka  
Дата: 19.12.10 07:56
Оценка:
Здравствуйте, Снег, Вы писали:

O>> Честно говоря, не понял идею. Не могли бы Вы объяснить по шагам что мне нужно сделать?

С>А надо? Как я понял, задча уже решена.

Скорее появилось понимание, что ее не решить с помощью индексов.
Люблю ставить оценки.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.