15年以上前にSQLServerを使い始めてから、バージョン2000を実行している顧客と一緒に作業して以来、tempdbについて同じ推奨事項を作成しています。要点:同じサイズの複数のデータファイルを同じ自動で作成する-成長設定、トレースフラグ1118(およびおそらく1117)を有効にし、tempdbの使用を減らします。顧客側から見ると、SQL Server 2019まで、これが実行できることの限界でした*。
* PamLahoudが彼女の非常に有益な投稿であるTEMPDB– Files and Trace Flags and Updates、Oh My!で説明している追加のコーディングに関する推奨事項がいくつかあります。
私が興味深いと思うのは、この間ずっと、tempdbがまだ問題であるということです。 SQL Serverチームは、問題を軽減するために何年にもわたって多くの変更を加えてきましたが、悪用は続いています。 SQL Serverチームによる最新の適応は、tempdbのシステムテーブル(メタデータ)をインメモリOLTP(別名メモリ最適化)に移動することです。一部の情報はSQLServer2019リリースノートで入手でき、PASSサミット基調講演の初日にBobWardとConorCunninghamからのデモがありました。パム・ラフードは、PASSサミットの一般セッションでも簡単なデモを行いました。 2019 CTP 3.2がリリースされたので、自分で少しテストする時期かもしれないと思いました。
セットアップ
SQL Server 2019 CTP 3.2を仮想マシンにインストールしています。仮想マシンには8GBのメモリ(最大サーバーメモリが6 GBに設定されています)と4つのvCPUがあります。それぞれ1GBのサイズの4つのtempdbデータファイルを作成しました。
WideWorldImportersのコピーを復元してから、3つのストアドプロシージャを作成しました(以下の定義)。各ストアドプロシージャは日付の入力を受け入れ、その日付のSales.OrderおよびSales.OrderLinesからのすべての行を一時オブジェクトにプッシュします。 Sales.usp_OrderInfoTVでは、オブジェクトはテーブル変数であり、Sales.usp_OrderInfoTTでは、オブジェクトはSELECT…INTOを介して定義された一時テーブルであり、後で非クラスター化が追加されます。Sales.usp_OrderInfoTTALTでは、オブジェクトは事前定義された一時テーブルであり、その後変更されます。追加の列があります。データが一時オブジェクトに追加された後、Sales.Customersテーブルに結合するオブジェクトに対するSELECTステートメントがあります。
/* Create the stored procedures */ USE [WideWorldImporters]; GO DROP PROCEDURE IF EXISTS Sales.usp_OrderInfoTV GO CREATE PROCEDURE Sales.usp_OrderInfoTV @OrderDate DATE AS BEGIN DECLARE @OrdersInfo TABLE ( OrderID INT, OrderLineID INT, CustomerID INT, StockItemID INT, Quantity INT, UnitPrice DECIMAL(18,2), OrderDate DATE); INSERT INTO @OrdersInfo ( OrderID, OrderLineID, CustomerID, StockItemID, Quantity, UnitPrice, OrderDate) SELECT o.OrderID, ol.OrderLineID, o.CustomerID, ol.StockItemID, ol.Quantity, ol.UnitPrice, OrderDate FROM Sales.Orders o INNER JOIN Sales.OrderLines ol ON o.OrderID = ol.OrderID WHERE o.OrderDate = @OrderDate; SELECT o.OrderID, c.CustomerName, SUM (o.Quantity), SUM (o.UnitPrice) FROM @OrdersInfo o JOIN Sales.Customers c ON o.CustomerID = c.CustomerID GROUP BY o.OrderID, c.CustomerName; END GO DROP PROCEDURE IF EXISTS Sales.usp_OrderInfoTT GO CREATE PROCEDURE Sales.usp_OrderInfoTT @OrderDate DATE AS BEGIN SELECT o.OrderID, ol.OrderLineID, o.CustomerID, ol.StockItemID, ol.Quantity, ol.UnitPrice, OrderDate INTO #temporderinfo FROM Sales.Orders o INNER JOIN Sales.OrderLines ol ON o.OrderID = ol.OrderID WHERE o.OrderDate = @OrderDate; SELECT o.OrderID, c.CustomerName, SUM (o.Quantity), SUM (o.UnitPrice) FROM #temporderinfo o JOIN Sales.Customers c ON o.CustomerID = c.CustomerID GROUP BY o.OrderID, c.CustomerName END GO DROP PROCEDURE IF EXISTS Sales.usp_OrderInfoTTALT GO CREATE PROCEDURE Sales.usp_OrderInfoTTALT @OrderDate DATE AS BEGIN CREATE TABLE #temporderinfo ( OrderID INT, OrderLineID INT, CustomerID INT, StockItemID INT, Quantity INT, UnitPrice DECIMAL(18,2)); INSERT INTO #temporderinfo ( OrderID, OrderLineID, CustomerID, StockItemID, Quantity, UnitPrice) SELECT o.OrderID, ol.OrderLineID, o.CustomerID, ol.StockItemID, ol.Quantity, ol.UnitPrice FROM Sales.Orders o INNER JOIN Sales.OrderLines ol ON o.OrderID = ol.OrderID WHERE o.OrderDate = @OrderDate; SELECT o.OrderID, c.CustomerName, SUM (o.Quantity), SUM (o.UnitPrice) FROM #temporderinfo o JOIN Sales.Customers c ON o.CustomerID c.CustomerID GROUP BY o.OrderID, c.CustomerName END GO /* Create tables to hold testing data */ USE [WideWorldImporters]; GO CREATE TABLE [dbo].[PerfTesting_Tests] ( [TestID] INT IDENTITY(1,1), [TestName] VARCHAR (200), [TestStartTime] DATETIME2, [TestEndTime] DATETIME2 ) ON [PRIMARY]; GO CREATE TABLE [dbo].[PerfTesting_WaitStats] ( [TestID] [int] NOT NULL, [CaptureDate] [datetime] NOT NULL DEFAULT (sysdatetime()), [WaitType] [nvarchar](60) NOT NULL, [Wait_S] [decimal](16, 2) NULL, [Resource_S] [decimal](16, 2) NULL, [Signal_S] [decimal](16, 2) NULL, [WaitCount] [bigint] NULL, [Percentage] [decimal](5, 2) NULL, [AvgWait_S] [decimal](16, 4) NULL, [AvgRes_S] [decimal](16, 4) NULL, [AvgSig_S] [decimal](16, 4) NULL ) ON [PRIMARY]; GO /* Enable Query Store (testing settings, not exactly what I would recommend for production) */ USE [master]; GO ALTER DATABASE [WideWorldImporters] SET QUERY_STORE = ON; GO ALTER DATABASE [WideWorldImporters] SET QUERY_STORE ( OPERATION_MODE = READ_WRITE, CLEANUP_POLICY = (STALE_QUERY_THRESHOLD_DAYS = 30), DATA_FLUSH_INTERVAL_SECONDS = 600, INTERVAL_LENGTH_MINUTES = 10, MAX_STORAGE_SIZE_MB = 1024, QUERY_CAPTURE_MODE = AUTO, SIZE_BASED_CLEANUP_MODE = AUTO); GO
テスト
SQL Server 2019のデフォルトの動作では、tempdbメタデータはメモリに最適化されていません。これは、sys.configurationsを確認することで確認できます。
SELECT * FROM sys.configurations WHERE configuration_id = 1589;
3つのストアドプロシージャすべてについて、sqlcmdを使用して、2つの異なる.sqlファイルのいずれかを実行する20の同時スレッドを生成します。 19個のスレッドで使用される最初の.sqlファイルは、1000回ループでプロシージャを実行します。 2番目の.sqlファイルはスレッドが1つだけで、3000回ループでプロシージャを実行します。このファイルには、対象の2つのメトリック(合計期間と待機統計)をキャプチャするためのTSQLも含まれています。クエリストアを使用して、手順の平均期間を取得します。
/* Example of first .sql file which calls the SP 1000 times */ SET NOCOUNT ON; GO USE [WideWorldImporters]; GO DECLARE @StartDate DATE; DECLARE @MaxDate DATE; DECLARE @Date DATE; DECLARE @Counter INT = 1; SELECT @StartDATE = MIN(OrderDate) FROM [WideWorldImporters].[Sales].[Orders]; SELECT @MaxDATE = MAX(OrderDate) FROM [WideWorldImporters].[Sales].[Orders]; SET @Date = @StartDate; WHILE @Counter <= 1000 BEGIN EXEC [Sales].[usp_OrderInfoTT] @Date; IF @Date <= @MaxDate BEGIN SET @Date = DATEADD(DAY, 1, @Date); END ELSE BEGIN SET @Date = @StartDate; END SET @Counter = @Counter + 1; END GO /* Example of second .sql file which calls the SP 3000 times and captures total duration and wait statisics */ SET NOCOUNT ON; GO USE [WideWorldImporters]; GO DECLARE @StartDate DATE; DECLARE @MaxDate DATE; DECLARE @DATE DATE; DECLARE @Counter INT = 1; DECLARE @TestID INT; DECLARE @TestName VARCHAR(200) = 'Execution of usp_OrderInfoTT - Disk Based System Tables'; INSERT INTO [WideWorldImporters].[dbo].[PerfTesting_Tests] ([TestName]) VALUES (@TestName); SELECT @TestID = MAX(TestID) FROM [WideWorldImporters].[dbo].[PerfTesting_Tests]; SELECT @StartDATE = MIN(OrderDate) FROM [WideWorldImporters].[Sales].[Orders]; SELECT @MaxDATE = MAX(OrderDate) FROM [WideWorldImporters].[Sales].[Orders]; SET @Date = @StartDate; IF EXISTS (SELECT * FROM [tempdb].[sys].[objects] WHERE [name] = N'##SQLskillsStats1') DROP TABLE [##SQLskillsStats1]; IF EXISTS (SELECT * FROM [tempdb].[sys].[objects] WHERE [name] = N'##SQLskillsStats2') DROP TABLE [##SQLskillsStats2]; SELECT [wait_type], [waiting_tasks_count], [wait_time_ms], [max_wait_time_ms], [signal_wait_time_ms] INTO ##SQLskillsStats1 FROM sys.dm_os_wait_stats; /* set start time */ UPDATE [WideWorldImporters].[dbo].[PerfTesting_Tests] SET [TestStartTime] = SYSDATETIME() WHERE [TestID] = @TestID; WHILE @Counter <= 3000 BEGIN EXEC [Sales].[usp_OrderInfoTT] @Date; IF @Date <= @MaxDate BEGIN SET @Date = DATEADD(DAY, 1, @Date); END ELSE BEGIN SET @Date = @StartDate; END SET @Counter = @Counter + 1 END /* set end time */ UPDATE [WideWorldImporters].[dbo].[PerfTesting_Tests] SET [TestEndTime] = SYSDATETIME() WHERE [TestID] = @TestID; SELECT [wait_type], [waiting_tasks_count], [wait_time_ms], [max_wait_time_ms], [signal_wait_time_ms] INTO ##SQLskillsStats2 FROM sys.dm_os_wait_stats; WITH [DiffWaits] AS (SELECT -- Waits that weren't in the first snapshot [ts2].[wait_type], [ts2].[wait_time_ms], [ts2].[signal_wait_time_ms], [ts2].[waiting_tasks_count] FROM [##SQLskillsStats2] AS [ts2] LEFT OUTER JOIN [##SQLskillsStats1] AS [ts1] ON [ts2].[wait_type] = [ts1].[wait_type] WHERE [ts1].[wait_type] IS NULL AND [ts2].[wait_time_ms] > 0 UNION SELECT -- Diff of waits in both snapshots [ts2].[wait_type], [ts2].[wait_time_ms] - [ts1].[wait_time_ms] AS [wait_time_ms], [ts2].[signal_wait_time_ms] - [ts1].[signal_wait_time_ms] AS [signal_wait_time_ms], [ts2].[waiting_tasks_count] - [ts1].[waiting_tasks_count] AS [waiting_tasks_count] FROM [##SQLskillsStats2] AS [ts2] LEFT OUTER JOIN [##SQLskillsStats1] AS [ts1] ON [ts2].[wait_type] = [ts1].[wait_type] WHERE [ts1].[wait_type] IS NOT NULL AND [ts2].[waiting_tasks_count] - [ts1].[waiting_tasks_count] > 0 AND [ts2].[wait_time_ms] - [ts1].[wait_time_ms] > 0), [Waits] AS (SELECT [wait_type], [wait_time_ms] / 1000.0 AS [WaitS], ([wait_time_ms] - [signal_wait_time_ms]) / 1000.0 AS [ResourceS], [signal_wait_time_ms] / 1000.0 AS [SignalS], [waiting_tasks_count] AS [WaitCount], 100.0 * [wait_time_ms] / SUM ([wait_time_ms]) OVER() AS [Percentage], ROW_NUMBER() OVER(ORDER BY [wait_time_ms] DESC) AS [RowNum] FROM [DiffWaits] WHERE [wait_type] NOT IN ( -- These wait types are almost 100% never a problem and so they are -- filtered out to avoid them skewing the results. N'BROKER_EVENTHANDLER', N'BROKER_RECEIVE_WAITFOR', N'BROKER_TASK_STOP', N'BROKER_TO_FLUSH', N'BROKER_TRANSMITTER', N'CHECKPOINT_QUEUE', N'CHKPT', N'CLR_AUTO_EVENT', N'CLR_MANUAL_EVENT', N'CLR_SEMAPHORE', N'CXCONSUMER', N'DBMIRROR_DBM_EVENT', N'DBMIRROR_EVENTS_QUEUE', N'DBMIRROR_WORKER_QUEUE', N'DBMIRRORING_CMD', N'DIRTY_PAGE_POLL', N'DISPATCHER_QUEUE_SEMAPHORE', N'EXECSYNC', N'FSAGENT', N'FT_IFTS_SCHEDULER_IDLE_WAIT', N'FT_IFTSHC_MUTEX', N'HADR_CLUSAPI_CALL', N'HADR_FILESTREAM_IOMGR_IOCOMPLETION', N'HADR_LOGCAPTURE_WAIT', N'HADR_NOTIFICATION_DEQUEUE', N'HADR_TIMER_TASK', N'HADR_WORK_QUEUE', N'KSOURCE_WAKEUP', N'LAZYWRITER_SLEEP', N'LOGMGR_QUEUE', N'MEMORY_ALLOCATION_EXT', N'ONDEMAND_TASK_QUEUE', N'PARALLEL_REDO_DRAIN_WORKER', N'PARALLEL_REDO_LOG_CACHE', N'PARALLEL_REDO_TRAN_LIST', N'PARALLEL_REDO_WORKER_SYNC', N'PARALLEL_REDO_WORKER_WAIT_WORK', N'PREEMPTIVE_XE_GETTARGETSTATE', N'PWAIT_ALL_COMPONENTS_INITIALIZED', N'PWAIT_DIRECTLOGCONSUMER_GETNEXT', N'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', N'QDS_ASYNC_QUEUE', N'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP', N'QDS_SHUTDOWN_QUEUE', N'REDO_THREAD_PENDING_WORK', N'REQUEST_FOR_DEADLOCK_SEARCH', N'RESOURCE_QUEUE', N'SERVER_IDLE_CHECK', N'SLEEP_BPOOL_FLUSH', N'SLEEP_DBSTARTUP', N'SLEEP_DCOMSTARTUP', N'SLEEP_MASTERDBREADY', N'SLEEP_MASTERMDREADY', N'SLEEP_MASTERUPGRADED', N'SLEEP_MSDBSTARTUP', N'SLEEP_SYSTEMTASK', N'SLEEP_TASK', N'SLEEP_TEMPDBSTARTUP', N'SNI_HTTP_ACCEPT', N'SOS_WORK_DISPATCHER', N'SP_SERVER_DIAGNOSTICS_SLEEP', N'SQLTRACE_BUFFER_FLUSH', N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', N'SQLTRACE_WAIT_ENTRIES', N'WAIT_FOR_RESULTS', N'WAITFOR', N'WAITFOR_TASKSHUTDOWN', N'WAIT_XTP_RECOVERY', N'WAIT_XTP_HOST_WAIT', N'WAIT_XTP_OFFLINE_CKPT_NEW_LOG', N'WAIT_XTP_CKPT_CLOSE', N'XE_DISPATCHER_JOIN', N'XE_DISPATCHER_WAIT', N'XE_TIMER_EVENT' ) ) INSERT INTO [WideWorldImporters].[dbo].[PerfTesting_WaitStats] ( [TestID], [WaitType] , [Wait_S] , [Resource_S] , [Signal_S] , [WaitCount] , [Percentage] , [AvgWait_S] , [AvgRes_S] , [AvgSig_S] ) SELECT @TestID, [W1].[wait_type] AS [WaitType], CAST ([W1].[WaitS] AS DECIMAL (16, 2)) AS [Wait_S], CAST ([W1].[ResourceS] AS DECIMAL (16, 2)) AS [Resource_S], CAST ([W1].[SignalS] AS DECIMAL (16, 2)) AS [Signal_S], [W1].[WaitCount] AS [WaitCount], CAST ([W1].[Percentage] AS DECIMAL (5, 2)) AS [Percentage], CAST (([W1].[WaitS] / [W1].[WaitCount]) AS DECIMAL (16, 4)) AS [AvgWait_S], CAST (([W1].[ResourceS] / [W1].[WaitCount]) AS DECIMAL (16, 4)) AS [AvgRes_S], CAST (([W1].[SignalS] / [W1].[WaitCount]) AS DECIMAL (16, 4)) AS [AvgSig_S] FROM [Waits] AS [W1] INNER JOIN [Waits] AS [W2] ON [W2].[RowNum] <= [W1].[RowNum] GROUP BY [W1].[RowNum], [W1].[wait_type], [W1].[WaitS], [W1].[ResourceS], [W1].[SignalS], [W1].[WaitCount], [W1].[Percentage] HAVING SUM ([W2].[Percentage]) - [W1].[Percentage] < 95; -- percentage threshold GO -- Cleanup IF EXISTS (SELECT * FROM [tempdb].[sys].[objects] WHERE [name] = N'##SQLskillsStats1') DROP TABLE [##SQLskillsStats1]; IF EXISTS (SELECT * FROM [tempdb].[sys].[objects] WHERE [name] = N'##SQLskillsStats2') DROP TABLE [##SQLskillsStats2]; GO
コマンドラインファイルの例:
結果
ストアドプロシージャごとに20スレッドを生成するコマンドラインファイルを実行した後、各プロシージャの12,000回の実行の合計期間を確認すると、次のようになります。
SELECT *, DATEDIFF(SECOND, TestStartTime, TestEndTime) AS [TotalDuration] FROM [dbo].[PerfTesting_Tests] ORDER BY [TestID];
一時テーブル(usp_OrderInfoTTおよびusp_OrderInfoTTC)を使用したストアドプロシージャは、完了するまでに時間がかかりました。個々のクエリのパフォーマンスを見ると:
SELECT [qsq].[query_id], [qsp].[plan_id], OBJECT_NAME([qsq].[object_id]) AS [ObjectName], [rs].[count_executions], [rs].[last_execution_time], [rs].[avg_duration], [rs].[avg_logical_io_reads], [qst].[query_sql_text] FROM [sys].[query_store_query] [qsq] JOIN [sys].[query_store_query_text] [qst] ON [qsq].[query_text_id] = [qst].[query_text_id] JOIN [sys].[query_store_plan] [qsp] ON [qsq].[query_id] = [qsp].[query_id] JOIN [sys].[query_store_runtime_stats] [rs] ON [qsp].[plan_id] = [rs].[plan_id] WHERE ([qsq].[object_id] = OBJECT_ID('Sales.usp_OrderInfoTT')) OR ([qsq].[object_id] = OBJECT_ID('Sales.usp_OrderInfoTV')) OR ([qsq].[object_id] = OBJECT_ID('Sales.usp_OrderInfoTTALT')) ORDER BY [qsq].[query_id], [rs].[last_execution_time];
usp_OrderInfoTTのSELECT…INTOは平均で約28ミリ秒かかり(クエリストアの期間はマイクロ秒単位で保存されます)、一時テーブルが事前に作成されている場合は9ミリ秒しかかかりませんでした。テーブル変数の場合、INSERTには平均で22ミリ秒強かかりました。興味深いことに、SELECTクエリは一時テーブルで1ミリ秒強、テーブル変数で約2.7ミリ秒かかりました。
待機統計データをチェックすると、おなじみのwait_type、PAGELATCH *が見つかります:
SELECT * FROM [dbo].[PerfTesting_WaitStats] ORDER BY [TestID], [Percentage] DESC;
PAGELATCH *は、一時テーブルを使用したプロシージャであるテスト1と2のみを待機していることに注意してください。テーブル変数を使用したusp_OrderInfoTVの場合、SOS_SCHEDULER_YIELD待機のみが表示されます。 注意: これは、一時テーブルの代わりにテーブル変数を使用する必要があることを意味するものではありません。 、また、しないことを意味するものでもありません。 PAGELATCHをテーブル変数で待機させます。これは不自然なシナリオです。私は非常に コードでテストして、wait_typesがどのように表示されるかを確認することをお勧めします。
次に、tempdbメタデータにメモリ最適化テーブルを使用するようにインスタンスを変更します。これを行うには、ALTER SERVER CONFIGURATIONコマンドを使用する方法と、sp_configureを使用する方法の2つがあります。この設定は詳細オプションであるため、sp_configureを使用する場合は、最初に詳細オプションを有効にする必要があります。
ALTER SERVER CONFIGURATION SET MEMORY_OPTIMIZED TEMPDB_METADATA = ON; GO
この変更後、インスタンスを再起動する必要があります。 (注:これを元に戻して、メモリ最適化テーブルを使用しないようにすることができます。インスタンスを再起動する必要があります。)再起動後、sys.configurationsを再度確認すると、メタデータテーブルがメモリ最適化されていることがわかります。
コマンドラインファイルを再度実行した後、各プロシージャの21,000回の実行の合計時間は次のようになります(比較しやすいように、結果はストアドプロシージャの順に並べられていることに注意してください)。
usp_OrderInfoTTとusp_OrderInfoTTCの両方でパフォーマンスが確実に向上し、usp_OrderInfoTVのパフォーマンスがわずかに向上しました。クエリ期間を確認しましょう:
テーブルが事前に作成されたときにINSERT期間が長くなることを除いて、すべてのクエリでクエリ期間はほぼ同じです。これはまったく予期しないことです。待機統計に興味深い変化が見られます:
usp_OrderInfoTTの場合、一時テーブルを作成するためにSELECT…INTOが実行されます。待機は、PAGELATCH_EXおよびPAGELATCH_SHからのみに変更されます。 PAGELATCH_EXおよび SOS_SCHEDULER_YIELD。 PAGELATCH_SHの待機が表示されなくなりました。
一時テーブルを作成してから挿入するusp_OrderInfoTTCの場合、PAGELATCH_EXおよびPAGELATCH_SH待機は表示されなくなり、SOS_SCHEDULER_YIELD待機のみが表示されます。
最後に、OrderInfoTVの場合、待機は一貫しています。SOS_SCHEDULER_YIELDのみで、合計待機時間はほぼ同じです。
概要
このテストに基づいて、一時テーブルを使用したストアドプロシージャで、すべてのケースで大幅な改善が見られます。テーブル変数プロシージャにわずかな変更があります。これは1つのシナリオであり、負荷テストが小さいことを覚えておくことが非常に重要です。 tempdbメタデータをメモリ最適化することで何が最もメリットがあるかを理解するために、これら3つの非常に単純なシナリオを試すことに非常に興味がありました。このワークロードは小さく、非常に限られた時間で実行されました。実際、スレッドが増えると結果はより多様になりました。これは別の投稿で調べる価値があります。最大のポイントは、すべての新機能と同様に、テストが重要であるということです。この機能では、現在のパフォーマンスのベースラインを取得して、バッチリクエスト/秒などのメトリックを比較し、メタデータをメモリ最適化した後に統計を待機する必要があります。
追加の考慮事項
インメモリOLTPを使用するには、MEMORYOPTIMIZEDDATAタイプのファイルグループが必要です。ただし、MEMORY_OPTIMIZED TEMPDB_METADATAを有効にすると、tempdb用に追加のファイルグループは作成されません。さらに、メモリ最適化テーブルが永続的であるか(SCHEMA_AND_DATA)、そうでないか(SCHEMA_ONLY)は不明です。通常、これはsys.tables(durability_desc)を介して決定できますが、専用管理者接続を使用している場合でも、tempdbでこれを照会すると、関連するシステムテーブルには何も返されません。メモリ最適化テーブルの非クラスター化インデックスを表示する機能があります。次のクエリを使用して、tempdbでメモリが最適化されているテーブルを確認できます。
SELECT * FROM tempdb.sys.dm_db_xtp_object_stats x JOIN tempdb.sys.objects o ON x.object_id = o.object_id JOIN tempdb.sys.schemas s ON o.schema_id = s.schema_id;
次に、いずれかのテーブルに対して、次のようにsp_helpindexを実行します。
EXEC sys.sp_helpindex N'sys.sysobjvalues';
ハッシュインデックス(作成の一部としてBUCKET_COUNTを推定する必要がある)の場合、説明には「非クラスター化ハッシュ」が含まれることに注意してください。