非同期呼び出しを使用すると、SQLパフォーマンスに大きな問題が発生します。私は問題を実証するために小さなケースを作成しました。
LANにあるSQL Server 2016(localDBではない)にデータベースを作成しました。
そのデータベースには、WorkingCopy
2つの列を持つテーブルがあります。
Id (nvarchar(255, PK))
Value (nvarchar(max))
DDL
CREATE TABLE [dbo].[Workingcopy]
(
[Id] [nvarchar](255) NOT NULL,
[Value] [nvarchar](max) NULL,
CONSTRAINT [PK_Workingcopy]
PRIMARY KEY CLUSTERED ([Id] ASC)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
その表では、1つのレコードを挿入しています(id
= 'PerfUnitTest' Value
は1.5 MBの文字列(より大きなJSONデータセットのzip)です)。
ここで、SSMSでクエリを実行すると:
SELECT [Value]
FROM [Workingcopy]
WHERE id = 'perfunittest'
結果はすぐにわかり、SQL Servreプロファイラーで実行時間が約20ミリ秒であることがわかりました。すべて正常。
プレーンを使用して.NET(4.6)コードからクエリを実行する場合SqlConnection
:
// at this point, the connection is already open
var command = new SqlCommand($"SELECT Value FROM WorkingCopy WHERE Id = @Id", _connection);
command.Parameters.Add("@Id", SqlDbType.NVarChar, 255).Value = key;
string value = command.ExecuteScalar() as string;
これの実行時間も約20〜30ミリ秒です。
しかし、それを非同期コードに変更するとき:
string value = await command.ExecuteScalarAsync() as string;
実行時間はいきなり1800ms!また、SQL Serverプロファイラーでは、クエリの実行時間が1秒を超えていることがわかります。プロファイラーによって報告された実行済みクエリは、非同期ではないバージョンとまったく同じですが。
しかし、それはさらに悪化します。接続文字列のパケットサイズをいじると、次の結果が得られます。
パケットサイズ32768:[タイミング]:SqlValueStoreのExecuteScalarAsync->経過時間:450ミリ秒
パケットサイズ4096:[タイミング]:SqlValueStoreのExecuteScalarAsync->経過時間:3667ミリ秒
パケットサイズ512:[タイミング]:SqlValueStoreのExecuteScalarAsync->経過時間:30776ミリ秒
30,000ミリ秒 !! これは、非同期バージョンよりも1000倍以上遅いです。また、SQL Server Profilerは、クエリの実行に10秒以上かかったと報告しています。それでは、残りの20秒がどこに向かっているのかさえ説明されていません!
次に、同期バージョンに切り替えて、パケットサイズをいじってみました。実行時間には多少の影響はありましたが、非同期バージョンほど劇的ではありませんでした。
補足として、小さな文字列(100バイト未満)だけを値に入れると、非同期クエリの実行は同期バージョンと同じくらい高速になります(結果は1または2ミリ秒)。
特にSqlConnection
ORMでなく組み込みのを使用しているので、これには本当に困惑しています。また、周りを検索したところ、この動作を説明できるものは何も見つかりませんでした。何か案は?
GetSqlChars
か、GetSqlBinary
ストリーミング形式でそれらを取得するために。また、それらをFILESTREAMデータとして保存することも検討してください。1.5MBのデータをテーブルのデータページに保存する理由はありません