Выполнение агрегатных функций в таблицах с многомиллионными строками - PullRequest
2 голосов
/ 12 мая 2010

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

  • Я использую SQL Server 2008 Standard, поэтому разделение в настоящее время недоступно

  • Я пытаюсь объединить все просмотры для всех ресурсов для определенного аккаунта за последние 30 дней.

  • Все представления хранятся в следующей таблице:

CREATE TABLE [dbo].[LogInvSearches_Daily](
    [ID] [bigint] IDENTITY(1,1) NOT NULL,
    [Inv_ID] [int] NOT NULL,
    [Site_ID] [int] NOT NULL,
    [LogCount] [int] NOT NULL,
    [LogDay] [smalldatetime] NOT NULL,
 CONSTRAINT [PK_LogInvSearches_Daily] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON, FILLFACTOR = 90) ON [PRIMARY]
) ON [PRIMARY]
  • В этой таблице 132 000 000 записей и более 4 гигов.

  • Выборка из 10 строк таблицы:

ID                   Inv_ID      Site_ID     LogCount    LogDay
-------------------- ----------- ----------- ----------- -----------------------
1                    486752      48          14          2009-07-21 00:00:00
2                    119314      51          16          2009-07-21 00:00:00
3                    313678      48          25          2009-07-21 00:00:00
4                    298863      0           1           2009-07-21 00:00:00
5                    119996      0           2           2009-07-21 00:00:00
6                    463777      534         7           2009-07-21 00:00:00
7                    339976      503         2           2009-07-21 00:00:00
8                    333501      570         4           2009-07-21 00:00:00
9                    453955      0           12          2009-07-21 00:00:00
10                   443291      0           4           2009-07-21 00:00:00

(10 row(s) affected)
  • У меня есть следующий индекс на LogInvSearches_Daily:
/****** Object:  Index [IX_LogInvSearches_Daily_LogDay]    Script Date: 05/12/2010 11:08:22 ******/
CREATE NONCLUSTERED INDEX [IX_LogInvSearches_Daily_LogDay] ON [dbo].[LogInvSearches_Daily] 
(
    [LogDay] ASC
)
INCLUDE ( [Inv_ID],
[LogCount]) 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]
  • Мне нужно вытащить инвентарь только из инвентаря для определенного идентификатора учетной записи. У меня также есть индекс по инвентарю.

Я использую следующий запрос для агрегирования данных и выдачи 5 лучших записей. Этот запрос в настоящее время занимает 24 секунды, чтобы вернуть 5 строк:

StmtText
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SELECT TOP 5
    Sum(LogCount) AS Views
    , DENSE_RANK() OVER(ORDER BY Sum(LogCount) DESC, Inv_ID DESC) AS Rank
    , Inv_ID
FROM LogInvSearches_Daily D (NOLOCK)
WHERE 
    LogDay > DateAdd(d, -30, getdate())
    AND EXISTS(
        SELECT NULL FROM propertyControlCenter.dbo.Inventory (NOLOCK) WHERE Acct_ID = 18731 AND Inv_ID = D.Inv_ID
    )
GROUP BY Inv_ID


(1 row(s) affected)

StmtText
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  |--Top(TOP EXPRESSION:((5)))
       |--Sequence Project(DEFINE:([Expr1007]=dense_rank))
            |--Segment
                 |--Segment
                      |--Sort(ORDER BY:([Expr1006] DESC, [D].[Inv_ID] DESC))
                           |--Stream Aggregate(GROUP BY:([D].[Inv_ID]) DEFINE:([Expr1006]=SUM([LOALogs].[dbo].[LogInvSearches_Daily].[LogCount] as [D].[LogCount])))
                                |--Sort(ORDER BY:([D].[Inv_ID] ASC))
                                     |--Nested Loops(Inner Join, OUTER REFERENCES:([D].[Inv_ID]))
                                          |--Nested Loops(Inner Join, OUTER REFERENCES:([Expr1011], [Expr1012], [Expr1010]))
                                          |    |--Compute Scalar(DEFINE:(([Expr1011],[Expr1012],[Expr1010])=GetRangeWithMismatchedTypes(dateadd(day,(-30),getdate()),NULL,(6))))
                                          |    |    |--Constant Scan
                                          |    |--Index Seek(OBJECT:([LOALogs].[dbo].[LogInvSearches_Daily].[IX_LogInvSearches_Daily_LogDay] AS [D]), SEEK:([D].[LogDay] > [Expr1011] AND [D].[LogDay] < [Expr1012]) ORDERED FORWARD)
                                          |--Index Seek(OBJECT:([propertyControlCenter].[dbo].[Inventory].[IX_Inventory_Acct_ID]), SEEK:([propertyControlCenter].[dbo].[Inventory].[Acct_ID]=(18731) AND [propertyControlCenter].[dbo].[Inventory].[Inv_ID]=[LOA

(13 row(s) affected)

Я пытался использовать CTE, чтобы сначала выбрать строки и объединить их, но это не ускорилось и дает мне практически тот же план выполнения.


(1 row(s) affected)
StmtText
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--SET SHOWPLAN_TEXT ON;
WITH getSearches AS (
        SELECT
            LogCount
--          , DENSE_RANK() OVER(ORDER BY Sum(LogCount) DESC, Inv_ID DESC) AS Rank
            , D.Inv_ID
        FROM LogInvSearches_Daily D (NOLOCK)
            INNER JOIN propertyControlCenter.dbo.Inventory I (NOLOCK) ON Acct_ID = 18731 AND I.Inv_ID = D.Inv_ID
        WHERE 
            LogDay > DateAdd(d, -30, getdate())
--      GROUP BY Inv_ID
)

SELECT Sum(LogCount) AS Views, Inv_ID
FROM getSearches
GROUP BY Inv_ID


(1 row(s) affected)

StmtText
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  |--Stream Aggregate(GROUP BY:([D].[Inv_ID]) DEFINE:([Expr1004]=SUM([LOALogs].[dbo].[LogInvSearches_Daily].[LogCount] as [D].[LogCount])))
       |--Sort(ORDER BY:([D].[Inv_ID] ASC))
            |--Nested Loops(Inner Join, OUTER REFERENCES:([D].[Inv_ID]))
                 |--Nested Loops(Inner Join, OUTER REFERENCES:([Expr1008], [Expr1009], [Expr1007]))
                 |    |--Compute Scalar(DEFINE:(([Expr1008],[Expr1009],[Expr1007])=GetRangeWithMismatchedTypes(dateadd(day,(-30),getdate()),NULL,(6))))
                 |    |    |--Constant Scan
                 |    |--Index Seek(OBJECT:([LOALogs].[dbo].[LogInvSearches_Daily].[IX_LogInvSearches_Daily_LogDay] AS [D]), SEEK:([D].[LogDay] > [Expr1008] AND [D].[LogDay] < [Expr1009]) ORDERED FORWARD)
                 |--Index Seek(OBJECT:([propertyControlCenter].[dbo].[Inventory].[IX_Inventory_Acct_ID] AS [I]), SEEK:([I].[Acct_ID]=(18731) AND [I].[Inv_ID]=[LOALogs].[dbo].[LogInvSearches_Daily].[Inv_ID] as [D].[Inv_ID]) ORDERED FORWARD)

(8 row(s) affected)


(1 row(s) affected)

Итак, учитывая, что я получаю хороший индексный запрос в моем плане выполнения, что я могу сделать, чтобы запустить его быстрее?

UPDATE:

Вот тот же запрос, который выполняется без DENSE_RANK (), и он занимает те же самые 24 секунды, что дает мне тот же базовый план запроса:

StmtText
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--SET SHOWPLAN_TEXT ON
SELECT TOP 5
    Sum(LogCount) AS Views
    , Inv_ID
FROM LogInvSearches_Daily D (NOLOCK)
WHERE 
    LogDay > DateAdd(d, -30, getdate())
    AND EXISTS(
        SELECT NULL FROM propertyControlCenter.dbo.Inventory (NOLOCK) WHERE Acct_ID = 18731 AND Inv_ID = D.Inv_ID
    )
GROUP BY Inv_ID
ORDER BY Views, Inv_ID
(1 row(s) affected)

StmtText
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  |--Sort(TOP 5, ORDER BY:([Expr1006] ASC, [D].[Inv_ID] ASC))
       |--Stream Aggregate(GROUP BY:([D].[Inv_ID]) DEFINE:([Expr1006]=SUM([LOALogs].[dbo].[LogInvSearches_Daily].[LogCount] as [D].[LogCount])))
            |--Sort(ORDER BY:([D].[Inv_ID] ASC))
                 |--Nested Loops(Inner Join, OUTER REFERENCES:([D].[Inv_ID]))
                      |--Nested Loops(Inner Join, OUTER REFERENCES:([Expr1010], [Expr1011], [Expr1009]))
                      |    |--Compute Scalar(DEFINE:(([Expr1010],[Expr1011],[Expr1009])=GetRangeWithMismatchedTypes(dateadd(day,(-30),getdate()),NULL,(6))))
                      |    |    |--Constant Scan
                      |    |--Index Seek(OBJECT:([LOALogs].[dbo].[LogInvSearches_Daily].[IX_LogInvSearches_Daily_LogDay] AS [D]), SEEK:([D].[LogDay] > [Expr1010] AND [D].[LogDay] < [Expr1011]) ORDERED FORWARD)
                      |--Index Seek(OBJECT:([propertyControlCenter].[dbo].[Inventory].[IX_Inventory_Acct_ID]), SEEK:([propertyControlCenter].[dbo].[Inventory].[Acct_ID]=(18731) AND [propertyControlCenter].[dbo].[Inventory].[Inv_ID]=[LOALogs].[dbo].[LogInvS

(9 row(s) affected)


Спасибо

Dan

Ответы [ 3 ]

1 голос
/ 12 мая 2010

Разделение в сторону,

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

Кроме этого, я согласен с замечаниями Дэниела Реншоу о DENSE_RANK

Я бы также подумал о перемещении [Inv_ID], [LogCount] в индекс (не включая, возможно, с сортировкой DESC)

1 голос
/ 12 мая 2010

Я еще не прочитал весь ваш вопрос (я скоро к этому вернусь), но чтобы ответить на ранний комментарий: вы можете использовать разделенные представления в SQL Server 2008 Стандартная версия. Он разбит на разделы таблиц (которые, по общему признанию, более гибкие), которые ограничены выпуском Enterprise.

Информация о разделенных взглядах: http://msdn.microsoft.com/en-us/library/ms190019.aspx

По более широкому вопросу, который я хотел бы знать, действительно ли вам нужен DENSE_RANK. Мне интересно, если вы запутались между ORDER BY внутри DENSE_RANK и ORDER BY самого запроса. В своем нынешнем виде ТОП 5 вернет 5 неопределенных записей, поскольку SQL Server не гарантирует какой-либо порядок записей, если не указано предложение ORDER BY (чего вы не сделали). Если вы переместите ORDER BY из DENSE_RANK вниз на весь запрос ORDER BY, как показано ниже, записи будут отображаться так, как я думаю, и вы устраните необходимость в дорогой агрегатной функции DENSE_RANK.

SELECT TOP 5
    SUM([LogCount]) AS [Views],
    [Inv_ID]
FROM [LogInvSearches_Daily] D (NOLOCK)
WHERE 
    [LogDay] > DateAdd(d, -30, getdate())
    AND EXISTS(
        SELECT *
        FROM Inventory (NOLOCK)
        WHERE Acct_ID = 18731
            AND Inv_ID = D.Inv_ID
    )
GROUP BY
    Inv_ID
ORDER BY
    [Views] DESC,
    [Inv_ID]

UPDATE:

Время, вероятно, используется здесь:

|--Sort(ORDER BY:([D].[Inv_ID] ASC))

Вы можете попробовать создать индекс покрытия, как этот:

CREATE NONCLUSTERED INDEX [IX_LogInvSearches_Daily_Perf] ON [dbo].[LogInvSearches_Daily] 
(
    [Inv_ID] ASC,
    [LogDay] ASC
)
INCLUDE
(
    [LogCount]
)

Обратите внимание, что я также немного изменил ORDER BY (Inv_ID теперь сортируется ASC вместо DESC). Я подозреваю, что это изменение не повлияет на результаты проблемным образом, но может повысить производительность, поскольку оно будет возвращать строки в том же порядке, в котором они сгруппированы (хотя это может быть неуместно!).

0 голосов
/ 12 мая 2010

Acct_ID находится в таблице инвентаризации и, похоже, имеет индекс для себя (IX_Inventory_Acct_ID). Возможно, если бы в Inventory был индекс (Acct_Id, Inv_Id), а LogInvSearches_Daily была сгруппирована (или, по крайней мере, проиндексирована) вокруг (Inv_Id, LogDay), вам бы повезло больше.

Кстати, я понятия не имею, какой текущий индекс кластеризации на LogInvSearches_Daily.ID должен вас покупать. Почему импортируются записи с близкими идентификаторами на диске?

...