Azure SQL DB вызывает тайм-аут соединения для хранимых процедур - PullRequest
0 голосов
/ 06 июля 2018

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

Размер нашей базы данных составляет 14 ГБ , а хранимые процедуры обычно возвращают записи 2k - 20k , и мы используем уровень ценообразования S3 (50 DTU) Лазурной БД.

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

Также, когда я работаю на той же БД с тем же количеством записей на машине с конфигурацией 8 ГБ ОЗУ, Win10 запускается за 15 секунд .

Это моя хранимая процедура:

CREATE PROCEDURE [dbo].[PRSP]   
    @CompanyID INT, 
    @fromDate DATETIME, 
    @toDate DATETIME, 
    @ListMailboxId as MailboxIds Readonly, 
    @ListConversationType as ConversationTypes Readonly
AS
BEGIN       
    SET NOCOUNT ON;    

    SELECT 
        C.ID,
        C.MailboxID,
        C.Status,
        C.CustomerID,
        Cust.FName,
        Cust.LName,
        C.ArrivalDate as ConversationArrivalDate,
        C.[ClosureDate],
        C.[ConversationType],
        M.[From],
        M.ArrivalDate as MessageArrivalDate,
        M.ID as MessageID
    FROM  
        [Conversation] as C
    INNER JOIN
        [ConversationHistory] AS CHis ON (CHis.ConversationID  = C.ID)
    INNER JOIN
        [Message] AS M ON (M.ConversationID = C.ID)
    INNER JOIN
        [Mailbox] AS Mb ON (Mb.ID = C.MailboxID)
    INNER JOIN
        [Customer] AS Cust ON (Cust.ID = C.CustomerID)
    JOIN
        @ListConversationType AS convType ON convType.ID = C.[ConversationType]
    JOIN
        @ListMailboxId AS mailboxIds ON mailboxIds.ID = Mb.ID
    WHERE
        Mb.CompanyID = @CompanyID
        AND ((CHis.CreatedOn > @fromDate
             AND CHis.CreatedOn < @toDate
             AND CHis.Activity = 1
             AND CHis.TagData = '3')
         OR (M.ArrivalDate > @fromDate
             AND M.ArrivalDate < @toDate)) 
END

Это план выполнения:

План выполнения

Пожалуйста, дайте ваши предложения относительно того, какое улучшение необходимо? Также нам нужно обновить мой уровень цен?

В идеале для базы данных объемом 14 ГБ, какой должен быть уровень цен Azure?

Ответы [ 3 ]

0 голосов
/ 06 июля 2018

У меня была такая же проблема на этой неделе, и конечные пользователи заявили о медленном использовании приложения, подключенного к виртуальной машине, размещенной в Azure.Кроме того, у меня есть почти такая же виртуальная машина (4CPU, 14 ГБ ОЗУ и S3, но с 100DTU).

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

Запустите это в SSMS, и если есть индексы таблиц, для которых вы используете свою хранимую процедуру, то вы можете позаботиться об этом:

SELECT dbschemas.[name] as 'Schema',
              dbtables.[name] as 'Table',
              dbindexes.[name] as 'Index',
              indexstats.avg_fragmentation_in_percent,
              indexstats.page_count
FROM sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL, NULL, NULL) AS indexstats
INNER JOIN sys.tables dbtables on dbtables.[object_id] = indexstats.[object_id]
INNER JOIN sys.schemas dbschemas on dbtables.[schema_id] = dbschemas.[schema_id]
INNER JOIN sys.indexes AS dbindexes ON dbindexes.[object_id] = indexstats.[object_id]
WHERE indexstats.database_id = DB_ID()
AND indexstats.index_id = dbindexes.index_id
AND indexstats.avg_fragmentation_in_percent >30
--AND dbindexes.[name] like '%CLUSTER%'
ORDER BY indexstats.avg_fragmentation_in_percent DESC

Подробнее здесь.

Редактировать:

Проверить также, сколько лет статистике:

SELECT 
        sys.objects.name AS table_name,
        sys.indexes.name as index_name, 
        sys.indexes.type_desc as index_type, 
        stats_date(sys.indexes.object_id,sys.indexes.index_id) 
        as last_update_stats_date,
        DATEDIFF(d,stats_date(sys.indexes.object_id,sys.indexes.index_id),getdate()) 
        as stats_age_in_days
FROM  
        sys.indexes
        INNER JOIN sys.objects on sys.indexes.object_id=sys.objects.object_id
WHERE 
        sys.objects.type = 'U' 
        AND
        sys.indexes.index_id > 0 
        --AND sys.indexes.name  Like  '%CLUSTER%'
ORDER BY 
        stats_age_in_days DESC;
GO
0 голосов
/ 06 июля 2018

Этот запрос должен занять от 1 до 3 секунд на вашем компьютере с Windows 10 8Gb RAM. Это займет 15 секунд, потому что SQL Server выбирает плохой план выполнения. В этом случае основной причиной плохого плана выполнения являются неверные оценки, некоторые операторы в плане показывают большую разницу между оценочными и фактическими строками. Например, SQL Server оценил, что ему нужно выполнить только один поиск в кластерном индексе pk_customer, но он выполнил 16 522 поиска. То же самое происходит с [ConversationHistory]. [IX_ConversationID_CreatedOn_Activity_ByWhom] и с [Message]. [IX_ConversationID_ID_ArrivalDt_From_RStatus_Type.

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

  • Обновить статистику
  • Попробуйте OPTION (HASH JOIN) в конце запроса. Это может улучшить производительность или это может замедлить его, это может даже вызвать запрос ошибка.
  • Сохранять данные табличных переменных во временных таблицах и использовать их в запросе. (SELECT * INTO #temp_table FROM @table_variable). Табличные переменные не имеют статистики, вызывающей неверные оценки.
  • Определите первый оператор, где разница между оценочными и фактическими строками достаточно велика. Сплит запрос. Запрос1: SELECT * INTO #operator_result FROM (query equivalent to operator). Query2: напишите запрос, используя #operator_result. Поскольку #operator_result является временной таблицей, SQL Server вынужден пересматривать оценки. В этом случае оператор-нарушитель является хеш-соответствием (внутренним соединением)

Есть и другие способы повысить производительность этого запроса:

  • Избегайте поиска ключей. В кластерный индекс Conversation.PK_dbo.Conversation имеется 16 522 ключевых поиска. Этого можно избежать, создав соответствующий индекс покрытия. В этом случае индекс покрытия следующий:

DROP INDEX [IX_MailboxID] ON [dbo].[Conversation] GO CREATE INDEX IX_MailboxID ON [dbo].[Conversation](MailboxID) INCLUDE (ArrivalDate, Status, ClosureDate, CustomerID, ConversationType)

  • Разделить ИЛИ предикат на UNION или UNION ALL. Например:

вместо:

SELECT *
FROM table
WHERE <predicate1> OR <predicate2>

использование:

SELECT *
FROM table
WHERE <predicate1>
UNION
SELECT *
FROM table
WHERE <predicate2>

Иногда это улучшает производительность.

Применять каждый совет по отдельности и измерять производительность.

РЕДАКТИРОВАТЬ: Вы можете попробовать следующее и посмотреть, если это улучшает производительность:

SELECT 
        C.ID,
        C.MailboxID,
        C.Status,
        C.CustomerID,
        Cust.FName,
        Cust.LName,
        C.ArrivalDate as ConversationArrivalDate,
        C.[ClosureDate],
        C.[ConversationType],
        M.[From],
        M.ArrivalDate as MessageArrivalDate,
        M.ID as MessageID
    FROM  
        @ListConversationType AS convType
        INNER JOIN (
            @ListMailboxId AS mailboxIds
            INNER JOIN
                [Mailbox] AS Mb ON (Mb.ID = mailboxIds.MailboxID)
            INNER JOIN
                [Conversation] as C
                ON C.ID = Mb.ID
        ) ON convType.ID = C.[ConversationType]
        INNER HASH JOIN
            [Customer] AS Cust ON (Cust.ID = C.CustomerID)
        INNER HASH JOIN
            [ConversationHistory] AS CHis ON (CHis.ConversationID  = C.ID)
        INNER HASH JOIN
            [Message] AS M ON (M.ConversationID = C.ID)

    WHERE
        Mb.CompanyID =  @CompanyID
        AND ((CHis.CreatedOn > @fromDate
             AND CHis.CreatedOn < @toDate
             AND CHis.Activity = 1
             AND CHis.TagData = '3')
         OR (M.ArrivalDate > @fromDate
             AND M.ArrivalDate < @toDate)) 

А это:

SELECT 
    C.ID,
    C.MailboxID,
    C.Status,
    C.CustomerID,
    Cust.FName,
    Cust.LName,
    C.ArrivalDate as ConversationArrivalDate,
    C.[ClosureDate],
    C.[ConversationType],
    M.[From],
    M.ArrivalDate as MessageArrivalDate,
    M.ID as MessageID
FROM  
    @ListConversationType AS convType
    INNER JOIN (
        @ListMailboxId AS mailboxIds
        INNER JOIN
            [Mailbox] AS Mb ON (Mb.ID = mailboxIds.MailboxID)
        INNER JOIN
            [Conversation] as C
            ON C.ID = Mb.ID
    ) ON convType.ID = C.[ConversationType]
    INNER MERGE JOIN
        [Customer] AS Cust ON (Cust.ID = C.CustomerID)
    INNER MERGE JOIN
        [ConversationHistory] AS CHis ON (CHis.ConversationID  = C.ID)
    INNER MERGE JOIN
        [Message] AS M ON (M.ConversationID = C.ID)

WHERE
    Mb.CompanyID =  @CompanyID
    AND ((CHis.CreatedOn > @fromDate
         AND CHis.CreatedOn < @toDate
         AND CHis.Activity = 1
         AND CHis.TagData = '3')
     OR (M.ArrivalDate > @fromDate
         AND M.ArrivalDate < @toDate)) 
0 голосов
/ 06 июля 2018

50 DTU эквивалентно 1/2 логического ядра. Подробнее: Использование калькулятора DTU базы данных SQL Azure

...