私はここの記事を見ていた 一時テーブルとテーブル変数、およびSQL Serverのパフォーマンスへの影響 とSQL Server 2008では、2005年に表示されたものと同様の結果を再現できた。
10行のみでストアドプロシージャ(以下の定義)を実行すると、テーブル変数version outは一時テーブルバージョンを2回以上実行します。
プロシージャキャッシュをクリアし、両方のストアドプロシージャを10,000回実行してから、このプロセスをさらに4回実行しました。以下の結果(バッチあたりのミリ秒単位の時間)
T2_Time V2_Time
----------- -----------
8578 2718
6641 2781
6469 2813
6766 2797
6156 2719
私の質問は:テーブル変数バージョンのパフォーマンスが向上する理由は何ですか?
調査を行いました。例えばパフォーマンスカウンターを見る
SELECT cntr_value
from sys.dm_os_performance_counters
where counter_name = 'Temp Tables Creation Rate';
どちらの場合も、最初の実行後に一時オブジェクトがキャッシュされることを確認します 予想どおり 呼び出しごとに最初から作成されるのではなく.
同様に、プロファイラーでAuto Stats
、SP:Recompile
、SQL:StmtRecompile
eventsをトレースすると(以下のスクリーンショット)、これらのイベントは1回だけ発生します(#temp
テーブルストアドプロシージャの最初の呼び出し時に)。他の9,999回の実行では、これらのイベントは発生しません。 (テーブル変数バージョンはこれらのイベントを取得しません)
ストアドプロシージャの最初の実行のわずかに大きなオーバーヘッドは、全体的な大きな違いを説明できません。ただし、プロシージャキャッシュをクリアして両方のプロシージャを1回実行するのに数ミリ秒しかかからないため、統計または再コンパイルが原因である可能性があります。
必要なデータベースオブジェクトの作成
CREATE DATABASE TESTDB_18Feb2012;
GO
USE TESTDB_18Feb2012;
CREATE TABLE NUM
(
n INT PRIMARY KEY,
s VARCHAR(128)
);
WITH NUMS(N)
AS (SELECT TOP 1000000 ROW_NUMBER() OVER (ORDER BY $/0)
FROM master..spt_values v1,
master..spt_values v2)
INSERT INTO NUM
SELECT N,
'Value: ' + CONVERT(VARCHAR, N)
FROM NUMS
GO
CREATE PROCEDURE [dbo].[T2] @total INT
AS
CREATE TABLE #T
(
n INT PRIMARY KEY,
s VARCHAR(128)
)
INSERT INTO #T
SELECT n,
s
FROM NUM
WHERE n%100 > 0
AND n <= @total
DECLARE @res VARCHAR(128)
SELECT @res = MAX(s)
FROM NUM
WHERE n <= @total
AND NOT EXISTS(SELECT *
FROM #T
WHERE #T.n = NUM.n)
GO
CREATE PROCEDURE [dbo].[V2] @total INT
AS
DECLARE @V TABLE (
n INT PRIMARY KEY,
s VARCHAR(128))
INSERT INTO @V
SELECT n,
s
FROM NUM
WHERE n%100 > 0
AND n <= @total
DECLARE @res VARCHAR(128)
SELECT @res = MAX(s)
FROM NUM
WHERE n <= @total
AND NOT EXISTS(SELECT *
FROM @V V
WHERE V.n = NUM.n)
GO
テストスクリプト
SET NOCOUNT ON;
DECLARE @T1 DATETIME2,
@T2 DATETIME2,
@T3 DATETIME2,
@Counter INT = 0
SET @T1 = SYSDATETIME()
WHILE ( @Counter < 10000)
BEGIN
EXEC dbo.T2 10
SET @Counter += 1
END
SET @T2 = SYSDATETIME()
SET @Counter = 0
WHILE ( @Counter < 10000)
BEGIN
EXEC dbo.V2 10
SET @Counter += 1
END
SET @T3 = SYSDATETIME()
SELECT DATEDIFF(MILLISECOND,@T1,@T2) AS T2_Time,
DATEDIFF(MILLISECOND,@T2,@T3) AS V2_Time
これは古い質問なので、SQL Serverの新しいバージョンの問題を再検討して、同じパフォーマンスプロファイルがまだ存在するかどうか、または特性がまったく変更されていないかどうかを確認することにしました。
具体的には、 SQL Server 2019のメモリ内システムテーブル の追加は、再テストする価値のある機会のようです。
他の作業をしているときにこの問題に遭遇したため、私は少し異なるテストハーネスを使用しています。
2013バージョンのStack Overflow を使用して、このインデックスと次の2つの手順があります。
インデックス:
CREATE INDEX ix_whatever
ON dbo.Posts(OwnerUserId) INCLUDE(Score);
GO
一時テーブル:
CREATE OR ALTER PROCEDURE dbo.TempTableTest(@Id INT)
AS
BEGIN
SET NOCOUNT ON;
CREATE TABLE #t(i INT NOT NULL);
DECLARE @i INT;
INSERT #t ( i )
SELECT p.Score
FROM dbo.Posts AS p
WHERE p.OwnerUserId = @Id;
SELECT @i = AVG(t.i)
FROM #t AS t;
END;
GO
テーブル変数:
CREATE OR ALTER PROCEDURE dbo.TableVariableTest(@Id INT)
AS
BEGIN
SET NOCOUNT ON;
DECLARE @t TABLE (i INT NOT NULL);
DECLARE @i INT;
INSERT @t ( i )
SELECT p.Score
FROM dbo.Posts AS p
WHERE p.OwnerUserId = @Id;
SELECT @i = AVG(t.i)
FROM @t AS t;
END;
GO
潜在的な ASYNC_NETWORK_IO待機 を防ぐために、ラッパープロシージャを使用しています。
CREATE PROCEDURE #TT AS
SET NOCOUNT ON;
DECLARE @i INT = 1;
DECLARE @StartDate DATETIME2(7) = SYSDATETIME();
WHILE @i <= 50000
BEGIN
EXEC dbo.TempTableTest @Id = @i;
SET @i += 1;
END;
SELECT DATEDIFF(MILLISECOND, @StartDate, SYSDATETIME()) AS [ElapsedTimeMilliseconds];
GO
CREATE PROCEDURE #TV AS
SET NOCOUNT ON;
DECLARE @i INT = 1;
DECLARE @StartDate DATETIME2(7) = SYSDATETIME();
WHILE @i <= 50000
BEGIN
EXEC dbo.TableVariableTest @Id = @i;
SET @i += 1;
END;
SELECT DATEDIFF(MILLISECOND, @StartDate, SYSDATETIME()) AS [ElapsedTimeMilliseconds];
GO
この時点で2014および2016は基本的にRELICSであるため、2017年からテストを開始します。また、簡潔にするために、 Perfview を使用してコードのプロファイリングを開始します。実生活では、待機、ラッチ、スピンロック、クレイジートレースフラグなどを調べました。
コードのプロファイリングは、興味のあることを明らかにした唯一のものです。
時差:
まだ非常に明確な違いですよね?しかし、SQL Serverは今何をしているのでしょうか
差分サンプルの上位2つの増加を見ると、sqlmin
とsqlsqllang!TCacheStore<CacheClockAlgorithm>::GetNextUserDataInHashBucket
が2つの最大の違反者であることがわかります。
コールスタックの名前から判断すると、一時テーブルのクリーンアップと内部での名前変更は、一時テーブルの呼び出しとテーブル変数の呼び出しで最も時間がかかるようです。
テーブル変数は一時テーブルによって内部的にサポートされていますが、これは問題ではないようです。
SET STATISTICS IO ON;
DECLARE @t TABLE(id INT);
SELECT * FROM @t AS t;
テーブル '#B98CE339'。スキャンカウント1
テーブル変数testのコールスタックを調べても、主な違反者はまったく表示されません。
申し訳ありませんが、これはSQL Server 2017の問題ですが、2019ですぐに何か違いはありますか?
まず、私の袖の上には何もないことを示すために:
SELECT c.name,
c.value_in_use,
c.description
FROM sys.configurations AS c
WHERE c.name = 'tempdb metadata memory-optimized';
時差:
どちらの手順も異なりました。一時テーブルの呼び出しは数秒速くなり、テーブル変数の呼び出しは約1.5秒遅くなりました。テーブル変数のスローダウンは、2019年の新しいオプティマイザの選択である テーブル変数の遅延コンパイル によって部分的に説明される場合があります。
Perfviewのdiffを見ると、少し変わっています-sqlminはもうありません-sqllang!TCacheStore<CacheClockAlgorithm>::GetNextUserDataInHashBucket
は違います。
この新しいメモリシステムテーブルのことはどうですか?えっ?いいですか?
つけましょう!
EXEC sys.sp_configure @configname = 'advanced',
@configvalue = 1
RECONFIGURE;
EXEC sys.sp_configure @configname = 'tempdb metadata memory-optimized',
@configvalue = 1
RECONFIGURE;
これを実行するにはSQL Serverを再起動する必要があることに注意してください。この素敵な金曜日の午後にSQLを再起動している間はご容赦ください。
今、物事は異なって見えます:
SELECT c.name,
c.value_in_use,
c.description
FROM sys.configurations AS c
WHERE c.name = 'tempdb metadata memory-optimized';
SELECT *,
OBJECT_NAME(object_id) AS object_name,
@@VERSION AS sql_server_version
FROM tempdb.sys.memory_optimized_tables_internal_attributes;
時差:
一時テーブルは約4秒速くなりました!それは何かです。
私は何かが好きです。
今回は、Perfviewの差分はそれほど興味深いものではありません。並んでいると、時間が全体でどれほど近いかを知るのは興味深いことです。
差分の興味深い点の1つは、hkengine!
の呼び出しです。これは、ヘカトンっぽい機能が現在使用されているため、明白に思えるかもしれません。
差分の上位2項目については、ntoskrnl!?
をあまり活用できません。
またはsqltses!CSqlSortManager_80::GetSortKey
ですが、Smrtr Ppl™が参照するためにここにあります。
文書化されていないため、本番環境では絶対に安全ではないため、使用しないでください スタートアップトレースフラグ 追加の一時テーブルシステムオブジェクト(sysrowsets、sysallocunits、sysseobjvalues)をインメモリ機能ですが、この場合、実行時間に大きな違いはありませんでした。
SQLサーバーの新しいバージョンでも、テーブル変数への高頻度の呼び出しは、一時テーブルへの高頻度の呼び出しよりもはるかに高速です。
コンパイル、再コンパイル、自動統計、ラッチ、スピンロック、キャッシング、またはその他の問題のせいにするのは魅力的ですが、問題は明らかに一時テーブルのクリーンアップの管理に関連しています。
インメモリシステムテーブルを有効にしたSQL Server 2019でのより近い呼び出しですが、呼び出し頻度が高い場合でもテーブル変数のパフォーマンスは向上します。
もちろん、かつて熟考されていたvaping sageとして、「プランの選択が問題ではないときにテーブル変数を使用する」です。