Вопрос оптимизации SQLServer Query - PullRequest
0 голосов
/ 27 октября 2009

У меня есть проект, который был «заколочен» с помощью Linq2SQL и сейчас сталкивается с некоторыми серьезными проблемами производительности запросов. Пойди разберись.

Linq на самом деле довольно хорошо работает в простых сценариях запросов и команд, но есть несколько сложных запросов, которые необходимо переписать как Sprocs.

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

Вдобавок ко всему, я думаю, что замена всех «In (@ p1, @ p2)», где предложения Inner Joins для временных таблиц будет хорошим началом.

Все столбцы внешнего ключа и столбца предложения индексируются.

Любые идеи приветствуются.

Вот код:

SELECT [t9].[ID], [t9].[Description], [t9].[AreaCodeID], [t9].[BedroomCodeID], [t9].[BathroomCodeID], [t9].[DwellingCodeID], [t9].[LandlordID], [t9].[ParsedItemID], [t9].[DeletedReasonID], [t9].[CoordinateID], [t9].[Address], [t9].[PhonePrefix1], [t9].[Phone1], [t9].[PhonePrefix2], [t9].[Phone2], [t9].[EmailAddress], [t9].[RentAmount], [t9].[SquareFeet], [t9].[DateAvailable], [t9].[DateCreated], [t9].[IsDeleted], [t9].[RowVersion], [t9].[ID2], [t9].[ParentAreaCodeID], [t9].[AreaGroupID], [t9].[Description2], [t9].[Order], [t9].[IsTopLevelArea], [t9].[IsDeleted2], [t9].[ID3], [t9].[CityID], [t9].[Description3], [t9].[Order2], [t9].[IsPrimary], [t9].[IsDeleted3], [t9].[ID4], [t9].[HostURL], [t9].[Description4], [t9].[Rate], [t9].[RateTax], [t9].[RateTaxCode], [t9].[Currency], [t9].[LogoImageFileName], [t9].[FlashQuotesFileName], [t9].[TestimonialQuotesFileName], [t9].[GoogleAnalyticsTrackingCode], [t9].[GoogleMapsAPIKey], [t9].[IDHash], [t9].[test], [t9].[ID5], [t9].[Description5], [t9].[ID6], [t9].[Description6], [t9].[Order3], [t9].[IsDeleted4], [t9].[ID7], [t9].[Description7], [t9].[IsDeleted5], [t9].[Order4]
FROM (
    SELECT TOP (100) [t0].[ID], [t0].[Description], [t0].[AreaCodeID], [t0].[BedroomCodeID], [t0].[BathroomCodeID], [t0].[DwellingCodeID], [t0].[LandlordID], [t0].[ParsedItemID], [t0].[DeletedReasonID], [t0].[CoordinateID], [t0].[Address], [t0].[PhonePrefix1], [t0].[Phone1], [t0].[PhonePrefix2], [t0].[Phone2], [t0].[EmailAddress], [t0].[RentAmount], [t0].[SquareFeet], [t0].[DateAvailable], [t0].[DateCreated], [t0].[IsDeleted], [t0].[RowVersion], [t1].[ID] AS [ID2], [t1].[ParentAreaCodeID], [t1].[AreaGroupID], [t1].[Description] AS [Description2], [t1].[Order], [t1].[IsTopLevelArea], [t1].[IsDeleted] AS [IsDeleted2], [t2].[ID] AS [ID3], [t2].[CityID], [t2].[Description] AS [Description3], [t2].[Order] AS [Order2], [t2].[IsPrimary], [t2].[IsDeleted] AS [IsDeleted3], [t3].[ID] AS [ID4], [t3].[HostURL], [t3].[Description] AS [Description4], [t3].[Rate], [t3].[RateTax], [t3].[RateTaxCode], [t3].[Currency], [t3].[LogoImageFileName], [t3].[FlashQuotesFileName], [t3].[TestimonialQuotesFileName], [t3].[GoogleAnalyticsTrackingCode], [t3].[GoogleMapsAPIKey], [t3].[IDHash], [t5].[test], [t5].[ID] AS [ID5], [t5].[Description] AS [Description5], [t6].[ID] AS [ID6], [t6].[Description] AS [Description6], [t6].[Order] AS [Order3], [t6].[IsDeleted] AS [IsDeleted4], [t7].[ID] AS [ID7], [t7].[Description] AS [Description7], [t7].[IsDeleted] AS [IsDeleted5], [t7].[Order] AS [Order4]
    FROM [dbo].[Listing] AS [t0]
    INNER JOIN ([dbo].[AreaCode] AS [t1]
        INNER JOIN ([dbo].[AreaGroup] AS [t2]
            INNER JOIN [dbo].[City] AS [t3] ON [t3].[ID] = [t2].[CityID]) ON [t2].[ID] = [t1].[AreaGroupID]) ON [t1].[ID] = [t0].[AreaCodeID]
    LEFT OUTER JOIN (
        SELECT 1 AS [test], [t4].[ID], [t4].[Description]
        FROM [dbo].[BathroomCode] AS [t4]
        ) AS [t5] ON [t5].[ID] = [t0].[BathroomCodeID]
    INNER JOIN [dbo].[BedroomCode] AS [t6] ON [t6].[ID] = [t0].[BedroomCodeID]
    INNER JOIN [dbo].[DwellingCode] AS [t7] ON [t7].[ID] = [t0].[DwellingCodeID]
    WHERE (NOT ([t0].[IsDeleted] = 1)) AND (EXISTS(
        SELECT NULL AS [EMPTY]
        FROM [dbo].[ListingMiscellaneousCode] AS [t8]
        WHERE ([t8].[MiscellaneousCodeID] IN (@p0, @p1, @p2, @p3, @p4, @p5, @p6)) AND ([t8].[ListingID] = [t0].[ID])
        )) AND ([t0].[DwellingCodeID] IN (@p7)) AND ([t0].[AreaCodeID] IN (@p8, @p9, @p10, @p11, @p12, @p13, @p14, @p15, @p16, @p17, @p18, @p19, @p20, @p21, @p22, @p23, @p24, @p25, @p26, @p27, @p28, @p29, @p30, @p31, @p32, @p33, @p34, @p35, @p36, @p37, @p38, @p39, @p40, @p41, @p42, @p43, @p44, @p45, @p46, @p47, @p48, @p49, @p50, @p51, @p52, @p53, @p54, @p55, @p56, @p57, @p58, @p59, @p60, @p61, @p62, @p63, @p64, @p65, @p66, @p67, @p68, @p69, @p70, @p71, @p72, @p73, @p74, @p75, @p76, @p77, @p78, @p79, @p80, @p81, @p82, @p83, @p84, @p85, @p86, @p87, @p88, @p89, @p90, @p91, @p92, @p93, @p94, @p95, @p96, @p97, @p98, @p99, @p100, @p101, @p102, @p103, @p104, @p105, @p106, @p107, @p108, @p109, @p110, @p111))
    ) AS [t9]
ORDER BY [t9].[DateCreated] DESC, [t9].[RentAmount], [t9].[Description2]

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

Даже с этим предложением Where, это не так медленно (около 1 секунды), но проблема в том, что мне также приходится возвращать различное количество текущих данных на основе запросов схожего стиля. Весь процесс занимает более 5 секунд из-за множественных запросов с плохими пунктами Where.

Еще одна вещь, которую я не понимаю, - это изменение размера страницы запроса, то есть «... Выберите TOP (100) ...» на большее число, например «... Выберите TOP (5000). .. "не замедляет запрос ВСЕ. Это странно для меня и является еще одним доказательством того, что я думаю, что проблема, мы надеемся, исправлена ​​с помощью модифицированного sql.

Вы также заметите, что одно конкретное предложение Where (для areacodeid) запрашивает почти 100 параметров. Это по замыслу. Теперь я могу добавить хак в родительскую таблицу, чтобы уменьшить это за счет некоторой ненормализации, но я надеюсь, что сначала будет чисто SQL-исправление, которое позволит мне эффективно присоединиться к временной таблице с сотнями параметров .

Спасибо за вашу помощь.

Ответы [ 3 ]

1 голос
/ 27 октября 2009

Есть ли индексы в любом из полезных столбцов в предложении WHERE (ListingMiscellaneousCode, MiscellaneousCodeID, DwellingCodeID, AreaCodeID? Вы рассматривали передачу одной строки для списка параметров вместо 100+ отдельных параметров? Обычно требуется противоположное , но в этом случае я думаю, что это может быть оправдано. Сначала я бы создал таблицу чисел, вероятно, достаточно 500 строк:

SET NOCOUNT ON;
DECLARE @UpperLimit INT;
SET @UpperLimit = 500;

    WITH n AS
    (
        SELECT
            x = ROW_NUMBER() OVER
            (ORDER BY s1.[object_id])
        FROM       [master].sys.columns AS s1
        CROSS JOIN [master].sys.columns AS s2
    )
    SELECT [Number] = x
      INTO dbo.Numbers
      FROM n
      WHERE x BETWEEN 1 AND @UpperLimit;
    GO
    CREATE UNIQUE CLUSTERED INDEX n ON dbo.Numbers([Number]);
    GO

Теперь создайте функцию, которая может анализировать список строк:

CREATE FUNCTION dbo.SplitINTs
(
    @List       VARCHAR(MAX),
    @Delimiter  NVARCHAR(10)
)
RETURNS TABLE
AS
    RETURN
    (
        SELECT DISTINCT
            [Value] = CONVERT(INT, LTRIM(RTRIM(
                SUBSTRING(@List, [Number],
                CHARINDEX
                (
                  @Delimiter, @List + @Delimiter, [Number]
                ) - [Number]))))
        FROM
            dbo.Numbers
        WHERE
            Number <= LEN(@List)
            AND SUBSTRING
            (
              @Delimiter + @List, [Number], LEN(@Delimiter)
            ) = @Delimiter
    );
GO

Теперь ваш запрос может сказать:

DECLARE @MiscCodeIDs VARCHAR(MAX), @AreaCodeIDs VARCHAR(MAX);
SELECT @MiscCodeIDs = '1,2,3,4,5...', @AreaCodeIDs = '6,7,8,9,10...';

SELECT <obnoxiously large column list>
FROM
...
INNER JOIN dbo.AreaCodes AS t1
ON ...
INNER JOIN dbo.SplitInts(@AreaCodeIDs, N',') AS acs
ON t1.AreaCodeID = acs.[Value]
...

AND 
(
    EXISTS
    (
              SELECT 1
            FROM [dbo].[ListingMiscellaneousCode] AS [t8]
            INNER JOIN dbo.SplitInts(@MiscCodeIDs, N',') AS m
            ON m.[Value] = t8.MiscellaneousCodeID
            AND ([t8].[ListingID] = [t0].[ID])
    )
)
...

Я предполагаю, что эти идентификаторы INT. Если они являются строками, просто извлеките CONVERT (INT) в функции (и вы можете использовать NVARCHAR в случае, если вам нужно поддерживать Unicode).

0 голосов
/ 27 октября 2009

Во-первых, я думаю, что у вас есть ошибка ... SELECT TOP 100 вернет случайные 100, а затем ORDER BY [t9]. [DateCreated] DESC их отсортирует. Это не даст вам последние 100 созданных.

Тебе не нужно возвращать 59 столбцов? Ограничьте это.

Я думаю

([t0].[AreaCodeID] IN (@...

должно быть

[t1].[ID] IN (@...

И должен быть уникальный индекс для [dbo]. [AreaCode] .ID

Учитывая, что индексы работают лучше при использовании BETWEEN, а не всех прописанных значений, я бы также посмотрел, смогу ли я свернуть 100 значений до чего-то более похожего: [t1]. [ID] МЕЖДУ @ p1 и @ p2 и [t1]. [ID] in (@ p3 .... Это может быть какая-то кодировка с вашей стороны.

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

0 голосов
/ 27 октября 2009

Там нет ничего, что выглядит "плохо" как таковое. Глядя на это, это кажется страшным, но после некоторого переформатирования и удаления посторонних скобок, это не так уж плохо. Мне никогда не нравились вложенные JOIN-ы, и я смотрю на их очистку, но это личное предпочтение: я не думаю, что это что-то сделает для производительности.

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

По сути, неявное преобразование является плохим, когда SQL Server преобразует данные в столбце базы данных, а не преобразовывает параметр, который сравнивается со столбцом базы данных. Это может произойти, когда столбец базы данных VARCHAR сравнивается с параметром NVARCHAR: SQL Server не может выполнить прямое сравнение, потому что VARCHAR! = NVARCHAR, поэтому он передает данные VARCHAR в столбце таблицы в NVARCHAR перед выполнением сравнения. Результатом является полное сканирование индекса, а не поиск индекса, который может снизить производительность для больших таблиц.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...