Подтвердить что ты не робот

SQL Query медленнее в приложении .NET, но мгновенно в SQL Server Management Studio

Вот SQL

SELECT tal.TrustAccountValue
FROM TrustAccountLog AS tal
INNER JOIN TrustAccount ta ON ta.TrustAccountID = tal.TrustAccountID
INNER JOIN Users usr ON usr.UserID = ta.UserID
WHERE usr.UserID = 70402 AND
ta.TrustAccountID = 117249 AND
tal.trustaccountlogid =  
(
 SELECT MAX (tal.trustaccountlogid)
 FROM  TrustAccountLog AS tal
 INNER JOIN TrustAccount ta ON ta.TrustAccountID = tal.TrustAccountID
 INNER JOIN Users usr ON usr.UserID = ta.UserID
 WHERE usr.UserID = 70402 AND
 ta.TrustAccountID = 117249 AND
 tal.TrustAccountLogDate < '3/1/2010 12:00:00 AM'
)

В основном таблица Users содержит таблицу TrustAccount и таблицу TrustAccountLog.
Пользователи: Содержит пользователей и их данные
TrustAccount: Пользователь может иметь несколько TrustAccounts.
TrustAccountLog: содержит проверку всех "движений" TrustAccount. A TrustAccount связан с несколькими записями TrustAccountLog. Теперь этот запрос выполняется в миллисекундах внутри SQL Server Management Studio, но по какой-то странной причине он всегда наступает в моем приложении С# и даже в расписании (120 секунд).

Вот код в двух словах. Он вызывается несколько раз в цикле и оператор готов к работе.

cmd.CommandTimeout = Configuration.DBTimeout;
cmd.CommandText = "SELECT tal.TrustAccountValue FROM TrustAccountLog AS tal INNER JOIN TrustAccount ta ON ta.TrustAccountID = tal.TrustAccountID INNER JOIN Users usr ON usr.UserID = ta.UserID WHERE usr.UserID = @UserID1 AND ta.TrustAccountID = @TrustAccountID1 AND tal.trustaccountlogid =  (SELECT MAX (tal.trustaccountlogid) FROM  TrustAccountLog AS tal INNER JOIN TrustAccount ta ON ta.TrustAccountID = tal.TrustAccountID INNER JOIN Users usr ON usr.UserID = ta.UserID WHERE usr.UserID = @UserID2 AND ta.TrustAccountID = @TrustAccountID2 AND tal.TrustAccountLogDate < @TrustAccountLogDate2 ))";
cmd.Parameters.Add("@TrustAccountID1", SqlDbType.Int).Value = trustAccountId;
cmd.Parameters.Add("@UserID1", SqlDbType.Int).Value = userId;
cmd.Parameters.Add("@TrustAccountID2", SqlDbType.Int).Value = trustAccountId;
cmd.Parameters.Add("@UserID2", SqlDbType.Int).Value = userId;
cmd.Parameters.Add("@TrustAccountLogDate2", SqlDbType.DateTime).Value =TrustAccountLogDate;

// And then...

reader = cmd.ExecuteReader();
if (reader.Read())
{
   double value = (double)reader.GetValue(0);
   if (System.Double.IsNaN(value))
      return 0;
   else
      return value;
}
else
   return 0;
4b9b3361

Ответ 1

Если это параметр sniffing, попробуйте добавить option(recompile) в конец вашего запроса. Я бы рекомендовал создать хранимую процедуру для инкапсуляции логики более управляемым образом. Также согласовано - почему вы передаете 5 параметров, если вам нужно всего три, судя по примеру? Вы можете использовать этот запрос?

select TrustAccountValue from
(
 SELECT MAX (tal.trustaccountlogid), tal.TrustAccountValue
 FROM  TrustAccountLog AS tal
 INNER JOIN TrustAccount ta ON ta.TrustAccountID = tal.TrustAccountID
 INNER JOIN Users usr ON usr.UserID = ta.UserID
 WHERE usr.UserID = 70402 AND
 ta.TrustAccountID = 117249 AND
 tal.TrustAccountLogDate < '3/1/2010 12:00:00 AM'
 group by tal.TrustAccountValue
) q

И для чего вы стоите, вы используете двусмысленный формат даты, в зависимости от языковых настроек пользователя, выполняющего запрос. Для меня, например, это 3 января, а не 1 марта. Проверьте это:

set language us_english
go
select @@language --us_english
select convert(datetime, '3/1/2010 12:00:00 AM')
go
set language british
go
select @@language --british
select convert(datetime, '3/1/2010 12:00:00 AM')

Рекомендуемый подход - использовать формат "ISO" yyyymmdd hh: mm: ss

select convert(datetime, '20100301 00:00:00') --midnight 00, noon 12

Ответ 2

По моему опыту, обычная причина, по которой запрос выполняется быстро в SSMS, но медленнее от .NET, объясняется различиями в соединении SET -tings. Когда соединение открывается SSMS или SqlConnection, для настройки среды выполнения автоматически выдается группа команд SET. К сожалению, SSMS и SqlConnection имеют разные значения SET по умолчанию.

Одно общее отличие - SET ARITHABORT. Попробуйте выполнить SET ARITHABORT ON как первую команду из вашего .NET-кода.

SQL Profiler может использоваться для контроля, какие команды SET выдаются как SSMS, так и .NET, поэтому вы можете найти другие отличия.

Следующий код демонстрирует, как выпустить команду SET, но обратите внимание, что этот код не был протестирован.

using (SqlConnection conn = new SqlConnection("<CONNECTION_STRING>")) {
    conn.Open();

    using (SqlCommand comm = new SqlCommand("SET ARITHABORT ON", conn)) {
        comm.ExecuteNonQuery();
    }

    // Do your own stuff here but you must use the same connection object
    // The SET command applies to the connection. Any other connections will not
    // be affected, nor will any new connections opened. If you want this applied
    // to every connection, you must do it every time one is opened.
}

Ответ 3

Имела ту же самую проблему в тестовой среде, хотя работающая система (на том же SQL-сервере) работала нормально. Добавление OPTION (RECOMPILE), а также OPTION (OPTIMIZE FOR (@p1 UNKNOWN)) не помогло.

Я использовал SQL Profiler для поиска точного запроса, который был отправлен клиентом .net, и обнаружил, что он был обернут exec sp_executesql N'select ... и что параметры были объявлены как nvarchars - сравниваемые столбцы являются простыми varchars.

Помещение захваченного текста запроса в SSMS подтвердило, что он работает так же медленно, как и от клиента .net.

Я обнаружил, что изменение типа параметров в AnsiText устраняет проблему:

p = cm.CreateParameter() p.ParameterName = "@company" p.Value = company p.DbType = DbType.AnsiString cm.Parameters.Add(p)

Я никогда не мог объяснить, почему тестовые и живые среды имели такую ​​заметную разницу в производительности.

Ответ 4

Скорее всего, проблема кроется в критерии

tal.TrustAccountLogDate < @TrustAccountLogDate2

Оптимальный план выполнения будет сильно зависеть от значения параметра, передавая 1910-01-01 (который не возвращает строк) наверняка вызовет другой план, чем 2100-12-31 (который возвращает все строки).

Когда значение задается в качестве литерала в запросе, сервер SQL знает, какое значение использовать во время генерации плана. Когда используется параметр, SQL-сервер будет генерировать план только один раз, а затем повторно использовать его, и если значение в последующем выполнении слишком сильно отличается от исходного, план не будет оптимальным.

Чтобы исправить ситуацию, вы можете указать OPTION(RECOMPILE) в запросе. Добавление запроса в хранимую процедуру не поможет вам в этой конкретной проблеме, если только  вы создаете процедуру WITH RECOMPILE.

Другие уже упомянули об этом ( "параметр sniffing" ), но я думал, что простое объяснение концепции не повредит.

Ответ 5

Это могут быть проблемы преобразования типов. Все ли идентификаторы действительно SqlDbType.Int на уровне данных?

Кроме того, почему есть 4 параметра, где 2 будет делать?

cmd.Parameters.Add("@TrustAccountID1", SqlDbType.Int).Value = trustAccountId;
cmd.Parameters.Add("@UserID1", SqlDbType.Int).Value = userId;
cmd.Parameters.Add("@TrustAccountID2", SqlDbType.Int).Value = trustAccountId;
cmd.Parameters.Add("@UserID2", SqlDbType.Int).Value = userId;

Может быть

cmd.Parameters.Add("@TrustAccountID", SqlDbType.Int).Value = trustAccountId;
cmd.Parameters.Add("@UserID", SqlDbType.Int).Value = userId;

Так как им присваивается одна и та же переменная.

(Это может привести к тому, что сервер сделает другой план, поскольку он ожидает, что четыре разные переменные будут константами 4: 4, что делает его 2 переменными, которые могут повлиять на оптимизацию сервера.)

Ответ 6

Надеюсь, ваша конкретная проблема будет решена к настоящему времени, поскольку это старая должность.

Следующие параметры SET могут повлиять на повторное использование плана (полный список в конце)

SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO
SET ARITHABORT ON
GO

Следующие два утверждения относятся к msdn - SET ARITHABORT

Настройка ARITHABORT на OFF может негативно повлиять на оптимизацию запросов, приводящую к проблемам с производительностью.

Параметр ARITHABORT по умолчанию для SQL Server Management Studio включен. Настройка клиентских приложений ARITHABORT на OFF может принимать разные планы запросов, что затрудняет устранение неполадок, связанных с плохой работой. То есть, тот же запрос может быстро выполняться в студии управления, но медленнее в приложении.

Еще одна интересная тема для понимания - Parameter Sniffing, как указано в Медленное приложение, быстро в SSMS? Понимание спектаклей "Тайны" - Эрланд Соммаргог

Еще одна возможность заключается в преобразовании (внутри) столбцов VARCHAR в NVARCHAR при использовании входного параметра Unicode, как указано в Устранение ошибок индекса SQL в столбцах varchar - от Jimmy Богард

ОПТИМИЗАЦИЯ ДЛЯ НЕИЗВЕСТНОГО

В SQL Server 2008 и выше рассмотрите OPTIMIZE FOR UNKNOWN. UNKNOWN: указывает, что оптимизатор запросов использует статистические данные вместо начального значения для определения значения для локальной переменной во время оптимизации запроса.

OPTION (RECOMPILE)

Используйте "OPTION (RECOMPILE)" вместо "WITH RECOMPILE", если рекомпиляция является единственным решением. Это помогает в оптимизации внедрения параметров. Прочитайте Параметр Sniffing, Embedding и опции RECOMPILE - Paul White

Параметры SET

Следующие параметры SET могут повлиять на повторное использование плана, основанное на msdn - Планирование кэширования в SQL Server 2008

  • ANSI_NULL_DFLT_OFF 2. ANSI_NULL_DFLT_ON 3. ANSI_NULLS 4. ANSI_PADDING 5. ANSI_WARNINGS 6. ARITHABORT 7. CONCAT_NULL_YIELDS_NUL 8. DATEFIRST 9. DATEFORMAT 10. FORCEPLAN 11. ЯЗЫК 12. NO_BROWSETABLE 13. NUMERIC_ROUNDABORT 14. QUOTED_IDENTIFIER

Ответ 7

Поскольку вы, кажется, когда-либо возвращаете значение из одной строки из одного столбца, вы можете использовать ExecuteScalar() в объекте команды вместо этого, что должно быть более эффективным:

    object value = cmd.ExecuteScalar();

    if (value == null)
        return 0;
    else
        return (double)value;

Ответ 8

В моем случае проблема заключалась в том, что моя Entity Framework генерировала запросы, которые используют exec sp_executesql.

Когда параметры не соответствуют точному типу, в плане выполнения не используются индексы, потому что он решает перевести преобразование в сам запрос. Как вы можете себе представить, это приводит к гораздо более низкой производительности.

в моем случае столбец был определен как CHR (3), а Entity Framework пропускал N'str 'в запросе, который вызывает преобразование из nchar в char. Итак, для запроса, который выглядит так:

ctx.Events.Where(e => e.Status == "Snt")

Он генерировал SQL-запрос, который выглядит примерно так:

FROM [ExtEvents] AS [Extent1] ... WHERE (N''Snt'' = [Extent1].[Status]) ...

Самое простое решение в моем случае состояло в том, чтобы изменить тип столбца, иначе вы можете бороться с вашим кодом, чтобы он сначала передавал правильный тип.

Ответ 9

Звуки, возможно, связаны с обнюхом параметров? Попробовали ли вы выполнить то, что клиентский код отправляет на SQL Server (использовать профилировщик, чтобы поймать точный оператор), а затем запустить это в Management Studio?

Параметр sniffing: Плохая производительность плана выполнения хранимых процедур SQL - параметр sniffing

Я раньше этого не видел в коде, только в процедурах, но это стоит посмотреть.

Ответ 10

Вы, кажется, не закрываете свой считыватель данных - это может начать складываться по нескольким итерациям...

Ответ 11

У меня возникла проблема с другой основной причиной, которая точно соответствовала названию этих проблем.

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

Исправление было поэтому реорганизацией кода .NET, вызывающего вызовы, чтобы он не оставил открытый набор результатов при обработке каждой строки.

Ответ 12

Я понимаю, что в OP не упоминается использование хранимых процедур, но есть альтернативное решение проблем с параметрами нюхания при использовании хранимых процедур, которые менее изящны, но работали для меня, когда OPTION(RECOMPILE), кажется, ничего не делает.

Просто скопируйте свои параметры в переменные, объявленные в процедуре, и используйте их вместо.

Пример:

ALTER PROCEDURE [ExampleProcedure]
@StartDate DATETIME,
@EndDate DATETIME
AS
BEGIN

--reassign to local variables to avoid parameter sniffing issues
DECLARE @MyStartDate datetime,
        @MyEndDate datetime

SELECT 
    @MyStartDate = @StartDate,
    @MyEndDate = @EndDate

--Rest of procedure goes here but refer to @MyStartDate and @MyEndDate
END

Ответ 13

Я предлагаю вам попробовать и создать хранимую процедуру, которая может быть скомпилирована и кэширована сервером Sql и, таким образом, повысить производительность