2013-07-10 13 views
5

Ho un problema con una query del database SQL che all'improvviso (ma regolarmente ogni tre settimane) diventa lento.SQL Server query improvvisamente lento

installazione è la seguente:

    Express 2008 R2
  • Il database ha una dimensione di 6 GB (file mdf
  • Windows Server 2008 (non R2) a 64 bit, 8 GB di RAM
  • SQL Server dimensioni)
  • la tabella (Orders) la query è principalmente selezionando dal ha circa 24000 record, altri cinque tabelle unite piccole (100 record o meno)
  • la tabella Orders ha un 012.colonna Report che contiene dati binari (documenti PDF) con una dimensione media di circa 200 a 300 kB (ma può essere fino a 2 MB di tanto in tanto). Più del 90% di questi 24000 ordini ha questa colonna riempita, per gli altri è NULL, vale a dire più del 90% delle dimensioni del database di 6 GB sono dati binari.

La query in questione ha la seguente struttura:

SELECT TOP (30) [Project2].[OrderID] AS [OrderID] 
       -- around 20 columns more 
FROM (SELECT [Project2].[OrderID] AS [OrderID], 
       -- around 20 columns more 
       row_number() OVER (ORDER BY [Project2].[OrderID] ASC) AS [row_number] 
     FROM (SELECT [Filter1].[OrderID] AS [OrderID] 
       -- around 20 columns more 
       FROM (SELECT [Extent1].[OrderID] AS [OrderID] 
        -- around 20 columns more 
        FROM [dbo].[Orders] AS [Extent1] 
        INNER JOIN -- small table 
        LEFT OUTER JOIN -- small table 
        LEFT OUTER JOIN -- small table 
        LEFT OUTER JOIN -- small table 
        LEFT OUTER JOIN -- small table 
        WHERE ([Extent1].[Status] IS NOT NULL) 
         AND (4 = CAST([Extent1].[Status] AS int)) 
         AND ([Extent1].[SomeDateTime] IS NULL) 
         AND ([Extent1].[Report] IS NULL) 
        ) AS [Filter1] 
       OUTER APPLY (SELECT TOP (1) [Project1].[C1] AS [C1] 
          FROM (SELECT CAST([Extent7].[CreationDateTime] AS datetime2) AS [C1], 
               [Extent7].[CreationDateTime] AS [CreationDateTime] 
            FROM [dbo].[OtherTable] AS [Extent7] 
            WHERE [Filter1].[OrderID] = [Extent7].[OrderID] 
           ) AS [Project1] 
          ORDER BY [Project1].[CreationDateTime] DESC 
      ) AS [Limit1] 
     ) AS [Project2] 
) AS [Project2] 
WHERE [Project2].[row_number] > 0 
ORDER BY [Project2].[OrderID] ASC 

Si è generato da una query LINQ to entità da Entity Framework. La query si verifica in alcune varianti che differiscono solo nel primo WHERE clausola:

  • I cinque varianti

    WHERE ([Extent1].[Status] IS NOT NULL) 
        AND (X = CAST([Extent1].[Status] AS int)) 
    

    X può essere tra 0 e 4. Queste domande non sono mai un problema.

  • E le due varianti (*)

    WHERE ([Extent1].[Status] IS NOT NULL) 
        AND (4 = CAST([Extent1].[Status] AS int)) 
        AND ([Extent1].[SomeDateTime] IS NULL) 
        AND ([Extent1].[Report] IS NULL) 
    

    o ... IS NOT NULL... nell'ultima riga. Ho il problema descritto di seguito solo con queste due domande.

Il "fenomeno" è:

  • Le due interrogazioni (*) sono gestiti da 100 a 200 volte al giorno, 5 giorni alla settimana. Si esibiscono con meno di un secondo per circa tre settimane.
  • Dopo tre settimane entrambe le query richiedono improvvisamente più di 60 secondi. (Questa volta aumenta in realtà con la crescente dimensione del database.) Gli utenti ottengono un errore (nella pagina Web, è un'app Web) a causa di un timeout. (Per impostazione predefinita, Entity Framework non sembra attendere più di 30 secondi per il risultato.)
  • Se si incolla la query in SSMS e si esegue la query (in attesa dei 60 secondi) il risultato viene restituito correttamente e lo stesso successivo la query viene eseguita nuovamente in meno di un secondo.
  • Dopo circa tre settimane lo stesso accade di nuovo (ma il tempo le piste della query sarà di 65 o 70 secondi, quindi)

Un ulteriore osservazione:

  • Se ricomincio il processo di servizio di SQL Server nei momenti in cui la query è normale, l'utilizzo della memoria del processo aumenta lentamente. Raggiunge un limite di circa 1,5 GB (set di lavoro privato in Task Manager), passo dopo passo, entro circa una settimana.
  • Se riavvio il processo del servizio SQL Server quando la query è improvvisamente lenta e si attiva nuovamente la query, è possibile controllare in Task Manager che il servizio carica quasi 1 GB in pochi secondi.

In qualche modo ho il sospetto che tutto il problema ha a che fare con la limitazione di memoria (1 GB) dell'edizione Express e la colonna varbinary(MAX) anche se ho appena uso nella clausola WHERE che controlla se il valore della colonna è NULL o no NULL. La colonna Report non è una delle colonne selezionate.

Dato che io sono in esecuzione contro le limitazioni (10 GB file MDF dimensione) della Express Edition prossimo anno più recente sto prendendo in considerazione i cambiamenti in ogni caso:

  • O spostare la colonna binaria ad un altro tavolo e memorizzare il contenuto dall'esterno via FILESTREAM, continuare ad usare l'Express Edition
  • utilizzare uno dei "grandi" edizioni di SQL Server senza le limitazioni Express, mantenere la colonna binaria nella tabella Orders
  • fare entrambe le cose

Domanda: quale potrebbe essere il motivo per cui la query è improvvisamente lenta? Potrebbe uno dei cambiamenti che sto pianificando risolvere il problema o ci sono altre soluzioni?

Modifica

seguente suggerimento del bhamby nei commenti qui sotto ho impostato SET STATISTICS TIME ON in SSMS prima di eseguire nuovamente la query. Quando la query è di nuovo lenta, ottengo un valore elevato per SQL Server parse and compile time, vale a dire: CPU time = 27,3 sec e Elapsed time = 81,9 sec. Il tempo di esecuzione per la query è solo il tempo della CPU = 0,06 secondi e il tempo trascorso = 2,8 secondi. Eseguendo la query una seconda volta dopo questo tempo della CPU 0,06 sec e Tempo trascorso = 0,08 per l'analisi di SQL Server e il tempo di compilazione.

+0

È possibile isolare qualsiasi processo che coincide con questo intervallo di 3 settimane? Sembra strano che qualcosa di diverso dalla concorrenza possa causare quel tipo di prestazioni incoerenti. –

+0

@GoatCO: l'ho controllato più volte, ma non c'è concorrenza con altri processi. La frequenza non è * esattamente * 3 settimane, può essere alcuni giorni più o meno e può succedere al mattino o al pomeriggio. Quando accade non c'è né una CPU elevata né un carico di memoria sul server. E il problema non scompare mai da solo (cosa che mi aspetterei se qualche altro processo concorrente finisse). L'unico modo che ho trovato finora è eseguire la query una volta in SSMS. – Slauma

+0

Ah è interessante. Se lo si esegue in SSMS di volta in volta, è possibile evitare del tutto il rallentamento o si verifica ancora? –

risposta

2

Questo sembra solo uno spreco

SELECT TOP (1) [Project1].[C1] AS [C1] 
FROM (SELECT CAST([Extent7].[CreationDateTime] AS datetime2) AS [C1], 
        [Extent7].[CreationDateTime] AS [CreationDateTime] 
     FROM [dbo].[OtherTable] AS [Extent7] 
     WHERE [Filter1].[OrderID] = [Extent7].[OrderID] 
    ) AS [Project1] 
ORDER BY [Project1].[CreationDateTime] DESC 

è

SELECT max(CAST([Extent7].[CreationDateTime] AS datetime2)) AS [C1] 
    FROM [dbo].[OtherTable] AS [Extent7] 
WHERE [Filter1].[OrderID] = [Extent7].[OrderID] 

date Perché non la memorizzazione come datetime?

non mi piace che si applicano esterno
vorrei creare un #temp che viene eseguito una sola volta e unirsi ad esso
Assicurarsi e dichiarare [OrderID] come PK

SELECT [Extent7].[OrderID], max(CAST([Extent7].[CreationDateTime] AS datetime2)) AS [C1] 
FROM [dbo].[OtherTable] AS [Extent7] 
GROUP BY [Extent7].[OrderID] 

Si potrebbe avere ciclo join in corso

successivo avrei messo questo a # temp2 in modo che si è sicuri che viene eseguito solo una volta
Anche in questo caso essere sicuri di dichiarare IDOrdine come PK

SELECT [Extent1].[OrderID] AS [OrderID] 
        -- around 20 columns more 
        FROM [dbo].[Orders] AS [Extent1] 
        INNER JOIN -- small table 
        LEFT OUTER JOIN -- small table 
        LEFT OUTER JOIN -- small table 
        LEFT OUTER JOIN -- small table 
        LEFT OUTER JOIN -- small table 
        WHERE ([Extent1].[Status] IS NOT NULL) 
         AND (4 = CAST([Extent1].[Status] AS int)) 
         AND ([Extent1].[SomeDateTime] IS NULL) 
         AND ([Extent1].[Report] IS NULL) 

Se l'ordine è solo 24.000 righe, allora qualcosa di stupido sta succedendo perché tu abbia domande più di qualche secondo.

+0

+1: usare 'max' è un buon punto! Ho preso la query LINQ corrispondente da un'altra query in cui seleziono effettivamente più colonne rispetto al solo 'CreationDateTime'. Ma in questa versione 'max' sembra davvero più intelligente. Ci proverò! BTW: 'CreationDateTime' è memorizzato come' datetime2 (0) '. Il '(ridondante)' CAST' è generato da Entity Framework per qualsiasi motivo. 'OrderID' è PK btw. – Slauma

+0

Quindi estrai il cast nel tempo necessario. – Paparazzi

0

Se si tratta di una query eseguita spesso, suggerirei di trasformarla in una stored procedure e utilizzare i risultati della procedura.

In Entity Framework è possibile importare la procedura come Function Import.

È quindi possibile assumere il controllo del piano di esecuzione della stored procedure assegnandolo a query hints o alla lotta Parameter Sniffing.

Sembra che i piani di esecuzione del tuo server non siano aggiornati ogni 3 settimane, quindi il rallentamento.

Inoltre, hai menzionato che stai usando l'SQL a 64 bit. La mia esperienza è stata che SQL a 64 bit non tende a funzionare in modo molto efficiente con le sottoquery. Il mio consiglio è di cercare di evitarli.