Производительность SQL Server с использованием JOIN внутри представлений против записи их в sprocs? - PullRequest
1 голос
/ 16 февраля 2012

В базе данных у меня менее 200 000 записей в таблице, а в хранимой процедуре я ссылаюсь на пару представлений, которые выполняют несколько ЛЕВЫХ СОЕДИНЕНИЙ и других объединений.

Одно из использованных представлений содержит четыреЛЕВЫЕ ВНЕШНИЕ СОЕДИНЕНИЯ, еще два содержат несколько ВНУТРЕННИХ СОЕДИНЕНИЙ, просто связывающих таблицы / данные.

При просмотре плана выполнения sproc я обнаружил, что один запрос занимает 39% времени выполнения.Зеленым предложением было создать некластеризованный индекс по двум полям основной таблицы, что я и сделал (уже содержал кластеризованный индекс, поскольку он содержит автоинкрементный PK).

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

Этого и следовало ожидать?

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

Было бы лучше всего реорганизовать БД таким способом,в это время?

ОБНОВЛЕНИЕ

Этот sproc выполняет около 14 различных правил, чтобы найти совпадения.Если совпадение найдено, содержимое добавляется к глобальному параметру.Таким образом, для проверки каждого правила существует отдельный запрос.

Вместо 14 или около того запросов в одном sproc, я создал отдельные sprocs и вызвал их, используя EXEC, передавая (вместе с другими параметрами)и возвращая этот глобальный параметр.

Я выполнил план выполнения с SET SHOWPLAN_ALL ON.

Первым виновником (показывает общую стоимость поддерева 4.408248)

   SELECT @ExternalTagName = etbs.ExternalTagName, @ExternalTagID = etbs.ExternalTagID, @ExternalPixelValue = etbs.ExternalPixelValue, @TriggerAlpha = ISNULL(SUM(dbo.FindInString(etbs.TriggerValue, @DocumentUrl)), '')   FROM vw_ETBS etbs   WHERE etbs.SystemBehaviouralSegmentID = 9  -- page url contains   AND etbs.AccountContainerID = @AccountContainerID   AND etbs.IsEnabled = 1    AND etbs.TriggerValue = @TriggerAlpha   GROUP BY ExternalPixelValue, etbs.ExternalTagID, etbs.ExternalTagName     --INSERT INTO DebugTable (DebugKey, DebugValue)   --VALUES ('after sql', 'test')  79  259 1   NULL    NULL    67  NULL    63.26242    NULL    NULL    NULL    4.408248    NULL    NULL    SELECT  0   NULL
       |--Compute Scalar(DEFINE:([Expr1016]=CONVERT_IMPLICIT(varchar(6000),[ROTags-Shopify-Alpha].[dbo].[ExternalTagList].[ExternalPixelValue],0), [Expr1017]=CONVERT_IMPLICIT(varchar(512),isnull([Expr1015],(0)),0))) 79  260 259 Compute Scalar  Compute Scalar  DEFINE:([Expr1016]=CONVERT_IMPLICIT(varchar(6000),[ROTags-Shopify-Alpha].[dbo].[ExternalTagList].[ExternalPixelValue],0), [Expr1017]=CONVERT_IMPLICIT(varchar(512),isnull([Expr1015],(0)),0))   [Expr1016]=CONVERT_IMPLICIT(varchar(6000),[ROTags-Shopify-Alpha].[dbo].[ExternalTagList].[ExternalPixelValue],0), [Expr1017]=CONVERT_IMPLICIT(varchar(512),isnull([Expr1015],(0)),0)    63.26242    0   6.326241E-06    3293    4.408248    [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagName], [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagID], [Expr1016], [Expr1017]    NULL    PLAN_ROW    0   1
            |--Compute Scalar(DEFINE:([Expr1015]=CASE WHEN [Expr1029]=(0) THEN NULL ELSE [Expr1030] END))   79  261 260 Compute Scalar  Compute Scalar  DEFINE:([Expr1015]=CASE WHEN [Expr1029]=(0) THEN NULL ELSE [Expr1030] END)  [Expr1015]=CASE WHEN [Expr1029]=(0) THEN NULL ELSE [Expr1030] END   63.26242    0   0.004639106 4063    4.408242    [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagName], [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagID], [ROTags-Shopify-Alpha].[dbo].[ExternalTagList].[ExternalPixelValue], [Expr1015]   NULL    PLAN_ROW    0   1
                 |--Stream Aggregate(GROUP BY:([ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagName], [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagID]) DEFINE:([Expr1029]=COUNT_BIG([ROTags-Shopify-Alpha].[dbo].[FindInString]([Expr1019],CONVERT_IMPLICIT(varchar(max),[@DocumentUrl],0))), [Expr1030]=SUM([ROTags-Shopify-Alpha].[dbo].[FindInString]([Expr1019],CONVERT_IMPLICIT(varchar(max),[@DocumentUrl],0))), [ROTags-Shopify-Alpha].[dbo].[ExternalTagList].[ExternalPixelValue]=ANY([ROTags-Shopify-Alpha].[dbo].[ExternalTagList].[ExternalPixelValue]))) 79  262 261 Stream Aggregate    Aggregate   GROUP BY:([ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagName], [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagID]) [Expr1029]=COUNT_BIG([ROTags-Shopify-Alpha].[dbo].[FindInString]([Expr1019],CONVERT_IMPLICIT(varchar(max),[@DocumentUrl],0))), [Expr1030]=SUM([ROTags-Shopify-Alpha].[dbo].[FindInString]([Expr1019],CONVERT_IMPLICIT(varchar(max),[@DocumentUrl],0))), [ROTags-Shopify-Alpha].[dbo].[ExternalTagList].[ExternalPixelValue]=ANY([ROTags-Shopify-Alpha].[dbo].[ExternalTagList].[ExternalPixelValue])    63.26242    0   0.004639106 4063    4.408242    [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagName], [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagID], [ROTags-Shopify-Alpha].[dbo].[ExternalTagList].[ExternalPixelValue], [Expr1029], [Expr1030]   NULL    PLAN_ROW    0   1
                      |--Sort(ORDER BY:([ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagName] ASC, [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagID] ASC))  79  263 262 Sort    Sort    ORDER BY:([ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagName] ASC, [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagID] ASC) NULL    7679.125    0.01126126  0.311861    4125    4.403603    [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagName], [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagID], [ROTags-Shopify-Alpha].[dbo].[ExternalTagList].[ExternalPixelValue], [Expr1019]   NULL    PLAN_ROW    0   1
                           |--Nested Loops(Inner Join, OUTER REFERENCES:([ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[VisitorTriggeredTagID], [Expr1028]) WITH UNORDERED PREFETCH)   79  264 263 Nested Loops    Inner Join  OUTER REFERENCES:([ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[VisitorTriggeredTagID], [Expr1028]) WITH UNORDERED PREFETCH   NULL    7679.125    0   0.03209874  4133    4.076795    [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagName], [ROTags-Shopify-Alpha].[dbo].[VisitorTriggeredExternalTag].[ExternalTagID], [ROTags-Shopify-Alpha].[dbo].[ExternalTagList].[ExternalPixelValue], [Expr1019]   NULL    PLAN_ROW    0   1

SELECT @ExternalTagName = ExternalTagName, @ExternalTagID = ExternalTagID, @ExternalPixelValue = ExternalPixelValue, @TriggerNumeric = COUNT(*)
FROM vw_ETBS
WHERE SystemBehaviouralSegmentID = 10       -- direct traffic
AND AccountContainerID = @AccountContainerID    
AND vw_ETBS.IsEnabled = 1
GROUP BY ExternalPixelValue, ExternalTagID, ExternalTagName

vw_ETBS:

SELECT [lots of individual fields]
FROM    dbo.VisitorTriggeredExternalTag LEFT OUTER JOIN
        dbo.PageVisitEvents ON dbo.VisitorTriggeredExternalTag.PageVisitEventID = dbo.PageVisitEvents.PageVisitEventID LEFT OUTER JOIN
        dbo.ExternalTagBehaviouralSegments ON dbo.VisitorTriggeredExternalTag.ExternalTagID = dbo.ExternalTagBehaviouralSegments.ExternalTagID LEFT OUTER JOIN
        dbo.ExternalTagList ON dbo.ExternalTagBehaviouralSegments.ExternalTagID = dbo.ExternalTagList.ExternalTagID LEFT OUTER JOIN
        dbo.AccountContainers ON dbo.ExternalTagList.AccountContainerID = dbo.AccountContainers.AccountContainerID

Ответы [ 5 ]

2 голосов
/ 16 февраля 2012

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

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

Я продолжу изучать планы выполнения и продолжу работать над самыми дорогими частями запроса, пока время выполнения не будет приемлемым для вас - я не знаю, достаточно ли 2,5 секунды «достаточно быстро» в вашей текущей ситуации и вам do необходимо определить приемлемую производительность, прежде чем приступать к оптимизации.

0 голосов
/ 16 февраля 2012

Представление - это виртуальная таблица, состоящая из столбцов одной или нескольких таблиц.Вы не можете отфильтровать записи на самом деле из этой техники.Все сопоставленные / не сопоставленные записи появятся в вашем наборе результатов в зависимости от вашего соединения (внутреннее / левое).

Таким образом, в основном при использовании представления вам придется ждать выполнения, пока все записи не будут сохранены вОЗУ ...

С другой стороны, хранимая процедура дает возможность ввода параметров для фильтрации запроса.

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

Дело 1

Select Column1, Column2 From LargeTable T1
Inner Join SmallTable T2 on T2.Id = T1.Id
Where T1.ID = 1;

Дело 2

Select Column1, Column2 From
(
    Select Column1, Column2, ID From LargeTable Where ID = 1
)K
Inner Join SmallTable T2 on T2.ID = K.ID

Проверьте различия ... Надеюсь, это поможет вам оптимизировать его ...

0 голосов
/ 16 февраля 2012

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

После поддержания ваших индексов запустите «UPDATE STATISTICS tableName», предпочтительно «WITH FULLSCAN», чтобы получить лучшие выборки, но указав «WITH SAMPLE 20 PERCENT» или любой большой размер выборки, вы получите лучшую статистику, чем при использовании по умолчанию. Кроме того, если вы решите перестроить все индексы, то вы можете сэкономить время, добавив «COLUMNS», чтобы только восстановить статистику столбцов, поскольку перестройка индексов уже дает вам идеальную выборку статистики индексов. Поэтому оптимальным утверждением будет «ОБНОВЛЕНИЕ СТАТИСТИКИ tableName WITH FULLSCAN, COLUMNS»

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

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

0 голосов
/ 16 февраля 2012

В идеальном мире соединение одного вида с другим не повлияет на вашу производительность вообще.К сожалению, в мире, в котором мы живем, это проблема.По сути, чем больше объектов и объединений вы добавляете в оптимизатор, тем больше работы он должен выполнять за ограниченное время, тем более вероятно, что вы получите тайм-аут, что приведет к неоптимальному плану.Поэтому, в общем, вместо того, чтобы соединять одно представление с другим, лучше просто переписать запрос, чтобы вы только ссылались на нужную информацию, упрощая работу для оптимизатора.

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

Если вы используете систему управления реляционными базами данных, я настоятельно рекомендую отказаться от концепций управления реляционными данными.Нормализуйте структуры, применяйте первичные ключи, применяйте внешние ключи, работайте с системой, как она спроектирована, и вы получите преимущества.Большинство проблем возникает, когда люди перестают так относиться к СУБД и вместо этого пытаются рассматривать ее как хранилище файлов или одну из систем NoSQL (которые действительно хороши в том, что они делают, но для работы им нужно то, что они делают)ну, я также видел людей, использующих базы данных NoSQL, например, СУБД, а затем жаловался, что они не работают правильно ...).

0 голосов
/ 16 февраля 2012

Включить оператор SET NOCOUNT ON : С каждым оператором SELECT и DML сервер SQL возвращает сообщение, в котором указано количество строк, затронутых этим оператором.Эта информация в основном полезна при отладке кода, но после этого бесполезна.Установив SET NOCOUNT ON, мы можем отключить функцию возврата этой дополнительной информации.Для хранимых процедур, которые содержат несколько операторов или содержат циклы Transact-SQL, установка SET NOCOUNT в положение ON может значительно повысить производительность, поскольку сетевой трафик значительно уменьшается.

CREATE PROC dbo.ProcName
AS
 SET NOCOUNT ON;
--Procedure code here
SELECT column1 FROM dbo.TblTable1
-- Reset SET NOCOUNT to OFF
SET NOCOUNT OFF;
GO

Использование имени схемы с именем объекта: Имя объекта уточняется, если используется с именем схемы.Имя схемы должно использоваться с именем хранимой процедуры и со всеми объектами, на которые есть ссылки в хранимой процедуре.Это помогает непосредственно найти выполненный план вместо поиска объектов в другой возможной схеме, прежде чем окончательно принять решение использовать кэшированный план, если таковой имеется.Этот процесс поиска и определения схемы для объекта приводит к блокировке COMPILE хранимой процедуры и снижает производительность хранимой процедуры.Поэтому всегда обращайтесь к объектам с определенным именем в хранимой процедуре, например

SELECT * FROM dbo.MyTable -- Preferred method
 -- Instead of
SELECT * FROM MyTable -- Avoid this method
 --And finally call the stored procedure with qualified name like:
EXEC dbo.MyProc -- Preferred method
 --Instead of
EXEC MyProc -- Avoid this method

. Не используйте префикс «sp_» в имени хранимой процедуры: Если имя хранимой процедуры начинается с«SP_», затем SQL-сервер сначала выполняет поиск в базе данных master, а затем в базе данных текущего сеанса.Поиск в базе данных master приводит к дополнительным издержкам и даже неверному результату, если в базе данных master обнаружена другая хранимая процедура с тем же именем.Используйте IF EXISTS (SELECT 1) вместо (SELECT *): чтобы проверить наличие записи в другой таблице, мы используем предложение IF EXISTS.Предложение IF EXISTS возвращает True, если какое-либо значение возвращается из внутреннего оператора, либо одного значения «1», либо всех столбцов записи или полного набора записей.Вывод внутреннего оператора не используется.Следовательно, чтобы минимизировать данные для обработки и передачи по сети, мы должны использовать «1» в предложении SELECT внутреннего оператора, как показано ниже:

IF EXISTS (SELECT 1 FROM sysobjects
WHERE name = 'MyTable' AND type = 'U')

Использовать хранимую процедуру sp_executesql вместо оператора EXECUTE,Хранимая процедура sp_executesql поддерживает параметры.Таким образом, использование хранимой процедуры sp_executesql вместо оператора EXECUTE улучшает возможность повторного использования вашего кода.План выполнения динамического оператора может быть повторно использован, только если каждый символ, включая регистр, пробел, комментарии и параметр, одинаков для двух операторов.Например, если мы выполним следующий пакет:

DECLARE @Query VARCHAR(100)
DECLARE @Age INT
 SET @Age = 25
SET @Query = 'SELECT * FROM dbo.tblPerson WHERE Age = ' + CONVERT(VARCHAR(3),@Age)
EXEC (@Query)

Если мы снова выполним этот пакет, используя другое значение @Age, то план выполнения для оператора SELECT, созданного для @Age = 25, не будет использоваться повторно.Однако, если мы напишем вышеупомянутый пакет, как указано ниже,

DECLARE @Query NVARCHAR(100)
SET @Query = N'SELECT * FROM dbo.tblPerson WHERE Age = @Age'
EXECUTE sp_executesql @Query, N'@Age int', @Age = 25

, скомпилированный план этого оператора SELECT будет повторно использован для другого значения параметра @Age.Повторное использование существующего выполненного плана приведет к повышению производительности.Старайтесь по возможности избегать использования курсоров SQL Server. Курсор использует много ресурсов для обработки служебных данных, чтобы поддерживать текущую позицию записи в наборе записей, и это снижает производительность.Если нам нужно обрабатывать записи одну за другой в цикле, то мы должны использовать предложение WHILE.По возможности, мы должны заменить подход, основанный на курсоре, подходом, основанным на SET.Потому что ядро ​​SQL Server спроектировано и оптимизировано для очень быстрого выполнения операций на основе SET.Опять же, обратите внимание, курсор также является своего рода циклом WHILE.

Keep tТранзакция как можно короче : Длина транзакции влияет на блокировку и взаимоблокировку.Эксклюзивная блокировка не снимается до конца транзакции.На более высоком уровне изоляции разделяемые блокировки также устаревают с транзакцией.Следовательно, длительная транзакция означает, что блокировки на более длительное время и блокировки на более длительное время превращаются в блокировки.В некоторых случаях блокировка также превращается в тупики.Таким образом, для более быстрого выполнения и уменьшения блокировок транзакция должна быть максимально короткой.Используйте TRY-Catch для обработки ошибок: до кода версии SQL Server 2005 для обработки ошибок существовала значительная часть фактического кода, поскольку оператор проверки ошибок записывался после каждого оператора t-sql.Больше кода всегда требует больше ресурсов и времени.В SQL Server 2005 появился новый простой способ для той же цели.Синтаксис выглядит следующим образом:

BEGIN TRY
--Your t-sql code goes here
END TRY
BEGIN CATCH
--Your error handling code goes here
END CATCH

Для получения дополнительной информации обратитесь к этому Ссылка U будет лучше идея.

...