Database
 sql >> Base de Dados >  >> RDS >> Database

Registro mínimo com INSERT…SELECT em tabelas de heap

Introdução


Alcançando o registro mínimo com INSERT...SELECT pode ser um negócio complicado. As considerações listadas no Guia de Desempenho de Carregamento de Dados ainda são bastante abrangentes, embora também seja necessário ler SQL Server 2016, Registro mínimo e Impacto do tamanho do lote em operações de carregamento em massa por Parikshit Savjani do SQL Server Tiger Team para obter a imagem atualizada para SQL Server 2016 e posterior, ao carregar em massa em tabelas de armazenamento de linhas em cluster. Dito isso, este artigo se preocupa apenas em fornecer novos detalhes sobre registro mínimo ao carregar em massa tabelas heap tradicionais (não “otimizadas para memória”) usando INSERT...SELECT . Tabelas com um índice agrupado de árvore b são abordadas separadamente na segunda parte desta série.


Tabelas de pilha


Ao inserir linhas usando INSERT...SELECT em um heap sem índices não clusterizados, a documentação afirma universalmente que tais inserções serão minimamente registradas contanto que um TABLOCK dica está presente. Isso se reflete nas tabelas de resumo incluídas no Guia de desempenho de carregamento de dados e o posto Tiger Team. As linhas de resumo para tabelas de heap sem índices são as mesmas em ambos os documentos (sem alterações para o SQL Server 2016):



Um TABLOCK explícito dica não é a única maneira de atender ao requisito de bloqueio em nível de tabela . Também podemos definir o 'bloqueio de tabela no carregamento em massa' opção para a tabela de destino usando sp_tableoption ou habilitando o sinalizador de rastreamento documentado 715. (Observação:essas opções não são suficientes para habilitar o registro mínimo ao usar INSERT...SELECT porque INSERT...SELECT não suporta bloqueios de atualização em massa).

O “possível concorrente” coluna no resumo só se aplica a métodos de carregamento em massa diferentes de INSERT...SELECT . O carregamento simultâneo de uma tabela heap não é possível com INSERT...SELECT . Conforme observado no Guia de desempenho de carregamento de dados , carregamento em massa com INSERT...SELECT leva um exclusivo X trava na tabela, não na atualização em massa BU bloqueio necessário para cargas a granel simultâneas.

Tudo isso de lado — e supondo que não haja outra razão para não esperar um registro mínimo ao carregar em massa um heap não indexado com TABLOCK (ou equivalente) — a inserção ainda pode não ser minimamente logado...

Uma exceção à regra


O script de demonstração a seguir deve ser executado em uma instância de desenvolvimento em um novo banco de dados de teste definido para usar o SIMPLE modelo de recuperação. Ele carrega um número de linhas em uma tabela heap usando INSERT...SELECT com TABLOCK , e relatórios sobre os registros de log de transações gerados:
CREATE TABLE dbo.TestHeap( id integer NOT NULL IDENTITY, c1 integer NOT NULL, padding char(45) NOT NULL DEFAULT '');GO-- Limpa o logCHECKPOINT;GO-- Inserir linhasINSERT dbo.TestHeap WITH (TABLOCK ) (c1)SELECT TOP (897) CHECKSUM(NEWID())FROM master.dbo.spt_values ​​AS SV;GO-- Mostrar entradas de logSELECT FD.Operation, FD.Context, FD.[Log Record Length], FD.[Log Reserve], FD.AllocUnitName, FD.[Nome da transação], FD.[Lock Information], FD.[Description]FROM sys.fn_dblog(NULL, NULL) AS FD;GO-- Contar o número de linhas totalmente registradasSELECT [ Linhas totalmente registradas] =COUNT_BIG(*) FROM sys.fn_dblog(NULL, NULL) AS FDWHERE FD.Operation =N'LOP_INSERT_ROWS' AND FD.Context =N'LCX_HEAP' AND FD.AllocUnitName =N'dbo.TestHeap'; 
A saída mostra que todas as 897 linhas foram totalmente registradas apesar de aparentemente atender a todas as condições para log mínimo (apenas uma amostra de registros de log é mostrada por motivos de espaço):



O mesmo resultado é visto se a inserção for repetida (ou seja, não importa se a tabela de heap está vazia ou não). Este resultado contradiz a documentação.

O limite mínimo de log para heaps


O número de linhas que se precisa adicionar em um único INSERT...SELECT declaração para obter registro mínimo em um heap não indexado com bloqueio de tabela ativado depende de um cálculo que o SQL Server executa ao estimar o tamanho total dos dados a serem inseridos. As entradas para este cálculo são:
  • A versão do SQL Server.
  • O número estimado de linhas que levam até Inserir operador.
  • Tamanho da linha da tabela de destino.

Para SQL Server 2012 e anteriores , o ponto de transição para esta tabela específica é 898 linhas . Alterando o número no script de demonstração TOP cláusula de 897 a 898 produz a seguinte saída:



As entradas de log de transações geradas estão relacionadas à alocação de página e à manutenção do Mapa de alocação de índice (IAM) e Espaço livre de página (PFS). Lembre-se de que registro mínimo significa que o SQL Server não registra cada inserção de linha individualmente. Em vez disso, apenas as alterações nos metadados e nas estruturas de alocação são registradas. Alterar de 897 para 898 linhas permite o registro mínimo para esta tabela específica.

Para SQL Server 2014 e posterior , o ponto de transição é 950 linhas para esta tabela. Executando o INSERT...SELECT com TOP (949) usará registro completo – mudando para TOP (950) produzirá registro mínimo .

Os limites não depende da Estimativa de cardinalidade modelo em uso ou o nível de compatibilidade do banco de dados.

O cálculo do tamanho dos dados


Se o SQL Server decide usar carregamento em massa do conjunto de linhas — e, portanto, se o registro mínimo está disponível ou não — depende do resultado de uma série de cálculos realizados em um método chamado sqllang!CUpdUtil::FOptimizeInsert , que retorna true para registro mínimo ou falso para registro completo. Um exemplo de pilha de chamadas é mostrado abaixo:



A essência do teste é:
  • A inserção deve ter mais de 250 linhas .
  • O tamanho total dos dados de inserção deve ser calculado como pelo menos 8 páginas .

A verificação de mais de 250 linhas depende exclusivamente do número estimado de linhas que chegam à Inserção de tabela operador. Isso é mostrado no plano de execução como 'Número estimado de linhas' . Tenha cuidado com isso. É fácil produzir um plano com um número baixo estimado de linhas, por exemplo, usando uma variável no TOP cláusula sem OPTION (RECOMPILE) . Nesse caso, o otimizador estima em 100 linhas, que não atingirão o limite e, portanto, evitará o carregamento em massa e o registro mínimo.

O cálculo do tamanho total dos dados é mais complexo e não corresponde o 'Tamanho estimado da linha' fluindo para a Inserção de tabela operador. A maneira como o cálculo é executado é um pouco diferente no SQL Server 2012 e anterior em comparação com o SQL Server 2014 e posterior. Ainda assim, ambos produzem um resultado de tamanho de linha diferente do que é visto no plano de execução.

O cálculo do tamanho da linha


O tamanho total dos dados de inserção é calculado multiplicando o número estimado de linhas pelo tamanho máximo esperado da linha . O cálculo do tamanho da linha é o ponto que difere entre as versões do SQL Server.

No SQL Server 2012 e anteriores, o cálculo é realizado por sqllang!OptimizerUtil::ComputeRowLength . Para a tabela de heap de teste (deliberadamente projetada com colunas não nulas de comprimento fixo simples usando a FixedVar original formato de armazenamento de linha) um esboço do cálculo é:
  • Inicializar uma FixedVar gerador de metadados.
  • Obtenha informações de tipo e atributo para cada coluna na Inserção de tabela fluxo de entrada.
  • Adicione colunas e atributos digitados aos metadados.
  • Finalize o gerador e solicite o tamanho máximo da linha.
  • Adicionar sobrecarga para o bitmap nulo e número de colunas.
  • Adicione quatro bytes para a linha bits de status e deslocamento de linha para o número de dados de colunas.

Tamanho da linha física


Pode-se esperar que o resultado desse cálculo corresponda ao tamanho da linha física, mas não. Por exemplo, com o controle de versão de linha desativado para o banco de dados:
SELECT DDIPS.index_type_desc, DDIPS.alloc_unit_type_desc, DDIPS.page_count, DDIPS.record_count, DDIPS.min_record_size_in_bytes, DDIPS.max_record_size_in_bytes, DDIPS.avg_record_size_in_bytesFROM sys.dm_db_index_physical_stats (DB_ID(), OBJECT_ID(N'db_index_physical_stats (DB_ID(), OBJECT_ID) U'), 0, -- heap NULL, -- todas as partições 'DETAILED' ) AS DDIPS;

…fornece um tamanho de registro de 60 bytes em cada linha da tabela de teste:



Isso é conforme descrito em Estimar o tamanho de um heap:
  • Tamanho total de bytes de todos os comprimento fixo colunas =53 bytes:
    • id integer NOT NULL =4 bytes
    • c1 integer NOT NULL =4 bytes
    • padding char(45) NOT NULL =45 bytes.
  • Bitmap nulo =3 bytes :
    • =2 + int((Num_Cols + 7) / 8)
    • =2 + int((3 + 7) / 8)
    • =3 bytes.
  • Cabeçalho da linha =4 bytes .
  • Total 53 + 3 + 4 =60 bytes .

Ele também corresponde ao tamanho estimado da linha mostrado no plano de execução:


Detalhes de cálculo interno


O cálculo interno usado para determinar se o carregamento em massa é usado apresenta um resultado diferente, com base no seguinte inserir fluxo informações de coluna obtidas usando um depurador. Os números de tipo usados ​​correspondem a sys.types :
  • Total de comprimento fixo tamanho da coluna =66 bytes :
    • ID do tipo 173 binary(8) =8 bytes (interno).
    • ID do tipo 56 integer =4 bytes (interno).
    • ID do tipo 104 bit =1 byte (interno).
    • ID do tipo 56 integer =4 bytes (id coluna).
    • ID do tipo 56 integer =4 bytes (c1 coluna).
    • ID do tipo 175 char(45) =45 bytes (padding coluna).
  • Bitmap nulo =3 bytes (como antes).
  • Cabeçalho da linha sobrecarga =4 bytes (como antes).
  • Tamanho da linha calculada =66 + 3 + 4 =73 bytes .

A diferença é que o fluxo de entrada que alimenta a Inserção de tabela operador contém três colunas internas extras . Estes são removidos quando o showplan é gerado. As colunas extras compõem o localizador de inserção de tabela , que inclui o marcador (RID ou localizador de linha) como seu primeiro componente. São metadados para a inserção e não acaba sendo adicionado à tabela.

As colunas extras explicam a discrepância entre o cálculo realizado por OptimizerUtil::ComputeRowLength e o tamanho físico das linhas. Isso pode ser visto como um bug :o SQL Server não deve contar colunas de metadados no fluxo de inserção para o tamanho físico final da linha. Por outro lado, o cálculo pode ser simplesmente uma estimativa de melhor esforço usando o genérico atualização operador.

O cálculo também não leva em conta outros fatores, como a sobrecarga de 14 bytes do controle de versão de linha. Isso pode ser testado executando novamente o script de demonstração com um dos isolamento de instantâneo ou leia o isolamento de instantâneos confirmados opções de banco de dados habilitadas. O tamanho físico da linha aumentará em 14 bytes (de 60 bytes para 74), mas o limite para registro mínimo permanece inalterado em 898 linhas.

Cálculo de limite


Agora temos todos os detalhes necessários para ver por que o limite é de 898 linhas para esta tabela no SQL Server 2012 e anteriores:
  • 898 linhas atendem ao primeiro requisito para mais de 250 linhas .
  • Tamanho da linha calculada =73 bytes.
  • Número estimado de linhas =897.
  • Tamanho total dos dados =73 bytes * 897 linhas =65.481 bytes.
  • Total de páginas =65481 / 8192 =7,9932861328125.
    • Isso está logo abaixo do segundo requisito para>=8 páginas.
  • Para 898 linhas, o número de páginas é 8,002197265625.
    • Isto é >=8 páginas então registro mínimo está ativado.

No SQL Server 2014 e posterior , as mudanças são:
  • O tamanho da linha é calculado pelo gerador de metadados.
  • A coluna de inteiro interno no localizador de tabela não está mais presente no fluxo de inserção. Isso representa o unificador , que se aplica apenas a índices. Parece provável que isso tenha sido removido como uma correção de bug.
  • O tamanho esperado da linha muda de 73 para 69 bytes devido à coluna inteira omitida (4 bytes).
  • O tamanho físico ainda é de 60 bytes. A diferença restante de 9 bytes é contabilizada pelo RID extra de 8 bytes e pelas colunas internas de 1 byte no fluxo de inserção.

Para atingir o limite de 8 páginas com 69 bytes por linha:
  • 8 páginas * 8192 bytes por página =65536 bytes.
  • 65535 bytes / 69 bytes por linha =949,7971014492754 linhas.
  • Portanto, esperamos um mínimo de 950 linhas para ativar o carregamento em massa do conjunto de linhas para esta tabela no SQL Server 2014 em diante.

Resumo e Considerações Finais


Em contraste com os métodos de carregamento em massa que suportam tamanho do lote , conforme abordado no post por Parikshit Savjani, INSERT...SELECT em um heap não indexado (vazio ou não) nem sempre resultar em log mínimo quando o bloqueio de tabela for especificado.

Para habilitar o registro mínimo com INSERT...SELECT , o SQL Server deve esperar mais de 250 linhas com um tamanho total de pelo menos uma extensão (8 páginas).

Ao calcular o tamanho total estimado da inserção (para comparar com o limite de 8 páginas), o SQL Server multiplica o número estimado de linhas por um tamanho de linha máximo calculado. SQL Server conta colunas internas presente no fluxo de inserção ao calcular o tamanho da linha. Para SQL Server 2012 e anteriores, isso adiciona 13 bytes por linha. Para SQL Server 2014 e posterior, ele adiciona 9 bytes por linha. Isso afeta apenas o cálculo; não afeta o tamanho físico final das linhas.

Quando o carregamento em massa de heap minimamente registrado está ativo, o SQL Server não inserir linhas uma de cada vez. As extensões são alocadas antecipadamente e as linhas a serem inseridas são coletadas em páginas totalmente novas por sqlmin!RowsetBulk antes de ser adicionado à estrutura existente. Um exemplo de pilha de chamadas é mostrado abaixo:



As leituras lógicas não são relatadas para a tabela de destino quando o carregamento em massa de heap minimamente registrado é usado – a Inserção de tabela O operador não precisa ler uma página existente para localizar o ponto de inserção para cada nova linha.

Os planos de execução atualmente não são exibidos quantas linhas ou páginas foram inseridas usando carregamento em massa do conjunto de linhas e registro mínimo . Talvez essas informações úteis sejam adicionadas ao produto em uma versão futura.