Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Várias instruções INSERT versus um único INSERT com vários VALUES


Adição: O SQL Server 2012 mostra algum desempenho aprimorado nessa área, mas não parece resolver os problemas específicos indicados abaixo. Aparentemente, isso deve ser corrigido na próxima versão principal depois SQL Server 2012!

Seu plano mostra que as inserções únicas estão usando procedimentos parametrizados (possivelmente parametrizados automaticamente), portanto, o tempo de análise/compilação para eles deve ser mínimo.

Eu pensei em pesquisar um pouco mais sobre isso, então configure um loop (script) e tentei ajustar o número de VALUES cláusulas e registrando o tempo de compilação.

Em seguida, dividi o tempo de compilação pelo número de linhas para obter o tempo médio de compilação por cláusula. Os resultados estão abaixo



Até 250 VALUES cláusulas apresentam o tempo de compilação/número de cláusulas tem uma ligeira tendência de alta mas nada muito dramático.



Mas então há uma mudança repentina.

Essa seção dos dados é mostrada abaixo.
+------+----------------+-------------+---------------+---------------+
| Rows | CachedPlanSize | CompileTime | CompileMemory | Duration/Rows |
+------+----------------+-------------+---------------+---------------+
|  245 |            528 |          41 |          2400 | 0.167346939   |
|  246 |            528 |          40 |          2416 | 0.162601626   |
|  247 |            528 |          38 |          2416 | 0.153846154   |
|  248 |            528 |          39 |          2432 | 0.157258065   |
|  249 |            528 |          39 |          2432 | 0.156626506   |
|  250 |            528 |          40 |          2448 | 0.16          |
|  251 |            400 |         273 |          3488 | 1.087649402   |
|  252 |            400 |         274 |          3496 | 1.087301587   |
|  253 |            400 |         282 |          3520 | 1.114624506   |
|  254 |            408 |         279 |          3544 | 1.098425197   |
|  255 |            408 |         290 |          3552 | 1.137254902   |
+------+----------------+-------------+---------------+---------------+

O tamanho do plano em cache que estava crescendo linearmente cai repentinamente, mas o CompileTime aumenta 7 vezes e o CompileMemory dispara. Este é o ponto de corte entre o plano ser um auto parametrizado (com 1.000 parâmetros) e um não parametrizado. A partir daí, parece ficar linearmente menos eficiente (em termos de número de cláusulas de valor processadas em um determinado tempo).

Não tenho certeza por que isso deveria ser. Presumivelmente, ao compilar um plano para valores literais específicos, ele deve executar alguma atividade que não seja dimensionada linearmente (como classificação).

Não parece afetar o tamanho do plano de consulta em cache quando tentei uma consulta consistindo inteiramente de linhas duplicadas e também não afeta a ordem da saída da tabela das constantes (e como você está inserindo em um heap o tempo gasto na classificação seria inútil de qualquer maneira, mesmo que o fizesse).

Além disso, se um índice clusterizado for adicionado à tabela, o plano ainda mostrará uma etapa de classificação explícita para que não pareça estar classificando em tempo de compilação para evitar uma classificação em tempo de execução.



Eu tentei ver isso em um depurador, mas os símbolos públicos para minha versão do SQL Server 2008 não parecem estar disponíveis, então eu tive que olhar para o equivalente UNION ALL construção no SQL Server 2005.

Um rastreamento de pilha típico está abaixo
sqlservr.exe!FastDBCSToUnicode()  + 0xac bytes  
sqlservr.exe!nls_sqlhilo()  + 0x35 bytes    
sqlservr.exe!CXVariant::CmpCompareStr()  + 0x2b bytes   
sqlservr.exe!CXVariantPerformCompare<167,167>::Compare()  + 0x18 bytes  
sqlservr.exe!CXVariant::CmpCompare()  + 0x11f67d bytes  
sqlservr.exe!CConstraintItvl::PcnstrItvlUnion()  + 0xe2 bytes   
sqlservr.exe!CConstraintProp::PcnstrUnion()  + 0x35e bytes  
sqlservr.exe!CLogOp_BaseSetOp::PcnstrDerive()  + 0x11a bytes    
sqlservr.exe!CLogOpArg::PcnstrDeriveHandler()  + 0x18f bytes    
sqlservr.exe!CLogOpArg::DeriveGroupProperties()  + 0xa9 bytes   
sqlservr.exe!COpArg::DeriveNormalizedGroupProperties()  + 0x40 bytes    
sqlservr.exe!COptExpr::DeriveGroupProperties()  + 0x18a bytes   
sqlservr.exe!COptExpr::DeriveGroupProperties()  + 0x146 bytes   
sqlservr.exe!COptExpr::DeriveGroupProperties()  + 0x146 bytes   
sqlservr.exe!COptExpr::DeriveGroupProperties()  + 0x146 bytes   
sqlservr.exe!CQuery::PqoBuild()  + 0x3cb bytes  
sqlservr.exe!CStmtQuery::InitQuery()  + 0x167 bytes 
sqlservr.exe!CStmtDML::InitNormal()  + 0xf0 bytes   
sqlservr.exe!CStmtDML::Init()  + 0x1b bytes 
sqlservr.exe!CCompPlan::FCompileStep()  + 0x176 bytes   
sqlservr.exe!CSQLSource::FCompile()  + 0x741 bytes  
sqlservr.exe!CSQLSource::FCompWrapper()  + 0x922be bytes    
sqlservr.exe!CSQLSource::Transform()  + 0x120431 bytes  
sqlservr.exe!CSQLSource::Compile()  + 0x2ff bytes   

Então, tirando os nomes no rastreamento de pilha, parece gastar muito tempo comparando strings.

Este artigo da base de conhecimento indica que DeriveNormalizedGroupProperties está associado ao que costumava ser chamado de estágio de normalização do processamento de consultas

Este estágio agora é chamado de vinculação ou algebrização e leva a saída da árvore de análise da expressão do estágio de análise anterior e gera uma árvore de expressão algebrizada (árvore do processador de consultas) para avançar para a otimização (otimização de plano trivial neste caso) [ref].

Eu tentei mais um experimento (Script) que era re-executar o teste original, mas olhando para três casos diferentes.
  1. Nome e sobrenome Strings de 10 caracteres sem duplicatas.
  2. Nome e sobrenome Strings de 50 caracteres sem duplicatas.
  3. Nome e sobrenome Strings de 10 caracteres com todas as duplicatas.



Pode-se ver claramente que quanto mais longas as cordas, piores as coisas ficam e que, inversamente, quanto mais duplicatas, melhores as coisas ficam. Como as duplicatas mencionadas anteriormente não afetam o tamanho do plano em cache, presumo que deve haver um processo de identificação de duplicatas ao construir a própria árvore de expressão algebrizada.

Editar

Um lugar onde essas informações são aproveitadas é mostrado por @Lieven aqui
SELECT * 
FROM (VALUES ('Lieven1', 1),
             ('Lieven2', 2),
             ('Lieven3', 3))Test (name, ID)
ORDER BY name, 1/ (ID - ID) 

Porque em tempo de compilação pode determinar que o Name a coluna não tem duplicatas, ela ignora a ordenação pelo secundário 1/ (ID - ID) expressão em tempo de execução (a classificação no plano tem apenas um ORDER BY coluna) e nenhum erro de divisão por zero é gerado. Se duplicatas forem adicionadas à tabela, o operador de classificação mostrará duas colunas ordenadas e o erro esperado será gerado.