Яка різниця між тимчасовою таблицею та змінною таблиці в SQL Server?


452

Здається, це область з досить багато міфів та суперечливих поглядів.

Тож у чому різниця між змінною таблиці та локальною тимчасовою таблицею в SQL Server?


Відповіді:


668

Зміст

Зміст

Caveat

У цій відповіді обговорюються "класичні" змінні таблиці, введені в SQL Server 2000. SQL Server 2014 в пам'яті OLTP вводить типи таблиць, оптимізованих за пам'яттю. Вимірники змінних таблиць багато в чому відрізняються від наведених нижче! ( детальніше ).

Місце зберігання

Без різниці. Обидва зберігаються в tempdb.

Я бачив, як було запропоновано, що для змінних таблиць це не завжди так, але це можна перевірити нижче

DECLARE @T TABLE(X INT)

INSERT INTO @T VALUES(1),(2)

SELECT sys.fn_PhysLocFormatter(%%physloc%%) AS [File:Page:Slot]
FROM @T

Приклад результатів (відображення розташування в tempdbдвох рядках зберігається)

File:Page:Slot
----------------
(1:148:0)
(1:148:1)

Логічне розташування

@table_variablesповодиться більше так, ніби вони були частиною поточної бази даних, ніж #tempце роблять таблиці. Для змінних таблиць (з 2005 року) зіставлення стовпців, якщо явно не вказано, буде поточною базою даних, тоді як для #tempтаблиць буде використовуватися порівняння за замовчуванням tempdb( Докладніше ). Крім того, визначені користувачем типи даних та колекції XML повинні бути в tempdb для використання у #tempтаблицях, але змінні таблиці можуть використовувати їх з поточної бази даних ( Джерело ).

SQL Server 2012 представляє вміщені бази даних. поведінка тимчасових таблиць у цих відрізняється (г / т Аарон)

У вміщеній базі даних дані тимчасової таблиці порівнюються при зіставленні вміщеної бази даних.

  • Усі метадані, пов'язані з тимчасовими таблицями (наприклад, назви таблиць і стовпців, індекси тощо), будуть у зіставленні каталогу.
  • Названі обмеження можуть не використовуватися у тимчасових таблицях.
  • Тимчасові таблиці можуть не посилатися на визначені користувачем типи, колекції XML-схем або визначені користувачем функції.

Видимість різних областей

@table_variablesможна отримати доступ лише в тій партії та обсязі, в якому вони оголошені. #temp_tablesдоступні в дочірніх партіях (вкладені тригери, процедура, execдзвінки). #temp_tablesстворений у зовнішній області ( @@NESTLEVEL=0), може також охоплювати партії, оскільки вони зберігаються до завершення сеансу. Жоден тип об'єкта не може бути створений у дочірній партії та доступ до нього в області виклику, проте, як обговорюється далі (глобальні ##tempтаблиці можуть бути хоч).

Час життя

@table_variablesстворюються неявно, коли DECLARE @.. TABLEвиконується пакет, що містить оператор (перед тим, як виконуватиметься будь-який код користувача в цій пакеті) і неявно скидається в кінці.

Хоча аналізатор не дозволить вам спробувати використати змінну таблиці перед DECLAREтвердженням, неявне створення можна побачити нижче.

IF (1 = 0)
BEGIN
DECLARE @T TABLE(X INT)
END

--Works fine
SELECT *
FROM @T

#temp_tablesстворюються явно, коли трапляється CREATE TABLEоператор TSQL , і його можна явно скинути з DROP TABLEабо буде відмінено неявно, коли пакет закінчується (якщо створено в дочірній пакеті з @@NESTLEVEL > 0) або коли сеанс закінчується інакше.

Примітка. У межах збережених процедур обидва типи об'єктів можуть кешуватися, а не повторно створювати та випускати нові таблиці. Існують обмеження щодо того, коли це кешування може відбутися, однак їх можна порушити, #temp_tablesале які обмеження щодо @table_variablesзапобігання в будь-якому разі не можна. Витрати на обслуговування кешованих #tempтаблиць трохи більше, ніж для змінних таблиць, як показано тут .

Метадані об’єкта

Це по суті однаково для обох типів об'єкта. Він зберігається в базових таблицях системи в tempdb. #tempТаблицю простіше бачити, проте, як OBJECT_ID('tempdb..#T')це можна використовувати для введення в системні таблиці, і внутрішньо створене ім'я більш тісно співвідноситься з іменем, визначеним у CREATE TABLEвиписці. Для змінних таблиці object_idфункція не працює, і внутрішнє ім'я повністю генерується системою, не має відношення до імені змінної. Нижче показано, що метадані все ще є, проте ввівши назву стовпця (сподіваємось, унікального). Для таблиць без унікальних імен стовпців object_id можна визначити, використовуючи DBCC PAGE, доки вони не порожні.

/*Declare a table variable with some unusual options.*/
DECLARE @T TABLE
(
[dba.se] INT IDENTITY PRIMARY KEY NONCLUSTERED,
A INT CHECK (A > 0),
B INT DEFAULT 1,
InRowFiller char(1000) DEFAULT REPLICATE('A',1000),
OffRowFiller varchar(8000) DEFAULT REPLICATE('B',8000),
LOBFiller varchar(max) DEFAULT REPLICATE(cast('C' as varchar(max)),10000),
UNIQUE CLUSTERED (A,B) 
    WITH (FILLFACTOR = 80, 
         IGNORE_DUP_KEY = ON, 
         DATA_COMPRESSION = PAGE, 
         ALLOW_ROW_LOCKS=ON, 
         ALLOW_PAGE_LOCKS=ON)
)

INSERT INTO @T (A)
VALUES (1),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13)

SELECT t.object_id,
       t.name,
       p.rows,
       a.type_desc,
       a.total_pages,
       a.used_pages,
       a.data_pages,
       p.data_compression_desc
FROM   tempdb.sys.partitions AS p
       INNER JOIN tempdb.sys.system_internals_allocation_units AS a
         ON p.hobt_id = a.container_id
       INNER JOIN tempdb.sys.tables AS t
         ON t.object_id = p.object_id
       INNER JOIN tempdb.sys.columns AS c
         ON c.object_id = p.object_id
WHERE  c.name = 'dba.se'

Вихідні дані

Duplicate key was ignored.

 +-----------+-----------+------+-------------------+-------------+------------+------------+-----------------------+
| object_id |   name    | rows |     type_desc     | total_pages | used_pages | data_pages | data_compression_desc |
+-----------+-----------+------+-------------------+-------------+------------+------------+-----------------------+
| 574625090 | #22401542 |   13 | IN_ROW_DATA       |           2 |          2 |          1 | PAGE                  |
| 574625090 | #22401542 |   13 | LOB_DATA          |          24 |         19 |          0 | PAGE                  |
| 574625090 | #22401542 |   13 | ROW_OVERFLOW_DATA |          16 |         14 |          0 | PAGE                  |
| 574625090 | #22401542 |   13 | IN_ROW_DATA       |           2 |          2 |          1 | NONE                  |
+-----------+-----------+------+-------------------+-------------+------------+------------+-----------------------+

Операції

Операції над @table_variablesздійснюються як системні транзакції, незалежні від будь-яких транзакцій із зовнішнім користувачем, тоді як еквівалентні #tempоперації з таблицею здійснюватимуться як частина самої транзакції користувача. З цієї причини ROLLBACKкоманда вплине на #tempтаблицю, але залишить @table_variableнедоторканою.

DECLARE @T TABLE(X INT)
CREATE TABLE #T(X INT)

BEGIN TRAN

INSERT #T
OUTPUT INSERTED.X INTO @T
VALUES(1),(2),(3)

/*Both have 3 rows*/
SELECT * FROM #T
SELECT * FROM @T

ROLLBACK

/*Only table variable now has rows*/
SELECT * FROM #T
SELECT * FROM @T
DROP TABLE #T

Ведення журналів

Обидва генерують записи журналу до tempdbжурналу транзакцій. Поширене помилкове уявлення про те, що це не стосується змінних таблиць, тому сценарій, що демонструє це нижче, він оголошує змінну таблиці, додає пару рядків, потім оновлює їх і видаляє.

Оскільки таблична змінна створюється та випадає неявно на початку та в кінці партії, необхідно використовувати кілька партій, щоб побачити повний журнал.

USE tempdb;

/*
Don't run this on a busy server.
Ideally should be no concurrent activity at all
*/
CHECKPOINT;

GO

/*
The 2nd column is binary to allow easier correlation with log output shown later*/
DECLARE @T TABLE ([C71ACF0B-47E9-4CAD-9A1E-0C687A8F9CF3] INT, B BINARY(10))

INSERT INTO @T
VALUES (1, 0x41414141414141414141), 
       (2, 0x41414141414141414141)

UPDATE @T
SET    B = 0x42424242424242424242

DELETE FROM @T

/*Put allocation_unit_id into CONTEXT_INFO to access in next batch*/
DECLARE @allocId BIGINT, @Context_Info VARBINARY(128)

SELECT @Context_Info = allocation_unit_id,
       @allocId = a.allocation_unit_id 
FROM   sys.system_internals_allocation_units a
       INNER JOIN sys.partitions p
         ON p.hobt_id = a.container_id
       INNER JOIN sys.columns c
         ON c.object_id = p.object_id
WHERE  ( c.name = 'C71ACF0B-47E9-4CAD-9A1E-0C687A8F9CF3' )

SET CONTEXT_INFO @Context_Info

/*Check log for records related to modifications of table variable itself*/
SELECT Operation,
       Context,
       AllocUnitName,
       [RowLog Contents 0],
       [Log Record Length]
FROM   fn_dblog(NULL, NULL)
WHERE  AllocUnitId = @allocId

GO

/*Check total log usage including updates against system tables*/
DECLARE @allocId BIGINT = CAST(CONTEXT_INFO() AS BINARY(8));

WITH T
     AS (SELECT Operation,
                Context,
                CASE
                  WHEN AllocUnitId = @allocId THEN 'Table Variable'
                  WHEN AllocUnitName LIKE 'sys.%' THEN 'System Base Table'
                  ELSE AllocUnitName
                END AS AllocUnitName,
                [Log Record Length]
         FROM   fn_dblog(NULL, NULL) AS D)
SELECT Operation = CASE
                     WHEN GROUPING(Operation) = 1 THEN 'Total'
                     ELSE Operation
                   END,
       Context,
       AllocUnitName,
       [Size in Bytes] = COALESCE(SUM([Log Record Length]), 0),
       Cnt = COUNT(*)
FROM   T
GROUP  BY GROUPING SETS( ( Operation, Context, AllocUnitName ), ( ) )
ORDER  BY GROUPING(Operation),
          AllocUnitName 

Повертається

Детальний вигляд

Скріншот результатів

Зведений перегляд (включає журнал для неявного падіння та базових таблиць системи)

Скріншот результатів

Наскільки мені вдалося помітити операції на обох, генерують приблизно однакові кількості журналів.

Хоча кількість журналів дуже схожа, одна важлива відмінність полягає в тому, що записи журналів, пов'язані з #tempтаблицями, не можна очистити до тих пір, поки будь-яка містить транзакцію користувача не завершиться, настільки тривала операція, що в якийсь момент записується в #tempтаблиці, не запобіжить усіченню журналу, tempdbтоді як автономні транзакції породили для змінних таблиці не.

Змінні таблиці не підтримують, TRUNCATEтому вони можуть бути в невигідному режимі ведення журналу, коли вимога полягає у видаленні всіх рядків із таблиці (хоча для дуже маленьких таблиць DELETE все одно може бути краще )

Кардинальність

У багатьох планах виконання, що включають змінні таблиці, буде показано один рядок, оцінений як результат з них. Перевірка змінних властивостей таблиці показує , що SQL Server вважає , що таблиця змінні мають нульові рядки (чому він оцінює 1 рядок буде викидатися з таблиці нульовий рядки пояснюється @Paul White тут ).

Однак результати, показані в попередньому розділі, показують точний rowsпідрахунок sys.partitions. Проблема полягає в тому, що в більшості випадків заяви, що посилаються на змінні таблиці, складаються, коли таблиця порожня. Якщо оператор (повторно) компілюється після того, @table_variableяк заповнюється, то це буде використовуватись замість кардинальності таблиці (Це може статися через явний recompileабо, можливо, тому, що в операторі також посилається на інший об'єкт, який викликає відкладену компіляцію або перекомпіляцію.)

DECLARE @T TABLE(I INT);

INSERT INTO @T VALUES(1),(2),(3),(4),(5)

CREATE TABLE #T(I INT)

/*Reference to #T means this statement is subject to deferred compile*/
SELECT * FROM @T WHERE NOT EXISTS(SELECT * FROM #T)

DROP TABLE #T

План показує точну оціночну кількість рядків після відкладеної компіляції.

Показує точну кількість рядків

У SQL Server 2012 SP2 введено прапор сліду 2453. Більш детальну інформацію ви можете знайти у розділі "Реляційний двигун" тут .

Якщо цей прапор слідів увімкнено, він може спричинити автоматичні перекомпіляції, щоб врахувати змінену кардинальність, про що йшлося далі дуже скоро.

Примітка: На Azure на рівні сумісності 150 компіляція оператора відкладена до першого виконання . Це означає, що вона більше не буде піддаватися проблемі оцінки нульового ряду.

Немає статистики стовпців

Наявність більш точної кардинальності таблиці не означає, що оцінене число рядків буде більш точним, однак (якщо не робити операцію з усіма рядками таблиці). SQL Server взагалі не підтримує статистику стовпців для змінних таблиць, тому повернеться до здогадок на основі предиката порівняння (наприклад, 10% таблиці буде повернуто =проти не унікального стовпця або 30% для >порівняння). На відміну від статистики стовпців будуть підтримуватися для #tempтаблиць.

SQL Server підтримує кількість змін, внесених до кожного стовпця. Якщо кількість змін з моменту складання плану перевищує поріг рекомпіляції (RT), план буде перекомпільовано та статистика оновлена. RT залежить від типу та розміру таблиці.

Від кешування плану в SQL Server 2008

RT розраховується наступним чином. (n посилається на кардинальність таблиці, коли складається план запитів.)

Постійна таблиця
- Якщо n <= 500, RT = 500.
- Якщо n> 500, RT = 500 + 0,20 * n.

Тимчасова таблиця
- Якщо n <6, RT = 6.
- Якщо 6 <= n <= 500, RT = 500.
- Якщо n> 500, RT = 500 + 0,20 * n.
Таблична змінна
- RT не існує. Тому рекомпіляції не відбуваються через зміни кардинальності табличних змінних. (Але див. Примітку про TF 2453 нижче)

KEEP PLANнатяк може бути використаний для установки RT для #tempтаблиць таких же , як для постійних таблиць.

Чистий ефект всього цього полягає в тому, що часто плани виконання, створені для #tempтаблиць, на порядок краще, ніж для @table_variablesтих випадків, коли задіяно багато рядків, оскільки SQL Server має кращу інформацію для роботи.

NB1: Змінні таблиці не мають статистичних даних, але все ще можуть мати місце події перекомпіляції "Статистика змінена" під прапором сліду 2453 (не застосовується для "тривіальних" планів). додатковий, що якщо N=0 -> RT = 1. тобто всі заяви, складені, коли змінна таблиця порожня, в кінцевому підсумку отримують рекомпіляцію та виправляють TableCardinalityперший раз, коли вони виконуються, коли не порожні. Кардинальність таблиці складання зберігається в плані, і якщо оператор знову виконується з тією ж кардинальністю (або через потоки операторів керування, або повторного використання кешованого плану), повторна компіляція не відбувається.

NB2: Для кешованих тимчасових таблиць у збережених процедурах історія перекомпіляції набагато складніше, ніж описано вище. Дивіться тимчасові таблиці в Збережених процедурах для всіх деталей горіхів.

Переклади

Як і перекомпіляції на основі модифікацій, описані вище, #tempтаблиці також можуть бути пов’язані з додатковими компіляціями просто тому, що вони дозволяють виконувати операції, заборонені для змінних таблиць, які викликають компіляцію (наприклад, зміни DDL CREATE INDEX, ALTER TABLE)

Блокування

Було заявлено, що змінні таблиці не беруть участі в блокуванні. Це не так. Запустивши нижче виходи на вкладку Повідомлення SSMS, дані про блокування, зняті та випущені для оператора вставлення.

DECLARE @tv_target TABLE (c11 int, c22 char(100))

DBCC TRACEON(1200,-1,3604)

INSERT INTO @tv_target (c11, c22)

VALUES (1, REPLICATE('A',100)), (2, REPLICATE('A',100))

DBCC TRACEOFF(1200,-1,3604)

Для запитів SELECTіз табличних змінних Пол Уайт в коментарях зазначає, що вони автоматично надходять із неявним NOLOCKпідказкою. Це показано нижче

DECLARE @T TABLE(X INT); 

SELECT X
FROM @T 
OPTION (RECOMPILE, QUERYTRACEON 3604, QUERYTRACEON 8607)

Вихідні дані

*** Output Tree: (trivial plan) ***

        PhyOp_TableScan TBL: @T Bmk ( Bmk1000) IsRow: COL: IsBaseRow1002  Hints( NOLOCK )

Однак вплив цього на фіксацію може бути незначним.

SET NOCOUNT ON;

CREATE TABLE #T( [ID] [int] IDENTITY NOT NULL,
                 [Filler] [char](8000) NULL,
                 PRIMARY KEY CLUSTERED ([ID] DESC))


DECLARE @T TABLE ( [ID] [int] IDENTITY NOT NULL,
                 [Filler] [char](8000) NULL,
                 PRIMARY KEY CLUSTERED ([ID] DESC))

DECLARE @I INT = 0

WHILE (@I < 10000)
BEGIN
INSERT INTO #T DEFAULT VALUES
INSERT INTO @T DEFAULT VALUES
SET @I += 1
END

/*Run once so compilation output doesn't appear in lock output*/
EXEC('SELECT *, sys.fn_PhysLocFormatter(%%physloc%%) FROM #T')

DBCC TRACEON(1200,3604,-1)
SELECT *, sys.fn_PhysLocFormatter(%%physloc%%)
FROM @T

PRINT '--*--'

EXEC('SELECT *, sys.fn_PhysLocFormatter(%%physloc%%) FROM #T')

DBCC TRACEOFF(1200,3604,-1)

DROP TABLE #T

Жоден із цих повернень не призводить до порядку індексного ключа, що вказує на те, що SQL Server використовував сканований розподілом сканування для обох.

Я запускав вищезазначений сценарій двічі, а результати другого запуску нижче

Process 58 acquiring Sch-S lock on OBJECT: 2:-1325894110:0  (class bit0 ref1) result: OK

--*--
Process 58 acquiring IS lock on OBJECT: 2:-1293893996:0  (class bit0 ref1) result: OK

Process 58 acquiring S lock on OBJECT: 2:-1293893996:0  (class bit0 ref1) result: OK

Process 58 releasing lock on OBJECT: 2:-1293893996:0 

Вихід блокування для змінної таблиці дійсно надзвичайно мінімальний, оскільки SQL Server просто отримує блокування стабільності схеми на об'єкті. Але для #tempстолу він майже такий же легкий, що він виймає Sблокування рівня об'єкта . Звичайно, NOLOCKпідказка або READ UNCOMMITTEDрівень ізоляції можна чітко вказати і під час роботи з #tempтаблицями.

Аналогічно проблемі з реєстрацією транзакції оточуючого користувача може означати, що блоки зберігаються довше для #tempтаблиць. Зі сценарієм нижче

    --BEGIN TRAN;   

    CREATE TABLE #T (X INT,Y CHAR(4000) NULL);

    INSERT INTO #T (X) VALUES(1) 

    SELECT CASE resource_type
             WHEN  'OBJECT' THEN OBJECT_NAME(resource_associated_entity_id, 2)
             WHEN  'ALLOCATION_UNIT' THEN (SELECT OBJECT_NAME(object_id, 2)
                                           FROM  tempdb.sys.allocation_units a 
                                           JOIN tempdb.sys.partitions p ON a.container_id = p.hobt_id
                                           WHERE  a.allocation_unit_id = resource_associated_entity_id)
             WHEN 'DATABASE' THEN DB_NAME(resource_database_id)                                      
             ELSE (SELECT OBJECT_NAME(object_id, 2)
                   FROM   tempdb.sys.partitions
                   WHERE  partition_id = resource_associated_entity_id)
           END AS object_name,
           *
    FROM   sys.dm_tran_locks
    WHERE  request_session_id = @@SPID

    DROP TABLE #T

   -- ROLLBACK  

при запуску поза явною транзакцією користувача для обох випадків єдиним блокуванням, що повертається при перевірці, sys.dm_tran_locksє спільний замок на DATABASE.

Після коментарів BEGIN TRAN ... ROLLBACK26 рядків повертаються, показуючи, що блокування проводяться як на самому об'єкті, так і на рядках системної таблиці, щоб дозволити відкат і запобігти іншим транзакціям зчитування недозволених даних. Еквівалентна операція змінної таблиці не підлягає відскоку з трансакцією користувача та не має потреби утримувати ці блокування для нас, щоб перевірити наступне висловлення, але відстеження замків, придбаних та випущених у Profiler, або використання прапора трассингу 1200, показує, що ще багато подій блокування роблять досі трапляються.

Покажчики

Для версій до SQL Server 2014 індекси можна створювати лише неявно на змінних таблиці як побічний ефект додавання унікального обмеження або первинного ключа. Це, звичайно, означає, що підтримуються лише унікальні індекси. Не унікальний некластеризований індекс на таблиці з унікальним кластерним індексом можна моделювати, однак просто оголосивши його UNIQUE NONCLUSTEREDта додавши ключ CI до кінця потрібного ключа NCI (SQL Server все одно зробив би це за кадром, навіть якщо не унікальний NCI можна вказати)

Як було показано раніше, різні index_options можуть бути вказані в декларації обмежень, включаючи DATA_COMPRESSION, IGNORE_DUP_KEYі FILLFACTOR(хоча немає сенсу встановлювати це, оскільки це може змінити лише відновлення індексу, і ви не можете перебудовувати індекси на змінних таблиці!)

Додатково змінні таблиці не підтримують INCLUDEd стовпців, відфільтровані індекси (до 2016 року) або розділення, як #tempце робиться в таблицях (схема розділу повинна бути створена в tempdb).

Індекси в SQL Server 2014

Не унікальні індекси можуть бути оголошені вбудованими у визначенні змінної таблиці в SQL Server 2014. Приклад синтаксису для цього наведено нижче.

DECLARE @T TABLE (
C1 INT INDEX IX1 CLUSTERED, /*Single column indexes can be declared next to the column*/
C2 INT INDEX IX2 NONCLUSTERED,
       INDEX IX3 NONCLUSTERED(C1,C2) /*Example composite index*/
);

Індекси в SQL Server 2016

З CTP 3.1 тепер можна оголосити відфільтровані індекси для змінних таблиці. За допомогою RTM можливо, що включені стовпці також дозволені, хоча вони , ймовірно, не перетворять його в SQL16 через обмеження ресурсів.

DECLARE @T TABLE
(
c1 INT NULL INDEX ix UNIQUE WHERE c1 IS NOT NULL /*Unique ignoring nulls*/
)

Паралелізм

Запити, які вставляються у (або іншим чином змінюються), @table_variablesне можуть мати паралельний план, #temp_tablesне обмежуються таким чином.

У цьому переписуванні є очевидне вирішення, як це дає змогу SELECTдеталі проходити паралельно, але це закінчується використанням прихованої тимчасової таблиці (за лаштунками)

INSERT INTO @DATA ( ... ) 
EXEC('SELECT .. FROM ...')

Не існує такого обмеження в запитах, які вибирають із змінних таблиць, як показано в моїй відповіді тут

Інші функціональні відмінності

  • #temp_tablesне можна використовувати всередині функції. @table_variablesможе використовуватися всередині скалярних UDF скалярних або багатосказальних таблиць.
  • @table_variables не міг назвати обмеження.
  • @table_variablesне може бути SELECT-ed INTO, ALTER-ed, TRUNCATEd або бути ціллю DBCCкоманд, таких як DBCC CHECKIDENTабо, SET IDENTITY INSERTі не підтримувати підказки таблиці, наприкладWITH (FORCESCAN)
  • CHECK обмеження щодо змінних таблиць оптимізатором не враховуються для спрощення, маються на увазі предикати або виявлення суперечностей.
  • Змінні таблиці, схоже, не підходять для оптимізації спільного набору рядків, тобто видалення та оновлення планів проти них може мати більше накладних витрат і PAGELATCH_EXочікування. ( Приклад )

Пам'ять лише?

Як зазначено на початку, обидва зберігаються на сторінках у tempdb. Однак я не звертався до того, чи була якась різниця в поведінці, коли справа стосується написання цих сторінок на диск.

Я зробив невелику кількість тестувань на цьому зараз, і поки що не бачив такої різниці. У конкретному тесті, який я робив на моєму екземплярі SQL Server, 250 сторінок здається точкою відсічення до того, як файл даних буде записаний.

Примітка: Поведінка нижче, більше не зустрічається в SQL Server 2014 або SQL Server 2012 SP1 / CU10 або SP2 / CU1, прагне письменника вже не так хочеться писати сторінки на диск. Детальніше про цю зміну на SQL Server 2014: tempdb Hemden Performance Gem .

Запуск сценарію нижче

CREATE TABLE #T(X INT, Filler char(8000) NULL)
INSERT INTO #T(X)
SELECT TOP 250 ROW_NUMBER() OVER (ORDER BY @@SPID)
FROM master..spt_values
DROP TABLE #T

І моніторинг записує у tempdbфайл даних за допомогою Монітора процесів, я не бачив жодного (за винятком випадків, коли вони завантажуються на сторінку завантаження бази даних зі зміщенням 73 728). Після переходу 250на 251почав бачити, як пише нижче.

ProcMon

На знімку екрана показано запис 5 * 32 сторінок і один запис на одній сторінці, який вказує на те, що 161 сторінок було записано на диск. Я отримав однакову точку відрізу в 250 сторінок під час тестування із змінними таблиці. Сценарій нижче показує це по-іншому, дивлячисьsys.dm_os_buffer_descriptors

DECLARE @T TABLE (
  X        INT,
  [dba.se] CHAR(8000) NULL)

INSERT INTO @T
            (X)
SELECT TOP 251 Row_number() OVER (ORDER BY (SELECT 0))
FROM   master..spt_values

SELECT is_modified,
       Count(*) AS page_count
FROM   sys.dm_os_buffer_descriptors
WHERE  database_id = 2
       AND allocation_unit_id = (SELECT a.allocation_unit_id
                                 FROM   tempdb.sys.partitions AS p
                               INNER JOIN tempdb.sys.system_internals_allocation_units AS a
                                          ON p.hobt_id = a.container_id
                                        INNER JOIN tempdb.sys.columns AS c
                                          ON c.object_id = p.object_id
                                 WHERE  c.name = 'dba.se')
GROUP  BY is_modified 

Результати

is_modified page_count
----------- -----------
0           192
1           61

Показано, що на диску було написано 192 сторінки, а брудний прапор очищено. Це також показує, що написання на диск не означає, що сторінки будуть вилучені з буферного пулу негайно. Запити щодо цієї змінної таблиці все ще можна задовольнити повністю з пам'яті.

На холостому сервері з max server memoryналаштованими 2000 MBта DBCC MEMORYSTATUSзвітнішими сторінками буферного пулу, розміщеними приблизно 1843 000 Кб (приблизно 23 000 сторінок), я вставив до таблиць вище в партіях по 1000 рядків / сторінок і за кожну записану ітерацію.

SELECT Count(*)
FROM   sys.dm_os_buffer_descriptors
WHERE  database_id = 2
       AND allocation_unit_id = @allocId
       AND page_type = 'DATA_PAGE' 

І змінна таблиця, і #tempтаблиця давали майже однакові графіки і встигли значно збільшити максимум буферного пулу, перш ніж дійти до того, що вони не були повністю збережені в пам’яті, тому, схоже, немає особливого обмеження в кількості пам'яті або можна споживати.

Сторінки в буферному басейні


Я виявив, що SQL Server отримує значно більше засувок під час створення темп-таблиць (навіть із кешуванням) порівняно з еквівалентною змінною таблиці. Ви можете перевірити, скориставшись налагодженням XE latch_acquired та створивши таблицю з приблизно 35 стовпцями. Я знайшов змінну таблиці, щоб взяти 4 засувки, а таблицю темп - десь близько 70 засувок.
Джо Оббіш

40

Я хотів би зазначити кілька речей, що базуються більше на конкретному досвіді, а не на навчанні. Як DBA, я дуже новачок, тому, будь ласка, виправте мене там, де потрібно.

  1. У таблицях #temp за замовчуванням використовується порівняння за замовчуванням екземпляра SQL Server. Тому, якщо не вказано інше, у вас можуть виникнути проблеми зі порівнянням або оновленням значень між #temp таблицями та таблицями баз даних, якщо masterdb відрізняється від бази даних. Дивіться: http://www.mssqltips.com/sqlservertip/2440/create-sql-server-temporary-tables-with-the-correct-collation/
  2. Цілком базуючись на особистому досвіді, наявна пам'ять впливає на ефективнішу роботу. MSDN рекомендує використовувати змінні таблиці для зберігання менших наборів результатів, але більшу частину часу різниця навіть не помітна. Однак у більших наборах у деяких випадках стає очевидним, що змінні таблиці набагато більші в обсязі пам’яті і можуть сповільнити запит до сканування.

6
Також зауважте, що зіставлення в таблицях #temp може успадкувати зіставлення бази даних викликів, якщо ви використовуєте SQL Server 2012 і база даних міститься.
Аарон Бертран

Роз'яснення на # 2 для малих проти великих наборів stackoverflow.com/a/14465163/5224021
GibralterTop
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.