Сгенерированный LinqtoSql Sql в 5 раз медленнее, чем ЖЕ ТОЧНО рукописный sql - PullRequest
1 голос
/ 13 мая 2010

У меня есть SQL-оператор, который жестко задан в существующем приложении VB6. Я обновляю новую версию в C # и использую Linq To Sql. Мне удалось заставить LinqToSql сгенерировать тот же sql (до того, как я начну рефакторинг), но по какой-то причине Sql, сгенерированный LinqToSql, в 5 раз медленнее , чем исходный sql. Это запускает сгенерированный Sql Directly в LinqPad.

Единственная реальная разница, которую могут заметить мои скудные глаза - это WITH (NOLOCK) , что, если я добавлю в sql, сгенерированный LinqToSql, не имеет значения.

Может кто-нибудь указать, что я здесь не так делаю? Спасибо!

Существующий жесткий код Sql (5,0 секунд)

SELECT DISTINCT 
CH.ClaimNum, CH.AcnProvID, CH.AcnPatID, CH.TinNum, CH.Diag1, CH.GroupNum, CH.AllowedTotal  
FROM Claims.dbo.T_ClaimsHeader AS CH WITH (NOLOCK) 
WHERE 
CH.ContractID IN ('123A','123B','123C','123D','123E','123F','123G','123H') 
AND ( ( (CH.Transmited Is Null or CH.Transmited = '') 
AND CH.DateTransmit Is Null 
AND CH.EobDate Is Null 
AND CH.ProcessFlag IN ('Y','E') 
AND CH.DataSource NOT IN ('A','EC','EU') 
AND CH.AllowedTotal > 0 ) ) 
ORDER BY CH.AcnPatID, CH.ClaimNum

Сгенерированный Sql из LinqToSql (27,6 секунд)

-- Region Parameters
DECLARE @p0 NVarChar(4) SET @p0 = '123A'
DECLARE @p1 NVarChar(4) SET @p1 = '123B'
DECLARE @p2 NVarChar(4) SET @p2 = '123C'
DECLARE @p3 NVarChar(4) SET @p3 = '123D'
DECLARE @p4 NVarChar(4) SET @p4 = '123E'
DECLARE @p5 NVarChar(4) SET @p5 = '123F'
DECLARE @p6 NVarChar(4) SET @p6 = '123G'
DECLARE @p7 NVarChar(4) SET @p7 = '123H'
DECLARE @p8 VarChar(1) SET @p8 = ''
DECLARE @p9 NVarChar(1) SET @p9 = 'Y'
DECLARE @p10 NVarChar(1) SET @p10 = 'E'
DECLARE @p11 NVarChar(1) SET @p11 = 'A'
DECLARE @p12 NVarChar(2) SET @p12 = 'EC'
DECLARE @p13 NVarChar(2) SET @p13 = 'EU'
DECLARE @p14 Decimal(5,4) SET @p14 = 0
-- EndRegion
SELECT DISTINCT 
[t0].[ClaimNum], 
[t0].[acnprovid] AS [AcnProvID], 
[t0].[acnpatid] AS [AcnPatID], 
[t0].[tinnum] AS [TinNum], 
[t0].[diag1] AS [Diag1], 
[t0].[GroupNum], 
[t0].[allowedtotal] AS [AllowedTotal]
FROM [Claims].[dbo].[T_ClaimsHeader] AS [t0]
WHERE 
([t0].[contractid] IN (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7)) 
AND (([t0].[Transmited] IS NULL) OR ([t0].[Transmited] = @p8)) 
AND ([t0].[DATETRANSMIT] IS NULL) 
AND ([t0].[EOBDATE] IS NULL) 
AND ([t0].[PROCESSFLAG] IN (@p9, @p10)) 
AND (NOT ([t0].[DataSource] IN (@p11, @p12, @p13))) 
AND ([t0].[allowedtotal] > @p14)
ORDER BY [t0].[acnpatid], [t0].[ClaimNum]

Новый код LinqToSql (30+ секунд ... Время ожидания)

var contractIds = T_ContractDatas.Where(x => x.EdiSubmissionGroupID == "123-01").Select(x => x.CONTRACTID).ToList();
var processFlags = new List<string> {"Y","E"};
var dataSource = new List<string> {"A","EC","EU"};

var results = (from claims in T_ClaimsHeaders
where contractIds.Contains(claims.contractid)
&& (claims.Transmited == null || claims.Transmited == string.Empty )
&& claims.DATETRANSMIT == null
&& claims.EOBDATE == null
&& processFlags.Contains(claims.PROCESSFLAG)
&& !dataSource.Contains(claims.DataSource)
&& claims.allowedtotal > 0

select new
 {
     ClaimNum = claims.ClaimNum,
     AcnProvID = claims.acnprovid,
     AcnPatID = claims.acnpatid,
     TinNum = claims.tinnum,
     Diag1 = claims.diag1,
     GroupNum = claims.GroupNum,
     AllowedTotal = claims.allowedtotal
 }).OrderBy(x => x.ClaimNum).OrderBy(x => x.AcnPatID).Distinct();

Я использую приведенный выше список констант, чтобы сделать LinqToSql Generate IN ('xxx', 'xxx' и т. Д.) В противном случае он использует подзапросы, которые являются такими же медленными ...

Ответы [ 4 ]

4 голосов
/ 13 мая 2010

Сравните планы выполнения для двух очередей. Запрос linqtosql использует множество параметров, оптимизатор запросов создаст план выполнения на основе того, что МОЖЕТ быть в параметрах, жестко запрограммированный SQL имеет литеральные значения, оптимизатор запросов построит план выполнения на основе фактических значений. Вероятно, он производит гораздо более эффективный план для буквальных значений. Лучше всего попытаться найти медленные биты в плане выполнения и попытаться получить linq2sql для получения лучшего запроса. Если вы не можете, но думаете, что можете создать его вручную, то создайте SP, который затем вы сможете использовать в качестве метода в своем классе контекста данных в linqtosql.

2 голосов
/ 13 мая 2010

Жестко закодированные значения в первом SQL могут позволять оптимизатору запросов использовать индексы, которые он не знает, что он может эффективно использовать для второго параметризованного SQL.

Другая возможность состоит в том, что если вы запускаете вручную SQL в среде SQL Server Management Studio, другие настройки по умолчанию для SSMS по сравнению с поставщиком .NET SQL Server могут влиять на производительность. В этом случае может помочь изменение некоторых настроек SET .NET перед выполнением команды (например, SET ARITHABORT ON), но я не знаю, сможете ли вы сделать это в LinqPad. См. здесь для получения дополнительной информации об этой возможности.

1 голос
/ 13 мая 2010

Большая разница в параметрах.

Я не могу знать наверняка, не анализируя планы, но L2S параметризует запросы, чтобы их планы могли эффективно использоваться повторно, избегая чрезмерной перекомпиляции запросов на сервере. В общем, это хорошая вещь, потому что он поддерживает низкое время процессора на SQL Server - ему не нужно постоянно генерировать, генерировать и генерировать один и тот же план.

Но L2S немного перегружен, когда вы используете константы. Он также параметризует их, что может отрицательно сказаться на производительности в определенных ситуациях.

Надевая свою шляпу для ясновидения из алюминиевой фольги, я представляю, какие структуры индексов могут быть у вас за этим столом. Например, у вас может быть индекс только для ProcessFlag, и может быть очень мало значений для «Y» и «E» для ProcessFlag, в результате чего запрос с жестко запрограммированными константами выполняет сканирование только тех значений, где ProcessFlag = «Y» и «E». Для параметризованного запроса SQL Server создает план, который считается оптимальным для произвольного ввода . Это означает, что сервер не может воспользоваться этим небольшим указанием (константами), которое вы ему даете.

В данный момент я советую вам внимательно изучить ваши индексы и отдать предпочтение составным индексам, которые в совокупности охватывают больше ваших условий WHERE. Держу пари, что с небольшим количеством анализа такого типа вы обнаружите, что производительность запросов становится намного более похожей. (и, вероятно, улучшается в обоих случаях!)

0 голосов
/ 15 мая 2010

Вы также можете проверить наши скомпилированные запросы LINQ - http://www.jdconley.com/blog/archive/2007/11/28/linq-to-sql-surprise-performance-hit.aspx

...