O cerne da questão não é "por que o pedido importa com o LINQ?". LINQ apenas traduz literalmente sem reordenar. A verdadeira questão é "por que as duas consultas SQL têm desempenho diferente?".
Consegui reproduzir o problema inserindo apenas 100 mil linhas. Nesse caso, uma fraqueza no otimizador está sendo acionada:ele não reconhece que pode fazer uma busca em
Colour
devido à condição complexa. Na primeira consulta, o otimizador reconhece o padrão e cria uma busca de índice. Não há nenhuma razão semântica para que isso aconteça. Uma busca em um índice é possível mesmo quando busca em
NULL
. Esta é uma fraqueza/bug no otimizador. Aqui estão os dois planos:O EF tenta ser útil aqui porque assume que tanto a coluna quanto a variável de filtro podem ser nulas. Nesse caso, ele tenta fornecer uma correspondência (o que, de acordo com a semântica do C#, é a coisa certa).
Eu tentei desfazer isso adicionando o seguinte filtro:
Colour IS NOT NULL AND @p__linq__0 IS NOT NULL
AND Size IS NOT NULL AND @p__linq__1 IS NOT NULL
Esperando que o otimizador agora use esse conhecimento para simplificar a complexa expressão do filtro EF. Não conseguiu fazê-lo. Se isso tivesse funcionado, o mesmo filtro poderia ter sido adicionado à consulta do EF, fornecendo uma correção fácil.
Aqui estão as correções que eu recomendo na ordem em que você deve experimentá-las:
- Torne as colunas do banco de dados não nulas no banco de dados
- Torne as colunas não nulas no modelo de dados do EF esperando que isso impeça o EF de criar a condição de filtro complexa
- Criar índices:
Colour, Size
e/ouSize, Colour
. Eles também removem o problema. - Certifique-se de que a filtragem seja feita na ordem correta e deixe um comentário de código
- Tente usar
INTERSECT
/Queryable.Intersect
para combinar os filtros. Isso geralmente resulta em diferentes formas de plano. - Crie uma função com valor de tabela embutida que faça a filtragem. O EF pode usar essa função como parte de uma consulta maior
- Desça para SQL bruto
- Use um guia de plano para alterar o plano
Todas essas são soluções alternativas, não correções de causa raiz.
No final, não estou feliz com o SQL Server e o EF aqui. Ambos os produtos devem ser corrigidos. Infelizmente, eles provavelmente não serão e você também não pode esperar por isso.
Aqui estão os scripts de índice:
CREATE NONCLUSTERED INDEX IX_Widget_Colour_Size ON dbo.Widget
(
Colour, Size
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
CREATE NONCLUSTERED INDEX IX_Widget_Size_Colour ON dbo.Widget
(
Size, Colour
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]