Por que é recomendável armazenar BLOBs em tabelas separadas do SQL Server?

29

Esta resposta SO altamente votada recomenda colocar imagens em tabelas separadas, mesmo se houver apenas um relacionamento 1: 1 com outra tabela:

Se você decidir colocar suas fotos em uma tabela do SQL Server, eu recomendo usar uma tabela separada para armazenar essas fotos - não armazene a foto do funcionário na tabela de funcionários - mantenha-as em uma tabela separada. Dessa forma, a tabela Employee pode permanecer enxuta, mesquinha e muito eficiente, desde que você nem sempre precise selecionar a foto do funcionário, como parte de suas consultas.

Por quê? Fiquei com a impressão de que o SQL Server armazena apenas um ponteiro para alguma estrutura de dados BLOB dedicada na tabela. Por que se preocupar em criar manualmente outra camada de indireção? Realmente melhora significativamente o desempenho? Se sim, por que?

Heinzi
fonte

Respostas:

15

Embora eu discorde que os BLOBs devam estar em outra tabela - eles não devem estar no banco de dados . Armazene um ponteiro para onde o arquivo está no disco e obtenha-o no banco de dados ...

O principal problema que eles causam (para mim) é com a indexação. Usando XML com planos de consulta, porque todo mundo os pegou, vamos fazer uma tabela:

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)

São apenas 1000 linhas, mas verificando o tamanho ...

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

Tem mais de 40 MB para apenas 1000 linhas. Supondo que você adicione 40 MB a cada 1000 linhas, isso pode ficar muito feio rapidamente. O que acontece quando você atinge 1 milhão de linhas? Isso significa apenas 1 TB de dados.

NUTS

Todas as consultas que precisam usar o índice em cluster agora precisam ler todos esses dados BLOB no esclarecimento da memória : quando a coluna de dados BLOB é referenciada.

Você consegue pensar em maneiras melhores de usar a memória do SQL Server do que em armazenar BLOBs? Porque com certeza posso.

Expandindo-o para índices não clusterizados:

CREATE INDEX ix_noblob ON dbo.index_test (ID)

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

Você pode projetar seus índices não clusterizados para evitar amplamente a coluna BLOB, para que consultas regulares possam evitar o índice clusterizado, mas assim que você precisar dessa coluna BLOB, precisará do índice clusterizado.

Se você adicioná-lo como uma INCLUDEDcoluna a um índice não clusterizado para evitar um cenário de pesquisa principal, você terá índices gigantescos não clusterizados:insira a descrição da imagem aqui

Mais problemas que eles causam:

  • Se alguém executa uma SELECT *consulta, obtém todos os dados BLOB.
  • Eles ocupam espaço em backups e restaurações, diminuindo a velocidade
  • Eles diminuem a velocidade DBCC CHECKDB, porque eu sei que você está verificando corrupção, certo?
  • E se você fizer alguma manutenção de índice, eles também diminuirão a velocidade.

Espero que isto ajude!

Erik Darling
fonte
7
Porque os usuários geralmente digitam SELECT *.
Brent Ozar
Eu acho que as desvantagens que você menciona são parte do motivo pelo qual ele recomendou colocar as fotos em uma tabela separada. Se estou executando vários relatórios sobre os usuários, não preciso do arquivo de imagem deles. Se estou carregando a página de perfil de um único usuário, é quando ingresso na tabela de blob, certo? Estou faltando alguma coisa aqui (ou seja, suas desvantagens ainda se aplicam mesmo nesse cenário que descrevi?)
BVernon
11

How large are these images, and how many do you expect to have? While I mostly agree with @sp_BlitzErik, I think there are some scenarios where it is ok to do this, and so it would help to have a clearer picture of what is actually being requested here.

Algumas opções a considerar que aliviam a maioria dos aspectos negativos apontados por Erik são:

Essas duas opções foram projetadas para serem um meio termo entre o armazenamento de BLOBs totalmente no SQL Server ou totalmente fora (exceto por um colun de seqüência de caracteres para manter o caminho). Eles permitem que os BLOBs façam parte do modelo de dados e participem das Transações sem desperdiçar espaço no buffer pool (ou seja, memória). Os dados do BLOB ainda estão incluídos nos backups, o que os faz ocupar mais espaço e levar mais tempo para fazer backup erestaurar. No entanto, tenho dificuldade em ver isso como um verdadeiro negativo, pois se ele faz parte do aplicativo, é necessário fazer backup de alguma forma, e ter apenas uma coluna de string contendo o caminho é completamente desconectado e permite que os arquivos BLOBs sejam recebidos. excluído sem indicação do que no DB (isto é, ponteiros inválidos / arquivos ausentes). Ele também permite que os arquivos sejam "excluídos" dentro do banco de dados, mas ainda existem no sistema de arquivos que precisará ser limpo (por exemplo, dor de cabeça). Mas, se os arquivos forem ENORMES, talvez seja melhor deixar completamente fora do SQL Server, exceto a coluna do caminho.

Isso ajuda com a pergunta "dentro ou fora", mas não toca na única tabela versus a questão da tabela múltipla. Posso dizer que, além dessa pergunta específica, certamente existem casos válidos para dividir tabelas em grupos de colunas com base nos padrões de uso. Frequentemente, quando se tem 50 ou mais colunas, há algumas que são acessadas com frequência e outras que não. Algumas colunas são gravadas com frequência, enquanto outras são lidas principalmente. Separar colunas de acesso frequente e acessado com pouca frequência em várias tabelas com um relacionamento de 1: 1 costuma ser benéfico, porque por que desperdiçar o espaço no Buffer Pool para dados que você provavelmente não está usando (semelhante ao por que armazenar imagens grandes regularmenteVARBINARY(MAX)colunas é um problema)? Você também aumenta o desempenho das colunas de acesso frequente, reduzindo o tamanho da linha e, portanto, ajustando mais linhas em uma página de dados, tornando as leituras (físicas e lógicas) mais eficientes. Obviamente, você também apresenta alguma ineficiência ao precisar duplicar a PK, e agora às vezes precisa juntar as duas tabelas, o que também complica (mesmo que apenas um pouco) algumas consultas.

Portanto, existem várias abordagens que você pode adotar e o melhor depende do seu ambiente e do que você está tentando realizar.


Fiquei com a impressão de que o SQL Server armazena apenas um ponteiro para alguma estrutura de dados BLOB dedicada na tabela

Não tão simples. Você pode encontrar algumas informações boas aqui: Qual é o tamanho do ponteiro LOB para tipos (MAX) como Varchar, Varbinary, Etc? , mas o básico é:

  • TEXT, NTEXT, E IMAGEtipos de dados (por padrão): ponteiro de 16 bytes
  • VARCHAR(MAX), NVARCHAR(MAX), VARBINARY(MAX)(Por padrão):
    • Se os dados puderem caber na linha, eles serão colocados lá
    • Se os dados forem inferiores a aprox. 40.000 bytes (a postagem do blog vinculado mostra 40.000 como o limite superior, mas meus testes mostraram um valor um pouco mais alto) E se houver espaço na linha para essa estrutura, haverá entre 1 e 5 links diretos para as páginas LOB, começando em 24 bytes para o primeiro link para os primeiros 8000 bytes e aumentando 12 bytes por cada link adicional para cada conjunto adicional de 8000 bytes, com no máximo 72 bytes.
    • Se os dados ultrapassarem aprox. 40.000 bytes OU não há espaço suficiente para armazenar o número apropriado de links diretos (por exemplo, apenas 40 bytes restantes na linha e um valor de 20.000 bytes precisa de 3 links, que são 24 bytes para o primeiro mais 12 para os dois links adicionais para 48 bytes espaço total necessário em linha), haverá apenas um ponteiro de 24 bytes para uma página da árvore de texto que contém os links para as páginas LOB).
Solomon Rutzky
fonte
7

Se os dados precisarem ser armazenados no SQL Server por qualquer motivo, posso pensar em alguns benefícios para armazená-los em uma tabela separada. Alguns são mais convincentes que outros.

  1. Colocar os dados em uma tabela separada significa que você pode armazená-los em um banco de dados separado. Isso pode ter vantagens para a manutenção programada. Por exemplo, você pode executar DBCC CHECKDBapenas no banco de dados que contém os dados BLOB.

  2. Se você nem sempre coloca mais de 8000 bytes no BLOB, é possível que ele seja armazenado em linha por algumas linhas. Você pode não querer isso, porque isso atrasará as consultas que acessam dados usando o índice clusterizado, mesmo que a coluna não seja necessária. Colocar os dados em uma tabela separada remove esse risco.

  3. Quando armazenado fora da linha, o SQL Server usa um ponteiro de até 24 bytes para apontar para a nova página. Isso ocupa espaço e limita o número total de colunas BLOB que você pode adicionar a uma única tabela. Veja a resposta de srutzky para mais detalhes.

  4. Um índice columnstore clusterizado não pode ser definido em uma tabela que contém uma coluna BLOB. Essa limitação foi removida será removida no SQL Server 2017.

  5. Se você decidir que os dados devem ser movidos para fora do SQL Server, pode ser mais fácil fazer essa alteração se os dados já estiverem em uma tabela separada.

Joe Obbish
fonte
1
Alguns bons pontos aqui (+1). Mas, para deixar claro o número 3 (re: ponteiro de 24 bytes para dados fora da linha), isso nem sempre é correto. Explico (brevemente) na parte inferior da minha resposta, como o tipo de dados, tamanho do valor e quantidade de espaço livre na linha determinam o tamanho do ponteiro.
Solomon Rutzky