Perché si consiglia di archiviare BLOB in tabelle separate di SQL Server?


29

Questa risposta SO molto votata raccomanda di mettere le immagini in tabelle separate, anche se esiste solo una relazione 1: 1 con un'altra tabella:

Se decidi di inserire le tue foto in una tabella di SQL Server, ti consiglio vivamente di utilizzare una tabella separata per archiviare quelle immagini - non conservare la foto dei dipendenti nella tabella dei dipendenti - conservale in una tabella separata. In questo modo, la tabella Employee può rimanere snella, media e molto efficiente, supponendo che non sia sempre necessario selezionare anche la foto del dipendente, come parte delle query.

Perché? Ho avuto l'impressione che SQL Server memorizzi solo un puntatore ad una struttura di dati BLOB dedicata nella tabella, quindi perché preoccuparsi di creare manualmente un altro livello di riferimento indiretto? Migliora davvero significativamente le prestazioni? Se si, perché?

Risposte:


15

Anche se non sono d'accordo sul fatto che i BLOB debbano essere solo in un'altra tabella, non dovrebbero affatto essere nel database . Memorizza un puntatore su dove risiede il file sul disco, quindi prendilo dal database ...

Il problema principale che causano (per me) è con l'indicizzazione. Usando XML con piani di query, perché tutti ce l'hanno fatta, facciamo una tabella:

SELECT TOP 1000
ID = IDENTITY(INT,1,1),
deq.query_plan
INTO dbo.index_test
FROM sys.dm_exec_cached_plans AS dec
CROSS APPLY sys.dm_exec_query_plan(dec.plan_handle) AS deq

ALTER TABLE dbo.index_test ADD CONSTRAINT pk_id PRIMARY KEY CLUSTERED (ID)

Sono solo 1000 righe, ma controllando le dimensioni ...

sp_BlitzIndex @DatabaseName = 'StackOverflow', @SchemaName = 'dbo', @TableName = 'index_test'

Sono oltre 40 MB per solo 1000 file. Supponendo di aggiungere 40 MB ogni 1000 righe, ciò può diventare piuttosto brutto abbastanza rapidamente. Cosa succede quando colpisci 1 milione di righe? Sono solo 1 TB di dati, lì.

NOCCIOLINE

Tutte le query che devono utilizzare l'indice cluster devono ora leggere tutti i dati BLOB nel chiarimento della memoria : quando si fa riferimento alla colonna di dati BLOB.

Riesci a pensare a modi migliori per utilizzare la memoria di SQL Server rispetto alla memorizzazione di BLOB? Perché sicuramente posso.

Espandendolo in indici non cluster:

CREATE INDEX ix_noblob ON dbo.index_test (ID)

CREATE INDEX ix_returnoftheblob ON dbo.index_test (ID) INCLUDE (query_plan)

È possibile progettare gli indici non cluster per evitare ampiamente la colonna BLOB in modo che query regolari possano evitare l'indice cluster, ma non appena è necessaria quella colonna BLOB, è necessario l'indice cluster.

Se lo aggiungi come INCLUDEDcolonna a un indice non cluster per evitare uno scenario di ricerca chiave, finisci con giganteschi indici non cluster:inserisci qui la descrizione dell'immagine

Più problemi che causano:

  • Se qualcuno esegue una SELECT *query, ottengono tutti quei dati BLOB.
  • Occupano spazio nei backup e nei ripristini, rallentandoli
  • Rallentano DBCC CHECKDB, perché so che stai cercando la corruzione, giusto?
  • E se si esegue la manutenzione dell'indice, anche loro rallentano.

Spero che sia di aiuto!


7
Perché gli utenti di solito digitano SELECT *.
Brent Ozar,

Penso che i lati negativi di cui parli siano parte del motivo per cui ha raccomandato di mettere le immagini in un tavolo separato. Se sto eseguendo vari rapporti sugli utenti, non ho bisogno del loro file di immagine. Se sto caricando la pagina del profilo di un singolo utente, allora è quando mi unisco alla tabella BLOB, giusto? Mi sto perdendo qualcosa qui (cioè i tuoi svantaggi in realtà si applicano ancora anche in questo scenario che ho descritto?)
BVernon

11

Quanto sono grandi queste immagini e quante ti aspetti di avere? Anche se per lo più concordo con @sp_BlitzErik , penso che ci siano alcuni scenari in cui va bene farlo, e quindi sarebbe utile avere un quadro più chiaro di ciò che viene effettivamente richiesto qui.

Alcune opzioni da considerare che alleviano la maggior parte degli aspetti negativi evidenziati da Erik sono:

Entrambe queste opzioni sono progettate per essere una via di mezzo tra l'archiviazione di BLOB completamente in SQL Server o completamente all'esterno (ad eccezione di una stringa di colonne per conservare il percorso). Consentono ai BLOB di far parte del modello di dati e di partecipare alle Transazioni senza sprecare spazio nel pool di buffer (ad es. Memoria). I dati BLOB sono ancora inclusi nei backup, il che li rende occupare più spazio e impiegare più tempo per il backup eripristinare. Tuttavia, ho difficoltà a vedere questo come un vero negativo dato che se fa parte dell'app, è necessario eseguirne il backup in qualche modo e avere solo una colonna stringa contenente il percorso è completamente disconnesso e consente ai file BLOB di ottenere cancellato senza alcuna indicazione nel DB (ovvero puntatori non validi / file mancanti). Permette anche di "cancellare" i file all'interno del DB ma esiste ancora sul file system che dovrà eventualmente essere ripulito (ovvero mal di testa). Ma, se i file sono ENORMI, allora forse è meglio lasciare completamente fuori da SQL Server ad eccezione della colonna del percorso.

Questo aiuta con la domanda "dentro o fuori", ma non tocca la domanda a tabella singola rispetto a quella a più tabelle. Posso dire che, al di là di questa domanda specifica, ci sono certamente casi validi per suddividere le tabelle in gruppi di colonne in base ai modelli di utilizzo. Spesso quando si hanno 50 o più colonne, alcune sono accessibili di frequente e altre no. Alcune colonne sono scritte frequentemente mentre altre sono per lo più lette. Separare frequentemente le colonne di accesso vs raramente accedute in più tabelle con una relazione 1: 1 è spesso utile perché perché sprecare lo spazio nel pool di buffer per i dati che probabilmente non si utilizzano (in modo simile al fatto di archiviare regolarmente immagini di grandi dimensioniVARBINARY(MAX)colonne è un problema)? Aumentate anche le prestazioni delle colonne ad accesso frequente riducendo le dimensioni delle righe e adattando quindi più righe in una pagina di dati, rendendo le letture (sia fisiche che logiche) più efficienti. Naturalmente, si introduce anche qualche inefficienza, duplicando il PK e ora a volte è necessario unire le due tabelle, il che complica (anche se solo leggermente) alcune query.

Quindi, ci sono diversi approcci che potresti adottare e ciò che è meglio dipende dal tuo ambiente e da ciò che stai cercando di realizzare.


Ho avuto l'impressione che SQL Server memorizzi solo un puntatore ad una struttura di dati BLOB dedicata nella tabella

Non così semplice Puoi trovare alcune buone informazioni qui, Qual è la dimensione del puntatore LOB per tipi (MAX) come Varchar, Varbinary, Etc? , ma le basi sono:

  • TEXT, NTEXTe IMAGEtipi di dati (per impostazione predefinita): puntatore a 16 byte
  • VARCHAR(MAX), NVARCHAR(MAX), VARBINARY(MAX)(Per impostazione predefinita):
    • Se i dati possono rientrare nella riga, verranno inseriti lì
    • Se i dati sono inferiori a ca. 40.000 byte (il post sul blog collegato mostra 40.000 come limite superiore ma i miei test hanno mostrato un valore leggermente superiore) E se c'è spazio sulla riga per questa struttura, allora ci saranno tra 1 e 5 collegamenti diretti alle pagine LOB, a partire da 24 byte per il primo collegamento ai primi 8000 byte e in aumento di 12 byte per ogni collegamento aggiuntivo per ogni set aggiuntivo di 8000 byte, fino a 72 byte max.
    • Se i dati superano ca. 40.000 byte OPPURE non c'è spazio sufficiente per memorizzare il numero appropriato di collegamenti diretti (ad es. Solo 40 byte rimasti sulla riga e un valore di 20.000 byte richiede 3 collegamenti che sono 24 byte per il primo più 12 per i due collegamenti aggiuntivi per 48 byte spazio in riga necessario), quindi ci sarà solo un puntatore a 24 byte in una pagina dell'albero di testo che contiene i collegamenti alle pagine LOB).

7

Se i dati devono essere archiviati in SQL Server per qualsiasi motivo, posso pensare ad alcuni vantaggi della memorizzazione in una tabella separata. Alcuni sono più convincenti di altri.

  1. Inserendo i dati in una tabella separata, è possibile archiviarli in un database separato. Ciò può avere vantaggi per la manutenzione programmata. Ad esempio, è possibile eseguire DBCC CHECKDBsolo sul database che contiene i dati BLOB.

  2. Se non si inseriscono sempre più di 8000 byte nel BLOB, è possibile che venga archiviato in fila per alcune righe. Potresti non volerlo perché rallenterà le query che accedono ai dati utilizzando l'indice cluster anche se la colonna non è necessaria per la query. Mettere i dati in una tabella separata elimina questo rischio.

  3. Se archiviato fuori riga, SQL Server utilizza un puntatore fino a 24 byte per puntare alla nuova pagina. Ciò occupa spazio e limita il numero totale di colonne BLOB che è possibile aggiungere a una singola tabella. Vedi la risposta di srutzky per maggiori dettagli.

  4. Un indice columnstore cluster non può essere definito su una tabella contenente una colonna BLOB. Questa limitazione è stata rimossa verrà rimossa in SQL Server 2017.

  5. Se alla fine si decide che i dati devono essere spostati al di fuori di SQL Server, potrebbe essere più semplice apportare tale modifica se i dati si trovano già in una tabella separata.


1
Alcuni punti positivi qui (+1). Ma per essere chiari su # 3 (ri: puntatore a 24 byte per i dati fuori riga), ciò non è sempre corretto. Spiego (brevemente) in fondo alla mia risposta come il tipo di dati, la dimensione del valore e la quantità di spazio libero sulla riga determinano la dimensione del puntatore.
Solomon Rutzky,
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.