Come è possibile migliorare le stime delle righe al fine di ridurre le possibilità di sversamenti in tempdb


11

Ho notato che quando si verificano eventi di tipo tempdb (che causano query lente), spesso le stime delle righe sono molto lontane per un particolare join. Ho visto eventi di spill avvenire con merge e hash join e spesso aumentano il tempo di esecuzione da 3x a 10x. Questa domanda riguarda come migliorare le stime delle righe partendo dal presupposto che ridurrà le possibilità di eventi di fuoriuscita.

Numero effettivo di file 40k.

Per questa query, il piano mostra una stima delle righe errate (11,3 righe):

select Value
  from Oav.ValueArray
 where ObjectId = (select convert(bigint, Value) NodeId
                     from Oav.ValueArray
                    where PropertyId = 3331  
                      and ObjectId = 3540233
                      and Sequence = 2)
   and PropertyId = 2840
option (recompile);

Per questa query, il piano mostra una buona stima delle righe (56k righe):

declare @a bigint = (select convert(bigint, Value) NodeId
                       from Oav.ValueArray
                      where PropertyId = 3331
                        and ObjectId = 3540233
                        and Sequence = 2);

select Value
  from Oav.ValueArray
 where ObjectId = @a               
   and PropertyId = 2840
option (recompile);

È possibile aggiungere statistiche o suggerimenti per migliorare le stime delle righe per il primo caso? Ho provato ad aggiungere statistiche con valori di filtro particolari (proprietà = 2840) ma non è stato possibile ottenere la combinazione corretta o forse viene ignorata perché l'ObjectId è sconosciuto al momento della compilazione e potrebbe scegliere una media su tutti gli ObjectId.

Esiste una modalità in cui esegua prima la query del probe e quindi la utilizza per determinare le stime delle righe o deve volare alla cieca?

Questa particolare proprietà ha molti valori (40k) su pochi oggetti e zero sulla stragrande maggioranza. Sarei felice con un suggerimento in cui è possibile specificare il numero massimo di righe previsto per un determinato join. Questo è un problema generalmente inquietante perché alcuni parametri possono essere determinati dinamicamente come parte del join o potrebbero essere posizionati meglio all'interno di una vista (nessun supporto per le variabili).

Esistono parametri che possono essere regolati per ridurre al minimo la possibilità di fuoriuscite in tempdb (ad es. Memoria minima per query)? Il piano robusto non ha avuto alcun effetto sulla stima.

Modifica 2013.11.06 : Risposta a commenti e informazioni aggiuntive:

Ecco le immagini del piano di query. Gli avvertimenti riguardano il predicato cardinalità / cercare con convert ():

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine

Per il commento di @Aaron Bertrand, ho provato a sostituire convert () come test:

create table Oav.SeekObject (
       LookupId bigint not null primary key,
       ObjectId bigint not null
);

insert into Oav.SeekObject (
   LookupId, ObjectId
) VALUES (
   1, 3540233
) 

select Value
  from Oav.ValueArray
 where ObjectId = (select ObjectId 
                     from Oav.SeekObject 
                    where LookupId = 1)
   and PropertyId = 2840
option (recompile);

inserisci qui la descrizione dell'immagine

Come punto di interesse strano ma di successo, ha anche permesso di cortocircuitare la ricerca:

select Value
  from Oav.ValueArray
 where ObjectId = (select ObjectId 
                     from Oav.ValueArray
                    where PropertyId = 2840
                      and ObjectId = 3540233
                      and Sequence = 2)
   and PropertyId = 2840
option (recompile);

inserisci qui la descrizione dell'immagine

Entrambi elencano una corretta ricerca dei tasti ma solo i primi elencano un "Output" di ObjectId. Immagino che indichi che il secondo è davvero un corto circuito?

Qualcuno può verificare se vengono mai eseguite sonde a riga singola per aiutare con le stime delle righe? Sembra sbagliato limitare l'ottimizzazione alle stime dell'istogramma solo quando una ricerca PK a riga singola può migliorare notevolmente l'accuratezza della ricerca nell'istogramma (specialmente se esiste un potenziale di fuoriuscita o cronologia). Quando ci sono 10 di questi sub-join in una vera query, idealmente dovrebbero accadere in parallelo.

Una nota a margine, dal momento che sql_variant memorizza il suo tipo di base (SQL_VARIANT_PROPERTY = BaseType) all'interno del campo stesso, mi aspetterei che un convert () sia quasi privo di costi fintanto che è "direttamente" convertibile (es. Non stringa in decimale ma piuttosto int per int o forse int to bigint). Poiché ciò non è noto al momento della compilazione ma può essere noto all'utente, forse una funzione "AssumeType (tipo, ...)" per sql_variants consentirebbe loro di essere trattati in modo più trasparente.


1
Una prima ipotesi sarebbe che la conversione in bigint stia eliminando le tue stime (il piano di query avrà un avviso in merito in SQL Server 2012) ma d'altra parte, la tua sottoquery non potrebbe mai restituire niente diverso da 0 o 1 righe per una query corretta. Sarebbe interessante vedere i piani di query che hai. Forse come collegamento alla versione XML.
Mikael Eriksson,

2
Cosa guadagni con la subquery in linea? Suggerirei di estrarlo separatamente è complessivamente più chiaro e, dal momento che porta a stime migliori, perché non usare semplicemente questo metodo?
Aaron Bertrand

2
Quale versione di SQL Server? Puoi fornire tabelle e indici DDL e BLOB statistici (a colonna singola e multi-colonna) per le tabelle in modo che possiamo vedere i dettagli del problema? Dividere la query usando declare @a bigint = come hai fatto mi sembra una soluzione naturale, perché è inaccettabile?
Paul White 9

2
Immagino che il tuo design sia un design EAV (molto semplicistico) che ti costringe a usare CONVERT()nelle colonne e poi unirle. Questo non è certamente efficace nella maggior parte dei casi. In questo particolare, è solo un valore da convertire, quindi probabilmente non è un problema ma quali indici hai sul tavolo? I progetti EAV di solito funzionano bene, solo con una corretta indicizzazione (il che significa molti indici nelle tabelle solitamente strette).
ypercubeᵀᴹ

@Paul White, per quanto riguarda la divisione ... va bene come soluzione per questo caso. Ma per più generale / complesso per lo più non voglio rinunciare alla parallelizzazione e alla leggibilità. Supponiamo che ne abbia 10 come sottoquery (alcune più complesse) all'interno di una query, ma solo 5 devono essere "mature" prima che possa iniziare il resto della query - vorrei evitare di capire quali sono 5.
crokusek,

Risposte:


7

Non commenterò versamenti, tempdb o suggerimenti perché la query sembra piuttosto semplice e necessita di tanta considerazione. Penso che l'ottimizzatore di SQL Server farà abbastanza bene il suo lavoro, se ci sono indici adatti per la query.

E la suddivisione in due query è buona in quanto mostra quali indici saranno utili. La prima parte:

(select convert(bigint, Value) NodeId
 from Oav.ValueArray
 where PropertyId = 3331  
   and ObjectId = 3540233
   and Sequence = 2)

ha bisogno di un indice per (PropertyId, ObjectId, Sequence)includere il Value. Lo farei UNIQUEper essere sicuro. La query genererebbe comunque un errore durante il runtime se venisse restituita più di una riga, quindi è bene assicurarsi in anticipo che ciò non accada, con l'indice univoco:

CREATE UNIQUE INDEX
    PropertyId_ObjectId_Sequence_UQ
  ON Oav.ValueArray
    (PropertyId, ObjectId, Sequence) INCLUDE (Value) ;

La seconda parte della query:

select Value
  from Oav.ValueArray
 where ObjectId = @a               
   and PropertyId = 2840

ha bisogno di un indice per (PropertyId, ObjectId)includere Value:

CREATE INDEX
    PropertyId_ObjectId_IX
  ON Oav.ValueArray
    (PropertyId, ObjectId) INCLUDE (Value) ;

Se l'efficienza non viene migliorata o questi indici non vengono utilizzati o ci sono ancora differenze nelle stime delle righe, sarebbe necessario esaminare ulteriormente questa query.

In tal caso, le conversioni (necessarie dalla progettazione EAV e dalla memorizzazione di tipi di dati diversi nelle stesse colonne) sono una causa probabile e la soluzione di suddivisione (come commentano @AAron Bertrand e @Paul White) la query in due parti sembra naturale e la strada da percorrere. Una riprogettazione per avere tipi di dati diversi nelle rispettive colonne potrebbe essere un'altra.


Le tabelle avevano indici di copertura - avrei dovuto affermarlo nella domanda. L'esempio è in realtà un sub join che alimenta una query più ampia, motivo per cui c'è tutta la confusione di fuoriuscite tempdb.
crokusek,

5

Come risposta parziale alla domanda esplicita sul miglioramento delle statistiche ...

Si noti che le stime delle righe anche per il caso separato separatamente sono ancora disattivate di 10X (4k contro 40k previsti).

L'istogramma delle statistiche è stato probabilmente diffuso in modo troppo sottile per quella proprietà perché è una tabella di righe lunga (verticale) da 3,5 M e quella particolare proprietà è estremamente scarsa.

Creare una statistica aggiuntiva (in qualche modo ridondante con le statistiche IX) per la proprietà sparse:

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840

Gli originali:

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine

Con convert () rimosso (corretto):

inserisci qui la descrizione dell'immagine

Con convert () rimosso (cortocircuito):

inserisci qui la descrizione dell'immagine

Probabilmente ancora spento di ~ 2X perché> 99,9% degli oggetti non ha affatto la proprietà 2840 definita su di essi. In effetti, solo per questo caso di test la proprietà esiste solo su 1 di 200k oggetti distinti della tabella di righe 3,5M. È incredibile, si è avvicinato davvero tanto. Regolando il filtro in modo da ridurre il numero di ObjectId,

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 and ObjectId >= 3540000 and ObjectId < 3541000

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 and ObjectId = 3540233;

Hmm, nessun cambiamento ... Sostenuto che aggiunto "con scansione completa" alla fine delle statistiche (potrebbe essere il motivo per cui i due precedenti non hanno funzionato) e sì:

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 with full scan;

inserisci qui la descrizione dell'immagine

Sìì. Quindi, in una tabella altamente verticale con un IX ampiamente coprente, l'aggiunta di ulteriori statistiche filtrate sembra essere un grande miglioramento (in particolare per le combinazioni di tasti sparse ma altamente varianti).


Un collegamento ad alcuni problemi con statistiche a più colonne.
crokusek,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.