2014-09-30 14 views
6

Devo selezionare le righe dalla tabella BUNDLES che hanno uno dei diversi valori SAP_STATE_ID. Tali valori dipendono dal fatto che il rispettivo stato SAP debba essere esportato o meno.Perché la subquery e il join sono così lenti

Questa query viene eseguita molto velocemente (non v'è indice sul campo SAP_STATE_ID) -

SELECT b.* FROM BUNDLES b WHERE b.SAP_STATE_ID IN (2,3,5,6) 

Ma ... mi piacerebbe andare a prendere elenco di ID in modo dinamico, in questo modo:

SELECT b.* FROM BUNDLES b 
WHERE b.SAP_STATE_ID IN 
(SELECT s.SAP_STATE_ID FROM SAP_STATES s WHERE s.EXPORT_TO_SAP = 1) 

E ahi, questa query sta prendendo improvvisamente troppo tempo. Mi aspetto che il server SQL esegua prima la sottoquery (non dipende da alcunché dalla query principale) e quindi esegue tutto come nel mio primo esempio. Ho provato a riscriverlo per usare joins anziché subquery:

SELECT b.* FROM BUNDLES b 
JOIN SAP_STATES s ON (s.SAP_STATE_ID = b.SAP_STATE_ID) 
WHERE s.EXPORT_TO_SAP = 1 

ma ha le stesse scarse prestazioni. Sembra che esegua la subquery per ogni riga della tabella BUNDLES o qualcosa del genere. Non sono molto bravo a leggere i piani di esecuzione, ma ci ho provato. Dice che il costo dell'81% è per la scansione dell'indice della chiave primaria di BUNDLES (non ho idea del perchè dovrebbe fare una cosa del genere, c'è un campo BUNDLE_ID definito come PRIMARY KEY, ma non appare affatto nella query ...)

Qualcuno ha una spiegazione perché SQL Server è così "stupido"? C'è un modo per ottenere ciò che voglio con buone prestazioni ma senza la necessità di fornire un elenco statico di SAP_STATE_IDs?

copione per entrambe le tabelle e rilevanti indici - piano http://mab.to/xbYiI0wKj

di esecuzione per la versione subquery - http://mab.to/8Qh6gpdYZ

piano di query per la versione con join - http://mab.to/YCqeGCUbr

(per qualche motivo queste due piani sembra lo stesso ed entrambi suggeriscono di creare l'indice BUNDLES.SAP_STATE_ID, che è già presente)

+0

Avete un indice su SAP_STATES.SAP_STATE_ID? –

+0

sì, è una chiave primaria in realtà – lot

+0

Quanto tempo impiega il subquery per eseguirlo da solo? – DavidG

risposta

3

Sono abbastanza sicuro che le statistiche sono fuori sui tavoli.Se si vuole farlo funzionare in fretta avrei scritto la query come:

SELECT b.* 
    FROM SAP_STATES s 
INNER LOOP JOIN BUNDLES b 
    ON s.SAP_STATE_ID = b.SAP_STATE_ID 
WHERE s.EXPORT_TO_SAP = 1 

Questo costringe a cicli annidati Unisciti a più SAP_STATES che filtra sul BUNDLES

+0

sì, questo ha fatto il trucco. Mi vergogno di non aver mai sentito parlare di una simile opzione prima d'ora. Devo imparare di più su questa cosa di LOOP. – lot

+0

ho capito bene che la creazione di statistiche corrette dovrebbe fare in modo che l'ottimizzatore scelga il giusto tipo di join e quindi la parola chiave LOOP non sarebbe necessaria qui? – lot

+0

Infatti. Sono abbastanza sicuro che se si esegue la query con Show Plan su questo si vedrà il numero stimato ed effettivo di righe restituite per essere molto diversi. Di solito l'aggiornamento delle statistiche sarà sufficiente per risolvere il problema. In alcuni casi è necessario fare cose più esotiche come "ottimizzare per sconosciuto", ma non penso che questo sia necessario qui. –

0

Dal momento che per qualche motivo ha problemi a andare su mab.to,

vorrei suggerire garantire la seguente

table  index 
sap_states (export_to_sap, sap_state_id) 
bundles  (sap_state_id) 

select 
     b.* 
    from 
     sap_states ss 
     join bundles b 
      on ss.sap_state_id = b.sap_state_id 
    where 
     ss.export_to_sap = 1 
+0

Ho provato a riscrivere la query utilizzando JOINs (come descritto nella domanda), ma con nessun successo. La tua query ha solo scambiato l'ordine dei bundle e delle tabelle sap_states: l'ho provato e rimane lo stesso. – lot

+0

Hai creato un indice su 'bundles.sap_state_id'? Inoltre, pubblica il piano di spiegazioni per la tua query utilizzando un join. – Andrew

+0

sì, c'è un tale piano di query – lot

2

Quando si utilizzano tabelle (temporanee o fisici), il motore SQL costruisce le statistiche contro di essa e ha un'idea molto chiara sul numero di righe in essa e che in questo modo è il miglior approccio di esecuzione per questo. D'altra parte, una tabella calcolata (sotto query) non ha statistiche contro di essa.

Così, mentre per un essere umano potrebbe sembrare semplice dedurre il numero di righe, lo "stupido" Motore SQL non è a conoscenza di tutto ciò. Ora, arrivando alla query, la clausola WHERE s.EXPORT_TO_SAP = 1 sta facendo un mondo di differenza qui. L'indice cluster è ordinato e costruito su SAP_STATE_ID, ma per controllare ulteriormente la clausola WHERE, non ha altra opzione che scansionare l'intera tabella (nel set di dati finale)! Scommetto che se invece di un indice cluster, se ci fosse un indice coperto non cluster sulla colonna SAP_STATE_ID che copriva il campo EXPORT_TO_SAP, avrebbe potuto fare il trucco. Dal momento che le scansioni dell'indice cluster sono generalmente male per le prestazioni, vorrei suggerire di prendere l'approccio di seguito:

SELECT s.SAP_STATE_ID 
into #Sap_State 
FROM SAP_STATES s WHERE s.EXPORT_TO_SAP = 1 

SELECT b.* FROM BUNDLES b 
join #Sap_State a on a.sap_state_id = b.sap_state_id 
+0

sì, questo approccio migliora le prestazioni, I upvote. Ma usare il tavolo temporaneo mi sembra poco strano in questo caso. Per essere puliti al 100%, ci dovrebbe essere anche DROP TABLE #Sap_state dopo la selezione. Così abbiamo realizzato un one-liner originale in tre linee, di cui non sono molto contento. – lot

+0

BTW, perché questo approccio funziona e altri no? Non capisco perché avere valori nella tabella temporanea sia più veloce di averli in un recordset derivante dalla subquery. Si tratta solo dell'ottimizzatore che non crea un piano di esecuzione adeguato in altri casi? – lot

+0

Modificato la mia risposta con spiegazione. – SouravA

Problemi correlati