私は奇妙な問題を実行しました。多分ここの誰かが私を助けることができます。
私のSQLServerのバージョンは2008R2です。24コアのサーバーで実行されます。
2つのクエリ文字列があります:
String SQL_1 = "select
t.testconfig_id, t.minuteSequence, t.location_id,
sum(t.vuPerNode) as totalVu,
sum(t.backOffPctSum) / sum(t.recordNum) as avgBackOffPct
from
(select
p.testconfig_id, p.minuteSequence, r.location_Id,
SUM(p.activeCount) * 1.0 / COUNT(1) as vuPerNode,
SUM(p.backOffPct) as backOffPctSum, COUNT(1) as recordNum
from
loadtest_progress_in_minute p (nolock)
join
loadtestRunrecord r (nolock) on p.test_id = r.test_id and p.nodeId = r.nodeId
where
p.test_id = ?
group by
p.testconfig_id, p.minuteSequence, p.nodeId, r.location_id) t
group by
t.testconfig_id, t.minuteSequence, t.location_id
order by
t.testconfig_id, t.minuteSequence, t.location_id option (maxdop 23)"
String SQL-2 = "select
t.testconfig_id, t.minuteSequence, t.location_id,
sum(t.vuPerNode) as totalVu,
sum(t.backOffPctSum) / sum(t.recordNum) as avgBackOffPct
from
(select
p.testconfig_id, p.minuteSequence, r.location_Id,
SUM(p.activeCount) * 1.0 / COUNT(1) as vuPerNode,
SUM(p.backOffPct) as backOffPctSum, COUNT(1) as recordNum
from
loadtest_progress_in_minute p (nolock)
join
loadtestRunrecord r (nolock) on p.test_id = r.test_id and p.nodeId = r.nodeId
where
p.test_id = ?
group by
p.testconfig_id, p.minuteSequence, p.nodeId, r.location_id) t
group by
t.testconfig_id, t.minuteSequence, t.location_id
order by
t.testconfig_id, t.minuteSequence, t.location_id option (maxdop 23) "
2つのクエリの唯一の違いは、SQL-2の最後に1つの余分なタブ文字があることです。
同じ環境で次のコードを使用して、これら2つのクエリを実行します。
PreparedStatemen ps = conn.prepareStatement(SQL_1);
//PreparedStatemen ps = conn.prepareStatement(SQL_2);
ps.setLong(1234);
ps.execute();
コードスニペットでは、2つのクエリのパフォーマンスが大きく異なる場合があります。
ps.excute()
このコードスニペットのSQL_1のコストは約10秒です。SQL_1の実行中はCPU使用率が高いようです。サーバーの24CPUがすべて使用されています。
ただしps.excute()
、同じコードスニペットのSQL_2のコストは約150秒です。SQL_2の実行中は、CPU使用率が遅くなります。24個のCPUのうち2個だけが使用されます。
SQL_1とSQL_2は同時に実行されています。
しかし、上記の観察は必ずしもそうではありません。SQL_1ps.execute()
とSQL_2のパフォーマンスが同じ場合があります。SQL_1ps.execute()
とSQL_2のパフォーマンスは、上記のとおりです。
それが私が見つけたものです。とても紛らわしいです。SQLの最後の空白は、SQL Serverのパフォーマンスに悪影響を及ぼしますか?
SQL Server 2008 R2のSpaceで説明されているように、自動パラメータ化キャッシュが原因ではないと思います。 パフォーマンスが低下します。
コードスニペットを無限ループで長時間呼び出すことで、上記の観察結果が得られたためです。
Wiresharkから、Microsoft JDBCがSQL文字列とパラメータの間に次のように2つの余分なタブ(1タブ文字= 4スペース文字)文字を追加することがわかりました。
[20] [00] [20] [00] [20] [00] [20] [00] [20] [00] [20] [00][20] [00][20] [00]
それが私の問題に関係しているかどうかはわかりません。
ありがとう。
更新2012-8-20:
sql-servermanagementstudioで次の3つのSQLを実行しました。それらは同じ実行計画を持っています。しかし、私には奇妙な発見があります:
declare @sql varchar(max);
set @sql='Declare @testId bigint;set @testId = 1234;select p.testconfig_id, p.minuteSequence,r.location_Id, SUM(p.activeCount) * 1.0 / COUNT(1) as vuPerNode, SUM(p.backOffPct) as backOffPctSum, COUNT(1) as recordNum from loadtest_progress_in_minute p with( nolock,index(idx_loadtest_progress_in_minute_1) ) join loadtestRunrecord r ( nolock ) on p.test_id = r.test_id and p.nodeId = r.nodeId where p.test_id = @testId group by p.testconfig_id, p.minuteSequence, p.nodeId, r.location_id option (maxdop 23)';
execute (@sql);
このSQLのパフォーマンスは非常に悪いです。約90秒かかります。2つのCPUのみが使用されます。
Declare @sSQL nvarchar(2000);
Declare @paramDefine nvarchar(2000);
Declare @testId bigint;
set @testId = 1234;
set @sSQL = N' select t.testconfig_id, t.minuteSequence, t.location_id, sum(t.vuPerNode) as totalVu, sum(t.backOffPctSum) / sum(t.recordNum) as avgBackOffPct from ( select p.testconfig_id, p.minuteSequence, r.location_Id, SUM(p.activeCount) * 1.0 / COUNT(1) as vuPerNode, SUM(p.backOffPct) as backOffPctSum, COUNT(1) as recordNum from loadtest_progress_in_minute p ( nolock ) join loadtestRunrecord r ( nolock ) on p.test_id = r.test_id and p.nodeId = r.nodeId where p.test_id = @P0 group by p.testconfig_id, p.minuteSequence, p.nodeId, r.location_id ) t group by t.testconfig_id, t.minuteSequence, t.location_id order by t.testconfig_id, t.minuteSequence, t.location_id option (maxdop 23) ';
set @paramDefine = N'@P0 bigint';
execute sp_executesql @sSQL, @paramDefine, @P0 = @testId;
このSQLは、ManagementStudioで約10秒しかかかりません。すべてのCPUが使用されます。
declare @p1 int
--set @p1=1
exec sp_prepexec @p1 output,N'@P0 bigint',N' select t.testconfig_id, t.minuteSequence, t.location_id, sum(t.vuPerNode) as totalVu, sum(t.backOffPctSum) / sum(t.recordNum) as avgBackOffPct from ( select p.testconfig_id, p.minuteSequence, r.location_Id, SUM(p.activeCount) * 1.0 / COUNT(1) as vuPerNode, SUM(p.backOffPct) as backOffPctSum, COUNT(1) as recordNum from loadtest_progress_in_minute p ( nolock ) join loadtestRunrecord r ( nolock ) on p.test_id = r.test_id and p.nodeId = r.nodeId where p.test_id = @P0 group by p.testconfig_id, p.minuteSequence, p.nodeId, r.location_id ) t group by t.testconfig_id, t.minuteSequence, t.location_id order by t.testconfig_id, t.minuteSequence, t.location_id option (maxdop 23) ',@P0=1234
select @p1
このSQLパフォーマンスは高速です。約10秒しかかかりません。すべてのCPUが使用されます。
2012年8月21日更新
今まで、私は自分が見つけたものについて最終的な明確な結論を得ることができませんでした。Windowsの世界は開かれていないため、SQLServer内の詳細を取得できない可能性があります。ここでは、私が見つけたものについて説明します。いくつかの説明は私の推測です。他の人にも役立つことを願っています。
1)JDBCで2つの類似したSQLのパフォーマンスが異なる場合がある理由(これらのSQLは、最後のタブ文字が存在するかどうかを除いて同じです)
私たちのテストは、隔離された環境ではありません。JDBCを使用して2つのSQLをテストすると、他のプロセスも同時に最後のタブ文字が存在するSQLを実行します。したがって、テスト結果は他のプロセスの影響を受けます。
異なるパフォーマンスの根本的な原因は、彼らが異なる排出計画を選択することです。1つは良いparellesimでexection計画を選びました。もう1つは、パレレシムが悪い実行プランを選択しました。他のすべてのプロセスはタブ文字を使用してSQLを実行しているため、タブ文字を使用しないSQLは新しいSQLとして扱われます。したがって、タブ文字のないSQLを実行すると、レコードの統計に従って、一般的なパラメータ値に基づいて新しい実行プランが生成されます。たぶん、典型的な値は、最初はパフォーマンスが良くありません。ただし、実際にアクセスしたパラメーター値のヒストグラムは、実行プランのキャッシュをフラッシュする可能性があります。タブ文字のないSQLは、私のテストでのみ使用されます。私のテストでは、パラメーター値(1234)のみを使用しています。SQLサーバーは、SQLのために(1234)が頻繁に訪問されると考え、実際に最も一般的に訪問されたパラメーター値(1234)で実行プランをフラッシュします。そのため、パフォーマンスが良くなります。
タブ文字を使用してSQLに戻すと、SQLServerは古い実行プランのキャッシュを取得します。このキャッシュは、実行中の他のプロセスによって導入され、他のプロセスの影響を受ける可能性があります。この実行プランも、実際に最も人気のある訪問済みパラメーター値に基づいて生成されます。ただし、この値は他のプロセスの影響を受けます。したがって、(1234)ではない可能性があり、値に基づく実行プランは(1234)のパフォーマンスには適していません。そのため、タブ文字を使用したSQLのパフォーマンスが悪い場合があります。
実際にアクセスしたパラメータ値を変更するのに十分な頻度でテストを実行している場合、タブ文字を使用したSQLのテストプログラムでも実行計画キャッシュをフラッシュできることがあるためです。タブ文字を使用したSQLのパフォーマンスも向上する場合があります。
2)SSMSの次のSQLが常に遅い理由
declare @sql varchar(max)
set @sql='Declare @testId bigint;set @testId = 36887;select p.testconfig_id, p.minuteSequence,r.location_Id, SUM(p.activeCount) * 1.0 / COUNT(1) as vuPerNode, SUM(p.backOffPct) as backOffPctSum, COUNT(1) as recordNum from loadtest_progress_in_minute p with( nolock,index(idx_loadtest_progress_in_minute_1) ) join loadtestRunrecord r ( nolock ) on p.test_id = r.test_id and p.nodeId = r.nodeId where p.test_id = @testId group by p.testconfig_id, p.minuteSequence, p.nodeId, r.location_id option (maxdop 23)'
execute (@sql)
SSMSは、レコード統計の一般的なパラメータ値に基づいて実行プランも生成するためです。パラメータ値(1234)は非定型値です。したがって、上記のSQLは最初は遅いです。SSMSの「execute」コマンドは特別であり、そのキャッシュは実際に最も一般的にアクセスされるパラメーター値によってフラッシュされないと思います。だからそれはいつも遅い。私の実験結果によると、「sp_prepexec」と「sp_executesql」は「execute」とは異なり、それらのプランキャッシュは実際に最も一般的にアクセスされるパラメーター値によってフラッシュされ、JDBCと同様の動作をする可能性があります。
3)再コンパイルのヒントを追加すると上記のSQLのパフォーマンスが向上する理由この問題に答える前に、MSDNオンラインヘルプドキュメントの次のテキストを見てみましょう。
"SQL Serverデータベースエンジンに、クエリの実行後にクエリ用に生成されたプランを破棄するように指示し、クエリオプティマイザに、次に同じクエリが実行されたときにクエリプランを再コンパイルするように強制します。RECOMPILEを指定しない場合、データベースエンジンはクエリプランをキャッシュして再利用します。 。クエリプランをコンパイルするとき、RECOMPILEクエリヒントはクエリ内のローカル変数の現在の値を使用し、クエリがストアドプロシージャ内にある場合は、現在の値が任意のパラメータに渡されます。
RECOMPILEは、ストアドプロシージャ全体ではなく、ストアドプロシージャ内のクエリのサブセットのみを再コンパイルする必要がある場合に、WITHRECOMPILE句を使用するストアドプロシージャを作成するための便利な代替手段です。詳細については、「ストアドプロシージャの再コンパイル」を参照してください。RECOMPILEは、プランガイドを作成するときにも役立ちます。」
次の文に注意してください。クエリプランをコンパイルするとき、RECOMPILEクエリヒントはクエリ内のローカル変数の現在の値を使用し、クエリがストアドプロシージャ内にある場合は、現在の値が任意のパラメータに渡されます。
これは、RECOMPILEクエリヒントがSSMS実行プランの生成動作を変更することを意味します。SSMSは、RECOMPILEクエリヒントを使用せずに通常のパラメーター値に基づいて実行プランを生成しますが、SSMSは、RECOMPILEクエリヒントを使用して現在のパラメーター値に基づいて実行プランを生成します。したがって、ヒントを再コンパイルして実行プランを作成すると、現在のパラメータ値に最適になります(1234)
一言で言えば、実行計画は複雑な要因によって選択されます。慎重に検討する必要があります。