Eu tenho uma tabela bastante grande com uma das colunas sendo um dado XML com um tamanho médio de entrada XML sendo ~ 15 kilobytes. Todas as outras colunas são entradas regulares, bigints, GUIDs etc. Para ter alguns números concretos, digamos que a tabela tenha um milhão de linhas e tenha aproximadamente 15 GB de tamanho.
O que notei é que essa tabela é muito lenta para selecionar dados, se eu quiser selecionar todas as colunas. Quando eu faço
SELECT TOP 1000 * FROM TABLE
leva cerca de 20 a 25 segundos para ler os dados do disco - mesmo que eu não imponha nenhuma ordem ao resultado. Eu executo a consulta com o cache frio (ou seja, depois DBCC DROPCLEANBUFFERS
). Aqui estão os resultados das estatísticas de IO:
Contagem de varredura 1, leituras lógicas 364, leituras físicas 24, leituras de leitura antecipada 7191, leituras lógicas de lob 7924, leituras físicas de lob 1690, leituras de leitura antecipada de lob 3968.
Ele captura ~ 15 MB de dados. O plano de execução mostra a Verificação de Índice em Cluster como eu esperaria.
Não há IO acontecendo no disco além das minhas consultas; Também verifiquei se a fragmentação do índice em cluster está próxima de 0%. Esta é uma unidade SATA de nível consumidor, mas ainda acho que o SQL Server poderá verificar a tabela mais rapidamente que ~ 100-150 MB / min.
A presença do campo XML faz com que a maioria dos dados da tabela seja localizada nas páginas LOB_DATA (na verdade, ~ 90% das páginas da tabela são LOB_DATA).
Acho que minha pergunta é: estou correto ao pensar que as páginas LOB_DATA podem causar verificações lentas não apenas por causa de seu tamanho, mas também porque o SQL Server não pode verificar o índice clusterizado de forma eficaz quando há muitas páginas LOB_DATA na tabela?
Ainda mais amplamente - é considerado razoável ter tal estrutura de tabela / padrão de dados? As recomendações para o uso do Filestream geralmente indicam tamanhos de campo muito maiores, então eu realmente não quero seguir esse caminho. Eu realmente não encontrei nenhuma informação boa sobre esse cenário em particular.
Eu estive pensando na compactação XML, mas ela precisa ser feita no cliente ou com o SQLCLR e exigiria bastante trabalho para implementar no sistema.
Tentei a compactação e, como os XMLs são altamente redundantes, posso (no ac # app) compactar XML de 20 KB a ~ 2,5 KB e armazená-lo na coluna VARBINARY, impedindo o uso de páginas de dados LOB. Isso acelera os SELECTs 20 vezes nos meus testes.
fonte
SELECT *
não é o problema se você estiver precisando dos dados XML. É apenas um problema se você não deseja os dados XML. Nesse caso, por que diminuir a velocidade da consulta para recuperar dados que você não usa? Perguntei sobre as atualizações no XML perguntando se a fragmentação nas páginas LOB não estava sendo relatada com precisão. Por isso, perguntei na minha resposta como exatamente você determinou que o índice de cluster não estava fragmentado? Você pode fornecer o comando que você executou? E você fez uma REBUILD completa no Índice de Cluster? (continuação)Respostas:
Apenas ter a coluna XML na tabela não tem esse efeito. É a presença de dados XML que, sob certas condições , faz com que uma parte dos dados de uma linha seja armazenada fora da linha, nas páginas LOB_DATA. E enquanto um (ou talvez vários ;-) possa argumentar que sim, a
XML
coluna implica que realmente haverá dados XML, não é garantido que os dados XML precisem ser armazenados fora da linha: a menos que a linha já esteja preenchida além de serem dados XML, documentos pequenos (até 8000 bytes) podem caber na linha e nunca vão para uma página LOB_DATA.A digitalização refere-se à observação de todas as linhas. Obviamente, quando uma página de dados é lida, todos os dados em linha são lidos, mesmo se você selecionou um subconjunto das colunas. A diferença com os dados LOB é que, se você não selecionar essa coluna, os dados fora da linha não serão lidos. Portanto, não é realmente justo tirar uma conclusão sobre a eficiência com que o SQL Server pode varrer esse índice clusterizado, pois você não testou exatamente isso (ou testou metade dele). Você selecionou todas as colunas, que inclui a coluna XML e, como você mencionou, é onde a maioria dos dados está localizada.
Portanto, já sabemos que o
SELECT TOP 1000 *
teste não estava apenas lendo uma série de 8k páginas de dados, todas seguidas, mas pulando para outros locais a cada linha . A estrutura exata desses dados LOB pode variar de acordo com o tamanho. Com base na pesquisa mostrada aqui ( Qual é o tamanho do ponteiro LOB para tipos (MAX) como Varchar, Varbinary, Etc? ), Existem dois tipos de alocações LOB fora da linha:Uma dessas duas situações ocorre cada vez que você recupera dados LOB com mais de 8000 bytes ou que simplesmente não se encaixam na linha. Publiquei um script de teste no PasteBin.com (script T-SQL para testar alocações e leituras de LOB ) que mostra os três tipos de alocações de LOB (com base no tamanho dos dados), bem como o efeito que cada um deles exerce sobre a lógica e leituras físicas. No seu caso, se os dados XML realmente forem inferiores a 42.000 bytes por linha, nenhum deles (ou muito pouco) deverá estar na estrutura TEXT_TREE menos eficiente.
Se você quiser testar a rapidez com que o SQL Server pode verificar esse Índice em Cluster, faça o,
SELECT TOP 1000
mas especifique uma ou mais colunas que não incluem essa coluna XML. Como isso afeta seus resultados? Deve ser um pouco mais rápido.Dado que temos uma descrição incompleta da estrutura da tabela e do padrão de dados reais, qualquer resposta pode não ser ideal, dependendo de quais são esses detalhes ausentes. Com isso em mente, eu diria que não há nada obviamente irracional na estrutura da tabela ou no padrão de dados.
Isso agilizou a seleção de todas as colunas, ou mesmo apenas os dados XML (agora em
VARBINARY
), mas na verdade gera consultas que não selecionam os dados "XML". Supondo que você tenha cerca de 50 bytes nas outras colunas e umFILLFACTOR
de 100, então:Sem compactação: 15k de
XML
dados devem exigir 2 páginas LOB_DATA, que requerem 2 ponteiros para a raiz embutida. O primeiro ponteiro tem 24 bytes e o segundo é 12, para um total de 36 bytes armazenados em linha para os dados XML. O tamanho total da linha é 86 bytes e você pode ajustar cerca de 93 dessas linhas em uma página de dados de 8060 bytes. Portanto, 1 milhão de linhas requer 10.753 páginas de dados.Compactação personalizada: 2,5 mil
VARBINARY
dados caberão na linha. O tamanho total da linha é de 2610 (2,5 * 1024 = 2560) bytes e você pode ajustar apenas três dessas linhas em uma página de dados de 8060 bytes. Portanto, 1 milhão de linhas requer 333.334 páginas de dados.Portanto, a implementação da compactação personalizada resulta em um aumento de 30x nas páginas de dados para o Clustered Index. Ou seja, todas as consultas que usam uma varredura de índice clusterizado agora têm cerca de 322.500 páginas a mais de dados para ler. Consulte a seção detalhada abaixo para obter ramificações adicionais ao fazer esse tipo de compactação.
Eu recomendaria não fazer refatoração com base no desempenho de
SELECT TOP 1000 *
. Não é provável que seja uma consulta que o aplicativo emita e nem deve ser usada como a única base para otimizações potencialmente desnecessárias.Para informações mais detalhadas e mais testes para tentar, consulte a seção abaixo.
Esta pergunta não pode receber uma resposta definitiva, mas podemos pelo menos progredir e sugerir pesquisas adicionais para ajudar a nos aproximar de descobrir o problema exato (idealmente com base em evidências).
O que nós sabemos:
XML
coluna e vários outros tipos de colunas:INT
,BIGINT
,UNIQUEIDENTIFIER
, "etc."XML
coluna "tamanho" é, em média, aproximadamente 15kDBCC DROPCLEANBUFFERS
, leva de 20 a 25 segundos para que a seguinte consulta seja concluída:SELECT TOP 1000 * FROM TABLE
O que achamos que sabemos:
A compactação XML pode ajudar. Como exatamente você faria a compactação no .NET? Através das classes GZipStream ou DeflateStream ? Esta não é uma opção de custo zero. Certamente compactará alguns dados em uma grande porcentagem, mas também exigirá mais CPU, pois você precisará de um processo adicional para compactar / descomprimir os dados a cada vez. Esse plano também removeria completamente sua capacidade de:
.nodes
,.value
,.query
, e.modify
funções XML.indexe os dados XML.
Lembre-se (desde que você mencionou que o XML é "altamente redundante")
XML
tipo de dados já está otimizado, pois armazena os nomes dos elementos e dos atributos em um dicionário, atribuindo um ID de índice inteiro a cada item e, em seguida, usando esse ID inteiro em todo o documento (portanto, ele não repete o nome completo para cada uso, nem o repete novamente como uma marca de fechamento para elementos). Os dados reais também têm espaço em branco externo removido. É por isso que os documentos XML extraídos não mantêm sua estrutura original e por que os elementos vazios são extraídos como<element />
se fossem inseridos como<element></element>
. Portanto, quaisquer ganhos de compactação via GZip (ou qualquer outra coisa) serão encontrados apenas pela compactação dos valores de elemento e / ou atributo, que é uma área de superfície muito menor que pode ser melhorada do que a maioria esperaria e provavelmente não vale a perda de recursos, conforme observado diretamente acima.Lembre-se também de que a compactação dos dados XML e o armazenamento do
VARBINARY(MAX)
resultado não eliminarão o acesso ao LOB, apenas o reduzirão. Dependendo do tamanho do restante dos dados na linha, o valor compactado pode caber na linha ou ainda pode exigir páginas LOB.Essas informações, embora úteis, não são suficientes. Existem muitos fatores que influenciam o desempenho da consulta, portanto, precisamos de uma imagem muito mais detalhada do que está acontecendo.
O que não sabemos, mas precisamos:
SELECT *
matéria? Esse é um padrão que você usa no código. Se sim, por quê?SELECT TOP 1000 XmlColumn FROM TABLE;
:?Quanto dos 20 a 25 segundos necessários para retornar essas 1000 linhas está relacionado a fatores de rede (obtendo os dados pela conexão) e quanto está relacionado a fatores de cliente (renderizando aproximadamente 15 MB mais o restante dos Dados XML na grade no SSMS ou possivelmente salvando em disco)?
Fatorar esses dois aspectos da operação às vezes pode ser feito simplesmente não retornando os dados. Agora, pode-se pensar em selecionar uma Tabela Temporária ou Variável de Tabela, mas isso apenas introduziria algumas novas variáveis (por exemplo
tempdb
, E / S de disco para , escritas no Log de Transações, possível crescimento automático de dados tempdb e / ou arquivo de log). espaço no buffer pool, etc). Todos esses novos fatores podem realmente aumentar o tempo de consulta. Em vez disso, normalmente armazeno as colunas em variáveis (do tipo de dados apropriado; nãoSQL_VARIANT
) que são substituídas a cada nova linha (ou sejaSELECT @Column1 = tab.Column1,...
).NO ENTANTO , conforme indicado por @PaulWhite neste DBA.StackExchange, a Logical lê diferentes ao acessar os mesmos dados LOB , com pesquisas adicionais publicadas no PasteBin ( script T-SQL para testar vários cenários para leituras LOB ) , LOBs não são acessados de forma consistente entre
SELECT
,SELECT INTO
,SELECT @XmlVariable = XmlColumn
,SELECT @XmlVariable = XmlColumn.query(N'/')
, eSELECT @NVarCharVariable = CONVERT(NVARCHAR(MAX), XmlColumn)
. Portanto, nossas opções são um pouco mais limitadas aqui, mas aqui está o que pode ser feito:Alternativamente, você pode executar a consulta via sqlcmd.exe e direcionar a saída para ir para lugar nenhum via:
-o NUL:
.Qual é o tamanho real dos dados para as
XML
colunas que estão sendo retornadas ? O tamanho médio dessa coluna em toda a tabela não importa realmente se as linhas "TOP 1000" contêm uma parte desproporcionalmente grande do total deXML
dados. Se você deseja conhecer as TOP 1000 linhas, consulte essas linhas. Por favor, execute o seguinte:CREATE TABLE
, incluindo todos os índices.Quais são os resultados exatos da seguinte consulta:
ATUALIZAR
Ocorreu-me que eu deveria tentar reproduzir esse cenário para ver se tenho um comportamento semelhante. Então, criei uma tabela com várias colunas (semelhante à vaga descrição da Pergunta) e a preenchi com 1 milhão de linhas, e a coluna XML possui aproximadamente 15k de dados por linha (veja o código abaixo).
O que eu descobri é que fazer um procedimento
SELECT TOP 1000 * FROM TABLE
concluído em 8 segundos na primeira vez e em 2 a 4 segundos a cada momento (sim, executandoDBCC DROPCLEANBUFFERS
antes de cada execução daSELECT *
consulta). E meu laptop de vários anos não é rápido: SQL Server 2012 SP2 Developer Edition, 64 bits, 6 GB de RAM, dual 2.5 Ghz Core i5 e uma unidade SATA de 5400 RPM. Também estou executando o SSMS 2014, SQL Server Express 2014, Chrome e várias outras coisas.Com base no tempo de resposta do meu sistema, repetirei que precisamos de mais informações (ou seja, informações específicas sobre a tabela e os dados, resultados dos testes sugeridos etc.) para ajudar a diminuir a causa do tempo de resposta de 20 a 25 segundos que você está vendo.
E, como queremos calcular o tempo necessário para ler as páginas que não são do LOB, executei a consulta a seguir para selecionar tudo, exceto a coluna XML (um dos testes sugeridos acima). Isso retorna em 1,5 segundos de forma bastante consistente.
Conclusão (no momento)
Com base na minha tentativa de recriar seu cenário, acho que não podemos apontar a unidade SATA ou a E / S não sequencial como a principal causa dos 20 - 25 segundos, especialmente porque ainda estamos não sabe a rapidez com que a consulta retorna quando não inclui a coluna XML. E não pude reproduzir o grande número de leituras lógicas (não LOB) que você está mostrando, mas tenho a sensação de que preciso adicionar mais dados a cada linha à luz disso e da declaração de:
Minha tabela possui 1 milhão de linhas, cada uma com pouco mais de
sys.dm_db_index_physical_stats
15 mil dados XML e mostra que existem 2 milhões de páginas LOB_DATA. Os 10% restantes seriam 222k IN_ROW páginas de dados, mas eu tenho apenas 11.630 delas. Portanto, mais uma vez, precisamos de mais informações sobre o esquema da tabela e os dados reais.fonte
Sim, a leitura de dados LOB não armazenados em linha leva a E / S aleatória em vez de E / S sequencial. A métrica de desempenho do disco a ser usada aqui para entender por que é rápida ou lenta é IOPS de leitura aleatória.
Os dados LOB são armazenados em uma estrutura em árvore onde a página de dados no índice clusterizado aponta para uma página de Dados LOB com uma estrutura raiz LOB que, por sua vez, aponta para os dados LOB reais. Ao percorrer os nós raiz no índice clusterizado, o SQL Server só pode obter os dados em linha por leituras seqüenciais. Para obter os dados LOB, o SQL Server precisa ir para outro lugar no disco.
Acho que se você mudasse para um disco SSD, não sofreria muito com isso, já que o IOPS aleatório para um SSD é muito maior do que para um disco giratório.
Sim, poderia ser. Depende do que esta tabela está fazendo por você.
Geralmente, os problemas de desempenho com XML no SQL Server acontecem quando você deseja usar o T-SQL para consultar o XML e, mais ainda, quando deseja usar valores do XML em um predicado em uma cláusula where ou associação. Se for esse o caso, você pode dar uma olhada na promoção da propriedade ou nos índices XML seletivos ou em um novo design das estruturas da tabela, destruindo o XML nas tabelas.
Fiz isso uma vez em um produto há mais de 10 anos e lamento desde então. Eu realmente sentia falta de não poder trabalhar com os dados usando o T-SQL, por isso não recomendaria isso a ninguém, se puder ser evitado.
fonte