web-dev-qa-db-ja.com

.NETアプリケーションではSQLクエリは遅いが、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:ユーザーは複数のTrustAccountを持つことができます。
TrustAccountLog:すべてのTrustAccountの「動き」の監査が含まれます。 A
TrustAccountは、複数のTrustAccountLogエントリに関連付けられています。現在、このクエリはSQL Server Management Studio内でミリ秒単位で実行されますが、何らかの奇妙な理由により、C#アプリでは永遠に、場合によってはタイムアウト(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;
56
n4rzul

これがパラメータスニッフィングの場合、option(recompile)をクエリの最後に追加してみてください。ストアドプロシージャを作成して、より管理しやすい方法でロジックをカプセル化することをお勧めします。また、例で判断して、必要なパラメーターが3つだけの場合に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日ではなく、1月3日です。これをチェックしてください:

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
28
Piotr Rodak

私の経験では、クエリがSSMSでは高速であるが.NETからは低速になる通常の理由は、接続のSET- tingsの違いによるものです。 SSMSまたはSqlConnectionのいずれかによって接続が開かれると、一連のSETコマンドが自動的に発行され、実行環境がセットアップされます。残念ながら、SSMSとSqlConnectionは異なるSETデフォルトを持っています。

1つの一般的な違いはSET ARITHABORTです。 .NETコードから最初のコマンドとしてSET ARITHABORT ONを発行してみてください。

SQLプロファイラーを使用して、SSMSと.NETの両方によって発行されるSETコマンドを監視できるため、他の違いを見つけることができます。

次のコードは、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.
}
64
Daniel Renshaw

テスト環境でも同じ問題がありましたが、ライブシステム(同じSQLサーバー上)は正常に動作していました。 OPTION(RECOMPILE)とOPTION(OPTIMIZE FOR(@ p1 UNKNOWN))の追加は役に立ちませんでした。

SQLプロファイラーを使用して、.netクライアントが送信している正確なクエリをキャッチし、これが_exec sp_executesql N'select ..._でラップされており、パラメーターがnvarcharsとして宣言されていることを確認しました。

キャプチャされたクエリテキストをSSMSに挿入すると、.netクライアントからの実行と同じくらい遅いことが確認されました。

パラメーターのタイプをAnsiTextに変更すると、問題が解決することがわかりました。

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

テスト環境とライブ環境のパフォーマンスに大きな違いがある理由を説明することはできませんでした。

11
Daz

古い投稿なので、特定の問題が今までに解決されることを願っています。

次のSETオプションは、計画の再利用に影響を与える可能性があります(最後の完全なリスト)

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

次の2つのステートメントは msdn-SET ARITHABORT からのものです。

ARITHABORTをOFFに設定すると、クエリの最適化に悪影響を及ぼし、パフォーマンスの問題が発生する可能性があります。

SQL Server Management StudioのデフォルトのARITHABORT設定はONです。 ARITHABORTをOFFに設定するクライアントアプリケーションは、異なるクエリプランを受信する可能性があり、パフォーマンスの低いクエリのトラブルシューティングを困難にします。つまり、同じクエリは管理スタジオでは高速に実行できますが、アプリケーションでは低速になります。

理解すべきもう1つの興味深いトピックはParameter Sniffingアプリケーションで遅い、SSMSで速い?パフォーマンスの謎を理解する-Erland Sommarskogによる で概説されているとおり

さらに別の可能性は、 varchar列でのSQLインデックスパフォーマンスのトラブルシューティング-by Jimmy Bogard

不明の最適化

SQL Server 2008以降では、OPTIMIZE FOR UNKNOWNを検討してください。 UNKNOWN:クエリオプティマイザーが初期値の代わりに統計データを使用して、クエリの最適化中にローカル変数の値を決定することを指定します。

オプション(再コンパイル)

再コンパイルが唯一の解決策である場合、「WITH RECOMPILE」の代わりに「OPTION(RECOMPILE)」を使用します。パラメーターの埋め込みの最適化に役立ちます。読み取り パラメータスニッフィング、埋め込み、およびRECOMPILEオプション-ポールホワイトによる

SETオプション

次のSETオプションは、 msdn-SQL Server 2008のプランキャッシュ に基づいて、プランの再利用に影響を与える可能性があります

  1. 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. LANGUAGE 12. NO_BROWSETABLE 13. NUMERIC_ROUNDABOTED 14。
7
Lijo

ほとんどの場合、問題は基準にあります

_tal.TrustAccountLogDate < @TrustAccountLogDate2
_

最適な実行プランは、パラメータの値に大きく依存します。1910-01-01(行を返さない)を渡すと、2100-12-31(すべての行を返す)とは異なるプランが最も確実に発生します。

値がクエリでリテラルとして指定されている場合、SQLサーバーはプラン生成中に使用する値を認識します。パラメーターを使用すると、SQLサーバーは計画を1回だけ生成して再利用します。その後の実行での値が元の値と大きく異なる場合、計画は最適化されません。

状況を改善するには、クエリでOPTION(RECOMPILE)を指定できます。ストアドプロシージャにクエリを追加しても、WITH RECOMPILEプロシージャを作成しない限り、this specificの問題は解決しません。

他の人はすでにこれについて言及しています(「パラメータースニッフィング」)が、この概念の簡単な説明は害にならないだろうと思いました。

6
erikkallen

タイプ変換の問題である可能性があります。すべてのIDは本当にSqlDbType.Intデータ層で?

また、なぜ2つが4つのパラメーターを持っているのですか?

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つの異なる変数をop。to。4定数として予期しているため、サーバーが異なる計画を作成する原因になる可能性があります。

3
Hogan

1つの列から1つの行の値のみを返すように見えるので、代わりにコマンドオブジェクトで ExecuteScalar() を使用できます。これはより効率的です。

    object value = cmd.ExecuteScalar();

    if (value == null)
        return 0;
    else
        return (double)value;
2
Dan Diplo

私の場合、問題は、Entity Frameworkが_exec sp_executesql_を使用するクエリを生成していたことです。

パラメーターが型で完全に一致しない場合、実行計画は変換をクエリ自体に入れることを決定するため、インデックスを使用しません。ご想像のとおり、これによりパフォーマンスが大幅に低下します。

私の場合、列はCHR(3)として定義され、Entity Frameworkはncharからcharへの変換を引き起こすクエリでN'str 'を渡していました。したがって、次のようなクエリの場合:

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

次のようなSQLクエリを生成していました。

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

私の場合の最も簡単な解決策は、列のタイプを変更することでした。代わりに、最初に正しいタイプを渡すようにコードと格闘することもできます。

2
Eyal

パラメータスニッフィングに関連している可能性がありますか?クライアントコードがSQL Serverに送信するものを正確にキャプチャ(プロファイラーを使用して正確なステートメントをキャッチ)し、それをManagement Studioで実行しようとしましたか?

パラメータスニッフィング: SQLのストアドプロシージャ実行プランのパフォーマンスが低い-パラメータスニッフィング

これをコードで見たことがなく、プロシージャでのみ見ましたが、一見の価値があります。

1
Meff

私は今日この問題を抱えており、これが私の問題を解決します: https://www.mssqltips.com/sqlservertip/4318/sql-server-stored-procedure-runs-fast-in-ssms-and-slow-in -application /

SP this:ARITHABORT ONを設定します

Holpこれはあなたを助けます!

1
Italo Reis

データリーダーを閉じているようには見えません。これは、何度も繰り返されていく可能性があります...

0
Paddy

私は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
0
GlacialSpoon

この質問の症状のタイトルと完全に一致する別の根本原因に問題がありました。

私の場合、問題は、結果セットがアプリケーションの.NETコードによって開かれたままになっていることでした返されたすべてのレコードをループし、データベースに対してさらに3つのクエリを実行しました!これにより、数千行を超えると、SQL Serverからのタイミング情報に基づいて、元のクエリが完了に時間がかかったように誤解を招きました。

そのため、修正は、呼び出しを行う.NETコードをリファクタリングして、各行の処理中に結果セットを開いたままにしないようにすることでした。

0
Tim Abell