Quando falamos em performance no SQL Server, normalmente pensamos em índices ausentes, estatísticas desatualizadas ou consultas mal escritas. Mas existe um problema extremamente comum e muitas vezes ignorado capaz de transformar uma consulta rápida em um gargalo: as conversões implícitas (implicit conversions).
Uma simples diferença entre os tipos de dados utilizados em uma consulta pode fazer o SQL Server abandonar um Index Seek e executar um Index Scan, aumentando drasticamente o consumo de CPU, as leituras lógicas e o tempo de resposta.
Com a popularização dos ORMs (Entity Framework, Hibernate, NHibernate e outros), o problema ficou ainda mais frequente: muitos frameworks enviam parâmetros usando tipos genéricos ou incompatíveis com a definição das colunas do banco e o desenvolvedor raramente percebe.
O que é uma Conversão Implícita?
Uma conversão implícita ocorre quando o SQL Server precisa converter automaticamente um dos lados de uma comparação para que a operação possa ser executada.
WHERE CodigoCliente = N'1000'
Se a coluna CodigoCliente for INT, o SQL Server precisará converter um dos lados da expressão para que os tipos sejam compatíveis.
A peça-chave aqui é a precedência de tipos de dados. O SQL Server tem uma ordem que define qual lado será convertido (por exemplo, NVARCHAR tem precedência sobre VARCHAR, e tipos numéricos têm precedência sobre tipos de texto). Quando a conversão recai sobre o literal/parâmetro, normalmente não há prejuízo. O problema grave aparece quando a conversão recai sobre a coluna: a condição deixa de ser sargable e o otimizador é forçado a abandonar o Index Seek.
Em outras palavras: nem toda conversão implícita é prejudicial — o que importa é qual lado é convertido.
Demonstração Prática
Vamos criar uma tabela com 1 milhão de registros.DROP TABLE IF EXISTS dbo.Clientes;
GO
CREATE TABLE dbo.Clientes
(
CodigoCliente INT NOT NULL,
Nome VARCHAR(100) NOT NULL
);
GO
CREATE CLUSTERED INDEX IX_Clientes
ON dbo.Clientes (CodigoCliente);
GO
WITH Numeros AS ( SELECT TOP (1000000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS Numero FROM sys.objects a CROSS JOIN sys.objects b CROSS JOIN sys.objects c ) INSERT INTO dbo.Clientes SELECT Numero, CONCAT('Cliente ', Numero) FROM Numeros; GO
Consulta utilizando o tipo correto
SET STATISTICS IO, TIME ON;
GO
SELECT *
FROM dbo.Clientes
WHERE CodigoCliente = 500000;
GO
Resultado esperado:
- Index Seek
- Pouquíssimas leituras lógicas
- CPU próxima de zero


Conversão implícita que NÃO quebra o Seek
Este é o ponto onde muitos artigos erram, então vale a atenção:
SELECT *
FROM dbo.Clientes
WHERE CodigoCliente = N'500000';


Ao analisar o plano de execução, você verá o aviso de conversão:
CONVERT_IMPLICIT(int, [@1], 0)
Repare que pela regra de precedência, tipos numéricos vencem tipos de texto. Aqui o SQL Server converte o literal (N'500000') para INT, não a coluna, com isso o Index Seek é preservado e a performance permanece praticamente idêntica.
A lição é importante: ver CONVERT_IMPLICIT no plano não significa, por si só, um problema de performance. O que precisamos investigar é se a conversão está caindo sobre a coluna.
O Exemplo Clássico (e perigoso): VARCHAR vs NVARCHAR
Este cenário é extremamente comum em aplicações .NET, e aqui a conversão realmente derruba a performance.
DROP TABLE IF EXISTS dbo.Produtos
GO
CREATE TABLE dbo.Produtos
(
Codigo VARCHAR(20) NOT NULL,
Descricao VARCHAR(100)
)
CREATE INDEX IX_Produtos_Codigo
ON dbo.Produtos (Codigo);
GO
WITH Numeros AS ( SELECT TOP (500000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS Numero FROM sys.objects a CROSS JOIN sys.objects b ) INSERT INTO dbo.Produtos SELECT CAST(Numero AS VARCHAR(20)), CONCAT('Produto ', Numero)FROM Numeros
Consulta correta (parâmetro VARCHAR, igual à coluna):SELECT *;
FROM dbo.Produtos
WHERE Codigo = '200000'


Consulta com parâmetro NVARCHAR (como muitos ORMs enviam por padrão):
DECLARE @Codigo NVARCHAR(20) = N'200000'
SELECT *
FROM dbo.Produtos
WHERE Codigo = @Codigo;


Como NVARCHAR tem precedência sobre VARCHAR, o SQL Server precisa converter a coluna VARCHAR para NVARCHAR. Intuitivamente, isso deveria quebrar o Seek e em muitos artigos é apresentado como certeza absoluta. Mas aqui entra um detalhe que separa quem realmente entende do assunto: o comportamento depende da collation da coluna.
Coluna com SQL collation (ex: SQL_Latin1_General_CP1_CI_AS): o otimizador não consegue mapear a conversão para uma busca no índice, neste caso o Index Scan.
Coluna com Windows collation (ex: Latin1_General_CI_AI) o SQL Server consegue aplicar um range predicate interno (um Seek com GetRangeThroughConvert) e, na maioria dos casos, mantém o Index Seek, mesmo com o CONVERT_IMPLICIT aparecendo no plano.
Como podemos ver a instancia do meu servidor é a Latin1_General_CI_AI.

Ou seja, se você testou esse exato cenário e viu o Seek se manter, não é bug nem engano é a collation Windows trabalhando a seu favor. Por isso esse exemplo, sozinho, não é confiável para demonstrar uma quebra de Seek, o resultado muda conforme o banco.
💡 Na prática: trate coluna VARCHAR = parâmetro NVARCHAR como risco, não como falha garantida. Ele pode quebrar o Seek dependendo da collation, da versão e da cardinalidade. O ideal continua sendo alinhar os tipos.
O Vilão Definitivo: coluna texto comparada com número
Para demonstrar uma quebra de Seek determinístico que acontece independente de collation, versão ou sorte precisamos de um cenário em que a conversão recaia obrigatoriamente sobre a coluna. A forma mais limpa uma coluna de texto comparada com um parâmetro numérico.
Esse caso é assustadoramente comum no mundo real chaves de negócio numéricas armazenadas como VARCHAR (códigos de pedido, matrículas, números de documento) consultadas com parâmetros INT (Isso acontece muito em ambientes Protheus e RM).
DROP TABLE IF EXISTS dbo.Pedidos;
CREATE TABLE dbo.Pedidos
(
CodigoPedido VARCHAR(20) NOT NULL, -- número guardado como texto
Valor DECIMAL(10,2)
);
CREATE INDEX IX_Pedidos_Codigo
ON dbo.Pedidos (CodigoPedido);
WITH Numeros AS
(
SELECT TOP (500000)
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS Numero
FROM sys.all_objects a
CROSS JOIN sys.all_objects b
)
INSERT INTO dbo.Pedidos
SELECT CAST(Numero AS VARCHAR(20)), Numero * 1.5
FROM Numeros;
Consulta correta (parâmetro VARCHAR, igual à coluna):


Consulta problemática (parâmetro INT contra coluna VARCHAR):
DECLARE @Codigo INT = 200000
SELECT *
FROM dbo.Pedidos
WHERE CodigoPedido = @Codigo;


Aqui não há margem para interpretação. Como INT tem precedência sobre VARCHAR, o SQL Server é obrigado a converter a coluna CodigoPedido para INT linha a linha. No plano você verá a conversão aplicada diretamente sobre a coluna:
CONVERT_IMPLICIT(int, [Pedidos].[CodigoPedido], 0) = [@Codigo]
A condição deixa de ser sargable, o índice não pode mais ser percorrido por busca e o resultado é um Index Scan garantido em qualquer collation, em qualquer versão.
Compare os números: de 3 leituras lógicas para 1.547, e de 0 ms para 78 ms de CPU, numa tabela “pequena” de 500 mil linhas. Em tabelas com dezenas de milhões de registros, esse fator se multiplica e vira incidente de produção.
Como Identificar Conversões Implícitas
Você pode localizar planos no cache que contêm conversões implícitas com a consulta abaixo:
SELECT
qs.execution_count,
st.text
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp
WHERE CAST(qp.query_plan AS NVARCHAR(MAX))
LIKE '%CONVERT_IMPLICIT%';
Atenção em produção: essa consulta força a materialização do XML de todos os planos em cache e faz um LIKE sobre eles. Em servidores movimentados, com milhares de planos, ela pode consumir CPU e memória de forma relevante.
Execute em janelas de baixa carga e, idealmente, refine o filtro (por exemplo, juntando com sys.dm_exec_query_stats ordenado por total_worker_time para olhar só as consultas mais caras).
Lembre-se: como vimos, nem todo CONVERT_IMPLICIT é problema. Use essa lista como ponto de partida e confirme, no plano de cada consulta, se a conversão está caindo sobre a coluna.
E as versões mais novas resolvem isso?
Uma dúvida comum: será que algum SQL Server mais recente eliminou esse problema? A resposta honesta é não. O background é o mesmo desde sempre se a conversão recai sobre a coluna, o Seek vira Scan, do SQL Server 2008 ao 2025. O que evoluiu foi o ferramental ao redor:
- Aviso explícito no plano (desde o SQL Server 2012/2014): hoje o plano exibe “Type conversion in expression may affect CardinalityEstimate in query plan choice”, em vez de você precisar caçar o
CONVERT_IMPLICITno XML. Facilita o diagnóstico, mas não corrige nada. - Extended Event
plan_affecting_convert: captura justamente as conversões que de fato alteram o plano de execução — separando o problema real das conversões inofensivas (como o caso do literal que vimos no início). É a ferramenta ideal para confirmar suspeitas. - Cardinality Estimation (CE) Feedback (SQL Server 2022, família Intelligent Query Processing): aprende, ao longo de execuções repetidas, quando a estimativa de linhas de uma consulta está consistentemente errada e persiste um hint no Query Store para corrigir o plano nas próximas vezes.
Cuidado com a expectativa em torno do CE Feedback ele não corrige a conversão implícita. Ele ataca um sintoma adjacente (a estimativa de cardinalidade ruim que a conversão costuma provocar), não a causa. Além disso, depende do Query Store habilitado, só atua sobre consultas repetidas e não corrige em tempo de execução a primeira execução ruim continua ruim. Se a conversão está forçando um Scan sobre a coluna, o CE Feedback não vai transformá-lo em Seek.
Conclusão: em mais de uma década de evolução, o SQL Server melhorou bastante a capacidade de encontrar e atenuar o dano colateral da conversão implícita mas a cura continua sendo a mesma de sempre alinhar os tipos de dados. Nenhuma versão te isenta de prestar atenção no tipo de dados.
O Papel dos ORMs
Atualmente, boa parte dos problemas de conversão implícita nasce fora do SQL Server, na camada da aplicação. É comum encontrar:
- Coluna
VARCHARrecebendo parâmetroNVARCHAR. - Coluna
BIGINTrecebendo parâmetroINT. - Coluna
DATETIMErecebendo parâmetroDATETIME2. - Chaves numéricas sendo enviadas como strings.
O desenvolvedor nem sempre percebe, porque a consulta retorna os dados corretos. O efeito colateral é silencioso: o resultado vem certo, mas com um Scan caríssimo por trás.
Como Corrigir?
A boa notícia é que a correção costuma ser simples muitas vezes basta um ajuste de parâmetro.
– Corrija na camada da aplicação (preferível). É onde o problema nasce e onde a solução é mais limpa.
- Entity Framework: mapeie colunas
VARCHARcomo não-Unicode para que o EF gere parâmetrosVARCHAR:
Dapper: especifique o tipo do parâmetro explicitamente
var p = new DynamicParameters();
p.Add("@Codigo", "200000", DbType.AnsiString);
Chave numérica armazenada como texto (o caso do dbo.Pedidos): garanta que a aplicação envie o parâmetro como string, e não como inteiro. No EF, modele a propriedade como string; no Dapper, passe o valor já como texto. Isso evita que o INT force a conversão da coluna.
– Quando não dá para mudar a aplicação rapidamente, alinhe a definição da coluna ao tipo que a aplicação envia (avaliando impacto e espaço). Mudar VARCHAR para NVARCHAR, por exemplo, resolve a conversão ao custo de dobrar o armazenamento daquela coluna. Para chaves que são de fato numéricas, considere também corrigir o modelo de dados se o código é sempre um número, armazená-lo como INT/BIGINT resolve a conversão e ainda economiza espaço.
– Em último caso, uma conversão explícita no lado do parâmetro pode forçar o tipo correto, mas trate isso como paliativo a causa-raiz quase sempre está na aplicação.
SQL Server x PostgreSQL: filosofias diferentes
Vale um paralelo rápido para quem trabalha com os dois bancos. A forma como cada um lida com tipos incompatíveis revela filosofias opostas e cada uma traz seus próprios benefícios e desafios..
O SQL Server prioriza a compatibilidade: diante de uma comparação entre tipos diferentes, ele converte implicitamente para que a consulta simplesmente funcione. O lado bom é que nada quebra, o lado ruim é que essa conveniência pode degradar a performance silenciosamente exatamente o cenário deste artigo.
O PostgreSQL prioriza o rigor de tipos: muitas conversões entre famílias diferentes nem são implícitas, e ele tende a falhar explicitamente em vez de converter no escuro. Na prática, o problema é empurrado do runtime para o desenvolvimento em troca de mais disciplina de tipagem da equipe.
Essa diferença muda completamente como o mesmo descuido de ORM se manifesta em cada banco e o PostgreSQL tem seus próprios vilões de índice subutilizado. Mas isso é assunto para um próximo artigo, dedicado a como a conversão implícita (e seus equivalentes) se comportam no PostgreSQL.
Conclusão:
Conversões implícitas são um dos problemas de performance mais fáceis de ignorar e, ao mesmo tempo, um dos mais simples de corrigir. Antes de criar novos índices ou aumentar os recursos do servidor, vale verificar se os tipos de dados usados pela aplicação correspondem exatamente aos tipos definidos no banco.
Muitas vezes, corrigir um único parâmetro transforma um Index Scan em um Index Seek e reduz drasticamente o tempo de execução de uma consulta sem custo de hardware e sem reescrever a query.
Suspeita que sua aplicação esteja sofrendo com conversões implícitas e outros gargalos silenciosos? O Health Check do DataPlus identifica esses padrões e mostra exatamente onde sua performance está sendo desperdiçada. Fale com a gente.