Se os TVPs forem "visivelmente mais lentos" do que as outras opções, provavelmente você não os está implementando corretamente.
- Você não deve usar uma DataTable, a menos que seu aplicativo tenha uso para ela fora do envio de valores para o TVP. Usando o
IEnumerable<SqlDataRecord>
interface é mais rápida e usa menos memória, pois você não está duplicando a coleção na memória apenas para enviá-la ao banco de dados. Eu tenho isso documentado nos seguintes lugares:- Como posso inserir 10 milhões de registros no menor tempo possível? (muitas informações extras e links aqui também)
- Passar dicionário para procedimento armazenado T-SQL
- Transmissão de dados No SQL Server 2008 a partir de um aplicativo (em SQLServerCentral.com; registro gratuito necessário)
-
Você não deve usarAddWithValue
para o SqlParameter, embora isso provavelmente não seja um problema de desempenho. Mas ainda assim, deve ser:
SqlParameter tvp = com.Parameters.Add("data", SqlDbType.Structured); tvp.Value = MethodThatReturnsIEnumerable<SqlDataRecord>(MyCollection);
- TVPs são variáveis de tabela e, como tal, não mantêm estatísticas. Ou seja, eles relatam ter apenas 1 linha para o Otimizador de consultas. Então, no seu proc, também:
- Use a recompilação em nível de instrução em qualquer consulta usando o TVP para qualquer coisa que não seja um simples SELECT:
OPTION (RECOMPILE)
- Crie uma tabela temporária local (ou seja,
#
único ) e copie o conteúdo do TVP na tabela temporária - Você pode tentar adicionar uma chave primária agrupada ao tipo de tabela definida pelo usuário
- Se estiver usando o SQL Server 2014 ou mais recente, tente usar OLTP na memória/tabelas com otimização de memória. Consulte:Tabela temporária e variável de tabela mais rápidas usando otimização de memória
- Use a recompilação em nível de instrução em qualquer consulta usando o TVP para qualquer coisa que não seja um simples SELECT:
Sobre o motivo pelo qual você está vendo:
insert into @data ( ... fields ... ) values ( ... values ... )
-- for each row
insert into @data ( ... fields ... ) values ( ... values ... )
ao invés de:
insert into @data ( ... fields ... )
values ( ... values ... ),
( ... values ... ),
SE isso é realmente o que está acontecendo, então:
- Se as inserções estiverem sendo feitas em uma transação, não haverá diferença real de desempenho
- A sintaxe de lista de valores mais recente (ou seja,
VALUES (row1), (row2), (row3)
) é limitado a algo como 1.000 linhas e, portanto, não é uma opção viável para TVPs que não possuem esse limite. NO ENTANTO, este não é provavelmente o motivo pelo qual inserções individuais estão sendo usadas, dado que não há limite ao fazerINSERT INTO @data (fields) SELECT tab.[col] FROM (VALUES (), (), ...) tab([col])
, que documentei aqui:Número máximo de linhas para o construtor de valor de tabela . Em vez disso... - O motivo mais provável é que fazer inserções individuais permite transmitir os valores do código do aplicativo para o SQL Server:
- usando um iterador (ou seja, o
IEnumerable<SqlDataRecord>
observado em #1 acima), o código do aplicativo envia cada linha conforme é retornada do método e - construindo os
VALUES (), (), ...
lista, mesmo fazendo oINSERT INTO ... SELECT FROM (VALUES ...)
abordagem (que não está limitada a 1.000 linhas), que ainda exigiria a construção de inteiroVALUES
lista antes de enviar qualquer dos dados no SQL Server. Se houver muitos dados, isso levaria mais tempo para construir a string superlonga e consumiria muito mais memória ao fazê-lo.
- usando um iterador (ou seja, o
Consulte também este whitepaper do SQL Server Customer Advisory Team:Maximizando a taxa de transferência com TVP