Давам същите препоръки относно tempdb, откакто започнах да работя със SQL Server преди повече от 15 години, когато работех с клиенти, работещи с версия 2000. Същността на това:създайте множество файлове с данни с еднакъв размер, със същия автоматичен -настройки за растеж, активирайте флаг за проследяване 1118 (и може би 1117) и намалете използването на tempdb. От страна на клиента това беше границата на това, което може да се направи* до SQL Server 2019.
*Има няколко допълнителни препоръки за кодиране, които Пам Лахуд обсъжда в своята много информативна публикация TEMPDB – Флагове и актуализации за файлове и проследяване, о, Боже!
Това, което намирам за интересно е, че след толкова време tempdb все още е проблем. Екипът на SQL Server направи много промени през годините, за да се опита да смекчи проблемите, но злоупотребата продължава. Последната адаптация от екипа на SQL Server премества системните таблици (метаданни) за tempdb към In-Memory OLTP (известен още като оптимизиран за памет). Част от информацията е налична в бележките за версията на SQL Server 2019 и имаше демонстрация от Боб Уорд и Конър Кънингам през първия ден от основната реч на PASS Summit. Пам Лахуд също направи бърза демонстрация в общата си сесия на PASS Summit. Сега, когато 2019 CTP 3.2 излезе, реших, че може би е време да направя малко тестване.
Настройка
Имам инсталиран SQL Server 2019 CTP 3.2 на моята виртуална машина, която има 8GB памет (максималната памет на сървъра е зададена на 6 GB) и 4 vCPU. Създадох четири (4) tempdb файла с данни, всеки с размер до 1GB.
Възстанових копие на WideWorldImporters и след това създадох три съхранени процедури (дефиниции по-долу). Всяка съхранена процедура приема въвеждане на дата и избутва всички редове от Sales.Order и Sales.OrderLines за тази дата във временния обект. В Sales.usp_OrderInfoTV обектът е променлива на таблица, в Sales.usp_OrderInfoTT обектът е временна таблица, дефинирана чрез SELECT ... INTO с неклъстер, добавен след това, а в Sales.usp_OrderInfoTTALT обектът е предварително дефинирана временна таблица, която след това се променя да има допълнителна колона. След като данните се добавят към временния обект, има оператор SELECT срещу обекта, който се присъединява към таблицата Sales.Customers.
/* 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;
И за трите съхранени процедури ще използваме sqlcmd, за да генерираме 20 едновременни нишки, изпълняващи един от два различни .sql файла. Първият .sql файл, който ще се използва от 19 нишки, ще изпълни процедурата в цикъл 1000 пъти. Вторият .sql файл, който ще има само една (1) нишка, ще изпълни процедурата в цикъл 3000 пъти. Файлът също така включва TSQL за улавяне на два показателя от интерес:обща продължителност и статистика за чакане. Ще използваме Query Store, за да уловим средната продължителност на процедурата.
/* 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];
Можем да видим, че SELECT ... INTO за usp_OrderInfoTT отне средно около 28 мс (продължителността в хранилището на заявки се съхранява в микросекунди) и отне само 9 мс, когато временната таблица е била предварително създадена. За променливата на таблицата INSERT отне средно малко над 22 мс. Интересното е, че заявката SELECT отне малко над 1 мс за временните таблици и приблизително 2,7 мс за променливата на таблицата.
Проверката на статистическите данни за чакане открива познат тип_чакане, PAGELATCH*:
SELECT * FROM [dbo].[PerfTesting_WaitStats] ORDER BY [TestID], [Percentage] DESC;
Забележете, че виждаме само PAGELATCH* изчаква за тестове 1 и 2, които бяха процедурите с временните таблици. За usp_OrderInfoTV, който използва таблична променлива, виждаме само SOS_SCHEDULER_YIELD чака. Моля, обърнете внимание: Това по никакъв начин не означава, че трябва да използвате променливи на таблица вместо временни таблици , нито означава, че не има PAGELATCH чака с променливи в таблицата. Това е измислен сценарий; Аз високо препоръчваме да тествате с ВАШИЯ код, за да видите какви чакащи_типове се появяват.
Сега ще променим екземпляра, за да използваме оптимизирани за паметта таблици за метаданните tempdb. Това може да стане по два начина, чрез командата ALTER SERVER CONFIGURATION или чрез sp_configure. Тъй като тази настройка е разширена опция, ако използвате 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, с почти същото общо време на изчакване.
Резюме
Въз основа на това тестване виждаме подобрение във всички случаи, значително за съхранените процедури с временни таблици. Има лека промяна в процедурата за променлива на таблицата. Изключително важно е да запомните, че това е един сценарий с малък тест за натоварване. Беше ми много интересно да опитам тези три много прости сценария, за да се опитам да разбера какво може да се възползва най-много от оптимизирането на паметта на метаданните tempdb. Това работно натоварване беше малко и се изпълняваше за много ограничено време – всъщност имах по-разнообразни резултати с повече нишки, което си струва да разгледате в друга публикация. Най-големият извод е, че както при всички нови функции и функционалност, тестването е важно. За тази функция искате да имате базова стойност на текущата производителност, спрямо която да сравнявате показатели като пакетни заявки/сек и статистика за изчакване, след като направите метаданните оптимизирани за паметта.
Допълнителни съображения
Използването на OLTP в паметта изисква файлова група от типа ПАМЕТ ОПТИМИЗИРАНИ ДАННИ. Въпреки това, след активиране на 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 като част от създаването), описанието ще включва „неклъстериран хеш“.