Domanda

Al momento sto affrontando un problema di prestazioni (può portare a problemi di ridimensionamento in seguito). L'applicazione su cui sto lavorando è piuttosto complessa ed è in esecuzione su SQL Server 2005. Ho bisogno di unire 6-7 tabelle per ottenere i dati desiderati. Finora ogni tabella contiene più di 100.000 righe di dati. Lo schema del database non può essere modificato (deve rimanere così com'è). Quindi posso solo provare a ottimizzare il più possibile. Mi vengono in mente 2 cose:

  • Cerca di non unirti al database e lascia che il server delle applicazioni esegua il filtro usando LINQ:

    • Pro: sarà in grado di scalare facilmente aggiungendo altri server di app.
    • Contro: più sforzo; Non sono sicuro se la reattività diminuirà.
  • Il server delle applicazioni rimane così com'è e cerca di ottimizzare il più possibile la query SQL (più indici, ricostruisci frequentemente l'indice ecc.):

    • Pro: sforzo minimo
    • Contro: quando i record della tabella diventano più grandi, il problema tornerà di nuovo

Fondamentalmente la memorizzazione nella cache non è una soluzione per me al momento (problema hardware, problemi di hosting, ecc.) ed è per questo che non l'ho originariamente sviluppato. Ma so quali sono i vantaggi della memorizzazione nella cache e l'ho usato molte volte.

È stato utile?

Soluzione

In generale, esegui l'unione nel DBMS. Se lo fai nel server delle applicazioni, stai scommettendo che puoi fare un lavoro migliore nell'ottimizzare i join rispetto alle persone che hanno scritto il DBMS e (ulteriormente) che puoi superare i loro sforzi migliori abbastanza da compensare il costo di trasferire i dati non uniti attraverso il filo.

Ora, se hai intenzione di fare un prodotto incrociato di due tabelle larghe (diciamo che sono T1, con N1 righe di larghezza W1 e T2 con N2 righe di larghezza W2) senza filtro, allora il DBMS è obbligato a creare e inviare N1 * N2 * (W1 + W2) byte di dati tramite il filo, mentre è possibile aspirare le tabelle separatamente come N1 * W1 + N2 * W2 byte di dati. Se N1 = N2 = 1M e W1 = W2 = 100, questo significa 200 TB contro 200 MB di trasferimento dei dati a favore del cross-product nel server delle app. Ma questo non è esattamente giusto per il DBMS. La maggior parte delle query non è così sciocca: si uniscono su colonne e applicano condizioni e l'ottimizzatore DBMS farà fatica (e automaticamente) a ridurre al minimo il lavoro svolto. Inoltre, ti invierà solo i dati pertinenti; non deve inviare tutte le righe che non corrispondono ai tuoi criteri.

Per mostrare uno scenario alternativo (a favore del DBMS) si consideri un caso in cui T1 ha N1 = 1M righe di larghezza W1 = 100, ma T2 ha N2 = 100K righe di larghezza W2 = 50. C'è un join tra il due tabelle su una colonna intera e quindi ci sono 10 righe in T1 per ognuna in T2. Supponiamo di risucchiare tutti i T1 e T2 sull'app server: ciò richiede N1 * W1 + N2 * W2 = 105 MB di dati. Ma le condizioni del filtro limitano i dati a 1/10 delle righe in T2 e per ogni riga in T1 che corrisponde a una riga in T2, esistono infatti solo 2 righe che corrispondono alle condizioni del filtro. Ora il DBMS sta solo per trasferire N2 * (W1 + W2) / 5 = 3 MB, un risparmio di oltre 100 MB di trasferimento dati da parte del DBMS. Ora, se riesci a essere intelligente e scarica solo N2 * W2 / 10 = 500 KB di dati che corrispondono ai valori in T2, devi comunque ottenere il DBMS per fare il 'semi-join' di T1 sui valori vuoi ottenere le righe giuste da T1 al server delle app. Se hai solo bisogno di un sottoinsieme delle colonne, può esserci un altro set di risparmi. E DBMS tende ad avere pacchetti di ordinamento piuttosto intelligenti; avrai bisogno di un buon pacchetto di ordinamento nel server delle app per presentare i dati nell'ordine corretto.

Normalmente dovrebbe essere una vittoria a mani basse per i join nel DBMS. Se non lo è, è perché stai chiedendo al server di fare più lavoro di quello che può gestire. In tal caso, è necessario verificare se la replica del server di database ha senso o se l'aggiunta di più core, una maggiore larghezza di banda della rete o una maggiore memoria principale farà il lavoro.

Altri suggerimenti

In generale, considero alcuni punti quando parlo di scala:

  1. Ogni quanto viene eseguito? Per query con accesso meno frequente potresti essere in grado di accettare un certo peggioramento delle prestazioni.

  2. Qual è il tasso di crescita / cambiamento? Se i record sono relativamente statici in alcune di queste tabelle, potresti prendere in considerazione la memorizzazione nella cache dei contenuti esternamente in un tipo di file dbm (o qualunque sia l'equivalente di Windows). Ci sono anche cose come memcache che vale la pena guardare. Questo può o non può essere possibile, però. Questo si basa sull'esecuzione di "join" nel codice dell'applicazione.

  3. Profilo. Se ti unisci su colonne indicizzate (e lo sei, non è vero?), Non diminuirai necessariamente con l'aumentare del numero di righe. Questo dipenderà fortemente dal fatto che tu abbia a che fare con relazioni 1: 1 o 1: N, qual è la dimensione media di N, quanta memoria disponibile hai disponibile sul server di database, come spesso vengono calcolate le statistiche della tabella e il tipo di colonne e indici. Se hai a che fare con una relazione 1: 1 ed è unica, il database sarà in grado di eseguire un hash semplice e cercare.

Assicurati di limitare le colonne recuperate a non più di quanto ti serva, soprattutto quando si uniscono molte tabelle, perché se tutto ciò che è necessario per unire due tabelle sono le colonne che sono indicizzate, il database potrebbe non prendere nemmeno in considerazione la tabella affatto; il join può essere eseguito utilizzando solo gli indici. Ciò riduce la contesa e migliora le prestazioni delle query meno ottimali che devono gestire i contenuti effettivi della tabella perché ci sono meno query che eseguono il pull sulla tabella.

Tutti i database relazionali hanno uno strumento o una funzione per visualizzare il piano di esecuzione della query per la query specificata. Usalo Se l'output non ha senso per te, imparalo. Questa è la finestra principale per capire cosa farà il database con una determinata query, quali indici verranno utilizzati, quale numero stimato (o effettivo) di righe che verranno incontrati in ogni fase dell'esecuzione e altre cose divertenti.

Una volta che hai informazioni su cosa sta effettivamente facendo Query Optimizer con la query e hai tutti gli indici / statistiche / selezione delle colonne direttamente, avrai un'idea migliore di dove andare da lì. Se fai tutto il possibile nel database, dovrai guardare usando le cache dei dati e fare cose come andare su un minor numero di tabelle con una clausola where più specifica / migliore.

Dichiarazione di non responsabilità: non ho esperienza diretta con SQL Server, ma ho molta esperienza su altri RDBMS (Oracle, MySQL, PostgreSQL, ecc.) e sull'architettura in generale.

È necessario esaminare quali indici sono già in atto, se essi (e le statistiche) sono aggiornati e se i nuovi indici andrebbero a beneficio del carico di lavoro delle query.

Aggiungendo più server in " Non partecipare " scenario otterrai un aumento delle prestazioni o provando a ottimizzare i join. Hai ragione: il problema tornerà quando avrai più dati.

La soluzione migliore è utilizzare la memorizzazione nella memoria cache. Puoi memorizzare nella cache le relazioni tabella-tabella che sono per lo più di piccole dimensioni e non recuperarle continuamente.

L'ottimale è ridurre al minimo i join, ridurre al minimo le selezioni e quindi memorizzare nella cache i dati modificati raramente. Ciò darà una spinta.

A partire dalle raccomandazioni di Microsoft (così come di altri produttori di DB) sui join - Utilizzali nel modo più ottimale possibile. Dalla mia esperienza - oltre 2-3 si uniscono al numero più alto per selezioni complesse.

Lei dice che ogni tabella ha "più di 100.000 righe" ma non menzioni la quantità di dati che stai selezionando e la complessità del join. Le righe da 100 KB sono non grandi per un server SQL indicizzato e configurato correttamente. Abbiamo join a 17 vie che restituiscono risultati in diversi ms, ma è ben indicizzato e seleziona poche righe. Vorrei esaminare le informazioni di profilazione su SQL Server prima di iniziare a ridisegnare la tua applicazione.

Non trascurare il sovraccarico del trasferimento dei dati tra i server. Ethernet si degrada abbastanza rapidamente sotto carico (penso che la velocità di trasferimento sostenuta sia qualcosa come il 30% della velocità a pacchetto singolo; cioè, il tuo collegamento da 100 Mb / sec in realtà farà solo 30 Mb di traffico intenso). Una volta saturato il tuo collegamento sul server DB, l'aggiunta di altri server app non avrà importanza, perché non sarai in grado di ottenere i dati più velocemente.

Unirti a un server di app ti mette anche in balia di quello più lento. Abbiamo visto il serbatoio delle prestazioni in un sito client e abbiamo scoperto che il server dell'app principale si era arrestato in modo anomalo e la strategia di recupero del client consisteva nel fare in modo che la macchina eseguisse il failover su una macchina virtuale in esecuzione su uno degli altri server. Una specie di soluzione pulita, ma certamente non altrettanto performante. Ho anche visto rallentamenti quando i router si guastano e improvvisamente tutti i tuoi server peer sono a tre o quattro hop invece di essere sulla stessa sottorete.

Basta aggiungere più RAM. Un database che si inserisce interamente nella RAM perdona molti errori.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top