2014-11-11 18 views
7

Ho una domanda di SQL avanzati per il suo SQL perf guru là fuori :-)SQL Server - non eseguire come previsto, non comportandosi come ho pensato che sarebbe

Attualmente sto cercando di capire un po 'di comportamento in un applicazione più grande, ma si riduce a una query su queste due tabelle:

  • Users tavolo - circa 750 voci, UserId (varchar(50)) come PK cluster
  • ActionLog tavola - di milioni di voci, include UserId - ma nessuna relazione FK

Per una griglia nella mia applicazione ASP.NET, sto cercando di ottenere tutti gli utenti oltre alla data dell'ultima voce di registro.

L'istruzione SQL che è attualmente in uso simile a questa:

SELECT 
    UserId, (other columns), 
    LastLogDate = (SELECT TOP (1) [Timestamp] FROM dbo.ActionLog a WHERE a.UserId = u.UserId ORDER BY [Timestamp] DESC) 
FROM 
    dbo.Users u; 

e restituisce le righe da mostrare - ma è piuttosto lento (circa 20 secondi.).

Il mio primo pensiero è stato quello di aggiungere un indice sul tavolo ActionLog su UserId e di includere la colonna Timestamp in esso:

CREATE NONCLUSTERED INDEX [IDX_UserId] 
ON [dbo].[ActionLog]([UserId] ASC) 
INCLUDE ([Timestamp]) 

Le righe sono ora restituiti molto rapidamente - meno di 2 secondi, con 350'000 voci nella tabella ActionLog e il mio indice viene utilizzato correttamente, come indicato dal piano di esecuzione. Tutto sembra a posto.

Ora, per approssimare lo scenario di produzione, abbiamo caricato circa 2 milioni di righe nella tabella ActionLog, il 95% o più dei quali si riferiscono a un utente non-esistente (vale a dire queste righe hanno un UserId che non esiste nel Tabella Users).

Ora la query diventa estremamente lenta (24 minuti!) E l'indice non viene più utilizzato.

ho pensato che dal momento che la stragrande maggioranza delle voci nella tabella ActionLog non si allineano con un utente esistente, vorrei vedere i guadagni di prestazioni se uso un indice filtrato - a "estirpare" tutti coloro disordinato le voci senza una corrispondente utente - così ho creato questo indice (che sostituisce l'altro che esisteva prima):

CREATE NONCLUSTERED INDEX [IDX_UserId] 
ON [dbo].[Log]([UserId] ASC) 
INCLUDE ([Timestamp]) 
WHERE UserId <> 'user' -- that's the fixed, non-existing "UserId" I wanted to avoid 

Ma con mio grande sgomento - l'interrogazione è ancora circa lo stesso - dura più di 20 minuti per completare. Ho aggiornato le statistiche - nessuna modifica - ancora estremamente lento.

La cosa divertente (per me) è: quando ho rilasciato l'indice e lo ho ricreato -> ora la query è stata davvero molto veloce (ancora in meno di 3 secondi). WOW!

Ma non appena inizio ad aggiungere più voci, la query "si inclina" e diventa veramente molto lenta .......

Non capisco perché questo sta accadendo - stavo pensando che con un indice filtrato che elimina tutte quelle voci "canaglia", vedrei buone prestazioni nel cercare di trovare la più recente voce ActionLog per gli utenti esistenti - ma questo non sembra essere il caso.

PERCHÉ NON?

Qualche idea? Pensieri? Cose da provare ??

+0

di poter postare o descrivere il piano di esecuzione quando si rilascia e ricrea l'indice vs quando si inizia ad aggiungere più voci e si blocca? Sta solo decidendo di non usare l'indice dopo aver aggiunto le righe? – jimdrang

+1

Prova ad aggiungere l'espressione filtro alla tua sottoquery correlata 'a.UserId <> 'utente''. Penso che l'ottimizzatore ne abbia bisogno per considerare l'indice filtrato. –

+0

Esistono alcuni suggerimenti su come creare e utilizzare indici filtrati su MSDN. Si potrebbe provare a specificare 'WITH (INDEX (IDX_UserId))' alla fine della clausola 'SELECT'. Riferimento: [Crea indici filtrati] (http://msdn.microsoft.com/en-us/library/cc280372.aspx) –

risposta

3

In primo luogo, INCLUDE qui non è la scelta migliore. Si ordina per data di ingresso, ma le colonne incluse non sono ordinate. Meglio soluzione potrebbe essere:

CREATE NONCLUSTERED INDEX [IX_ActionLog_UserIdTimestamp] ON [dbo].[ActionLog] 
([UserId], [Timestamp]); 

In secondo luogo, sembra che potrebbe essere necessario aggiornare le statistiche sul vostro indice più spesso di un aggiornamento automatico avrebbe fatto. Ho visto casi in cui, in una situazione simile alla tua, ho dovuto aggiornare le statistiche ogni 10 minuti, a causa di inserimenti eccessivi. Era il 2005, comunque.

+0

Non sai quale vantaggio aggiungere il "Timestamp" all'indice stesso - poiché è la seconda colonna dell'indice, questo indice non potrà essere usato per ordinamento per 'Timestamp' ..... (lo proverò in ufficio più tardi e riferirò indietro.) Inoltre: l'aggiornamento delle statistiche non sembra aiutare :-((che è stato un po 'di confusione per me) –

+1

@marc_s, l'indice sarà ordinato per UserId, quindi (all'interno di ciascun utente) per Timestamp. Ciò ti consentirà di individuare rapidamente d ultimo (es. max()) data per ciascuno di essi. La colonna inclusa non offre un tale vantaggio, afair. –

+0

Roger - hai perfettamente ragione. Questo indice sembra funzionare meglio di quello che ho avuto. Grazie! –

-1

Cancella la selezione secondaria:

SELECT u.UserId, Max(a.TimeStamp) As LastLogDate 
FROM dbo.Users u 
,  dob.ActionLog a 
Where a.UserId = u.UserId 
Group By u.UserId; 

Poi pensare di ottenere le altre colonne.

+1

OK - ma allora raccomanderei ** non ** usando l'elenco di tabelle "separate da virgole" (questo stile è stato deprecato con lo standard SQL: 92 - più di 20 anni fa!) - usa la sintassi ** corretta ** ANSI JOIN! –

+0

diavolo, sì, ma dato che sono costretto a usare qualche vecchia merda sul lavoro che ha PROBS con prober Sintassi JOIN, la merda sta per succedere :(thanx! –

2

Prova questa ricerca e vedere come si svolge con il vostro indice originale o con l'modificato suggerite da @Roger Lupo:

SELECT u.UserId, a.LastLogDate 
FROM dbo.Users u 
INNER JOIN (
    SELECT UserId, Max([TimeStamp]) AS LastLogDate 
    FROM dbo.ActionLog 
    WHERE userid <> 'user' -- the user to filter out 
    GROUP BY UserId 
) a ON a.UserId = u.UserId 

Se fa schifo io cancellare la risposta :)

+0

Non fa schifo - è altrettanto buono, se non addirittura migliore, di quello che Roger ha suggerito. Le prestazioni sono stabili e molto veloci - grazie! –

+0

@marc_s Ah ok, questo è bello sapere. Ovviamente non avevo una grande quantità di dati adeguati per testarlo. Buono a sapersi, ha funzionato bene. – jpw

Problemi correlati