Adição: o SQL Server 2012 mostra algum desempenho aprimorado nesta área, mas não parece resolver os problemas específicos observados abaixo. Aparentemente, isso deve ser corrigido na próxima versão principal após o
SQL Server 2012!
Seu plano mostra que as inserções individuais estão usando procedimentos parametrizados (possivelmente parametrizados automaticamente), então o tempo de análise / compilação para eles deve ser mínimo.
Pensei em analisar isso um pouco mais, então configure um loop ( script ) e tente ajustar o número de VALUES
cláusulas e registrar 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 presentes, o tempo de compilação / número de cláusulas tem uma leve tendência de aumento, mas nada muito dramático.

Mas então ocorre 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 vinha crescendo linearmente, cai de repente, mas o CompileTime aumenta 7 vezes e o CompileMemory dispara. Este é o ponto de corte entre o plano ser auto parametrizado (com 1.000 parâmetros) e um não parametrizado. Depois disso, parece se tornar linearmente menos eficiente (em termos de número de cláusulas de valor processadas em um determinado tempo).
Não sei por que isso deveria ser. Presumivelmente, ao compilar um plano para valores literais específicos, ele deve realizar alguma atividade que não seja escalonada linearmente (como classificação).
Não parece afetar o tamanho do plano de consulta em cache quando tentei uma consulta consistindo inteiramente em linhas duplicadas e nem afeta a ordem de 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 fizesse).
Além disso, se um índice clusterizado for adicionado à tabela, o plano ainda mostra uma etapa de classificação explícita, portanto, não parece estar classificando em tempo de compilação para evitar uma classificação em tempo de execução.

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, em vez disso, tive que olhar para a UNION ALL
construção equivalente 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
Portanto, ir dos nomes no rastreamento de pilha parece levar 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 consulta
Este estágio agora é chamado de ligação ou algebrização e leva a saída da árvore de análise de expressão do estágio de análise anterior e produz uma árvore de expressão algbrizada (árvore do processador de consulta) para avançar para a otimização (otimização de plano trivial neste caso) [ref] .
Eu tentei mais um experimento ( Script ) que era repetir o teste original, mas olhando para três casos diferentes.
- Strings de nome e sobrenome de 10 caracteres sem duplicatas.
- Strings de nome e sobrenome de 50 caracteres sem duplicatas.
- Strings de nome e sobrenome de 10 caracteres com todas as duplicatas.

Pode-se ver claramente que quanto mais longas as strings, piores as coisas ficam e que, inversamente, quanto mais duplicatas, melhor as coisas ficam. Como mencionado anteriormente, as duplicatas não afetam o tamanho do plano em cache, então presumo que deve haver um processo de identificação de duplicatas ao construir a própria árvore de expressão algbrizada.
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)
Como em tempo de compilação, ele pode determinar que a Name
coluna não tem duplicatas, ele pula a ordenação pela 1/ (ID - ID)
expressão secundária em tempo de execução (a classificação no plano tem apenas uma ORDER BY
coluna) e nenhum erro de divisão por zero é gerado. Se duplicatas forem adicionadas à tabela, o operador de classificação mostrará duas ordens por colunas e o erro esperado será gerado.