В чем разница между временной таблицей и табличной переменной в SQL Server?


390

В SQL Server 2005 мы можем создавать временные таблицы одним из двух способов:

declare @tmp table (Col1 int, Col2 int);

или

create table #tmp (Col1 int, Col2 int);

Каковы различия между этими двумя? Я прочитал противоречивые мнения о том, использует ли @tmp все еще tempdb, или все происходит в памяти.

В каких случаях один превосходит другой?



2
Пинал Дэйв написал
sam yi

Ответы:


392

Между временными таблицами (#tmp) и переменными таблиц (@tmp) есть несколько отличий, хотя использование tempdb не является одним из них, как указано в ссылке MSDN ниже.

Как правило, для небольших и средних объемов данных и простых сценариев использования следует использовать табличные переменные. (Это слишком широкое руководство, конечно, с множеством исключений - см. Ниже и в следующих статьях.)

Некоторые моменты, которые следует учитывать при выборе между ними:

  • Временные таблицы являются реальными таблицами, поэтому вы можете делать такие вещи, как CREATE INDEXes и т. Д. Если у вас есть большие объемы данных, для которых доступ по индексу будет быстрее, тогда временные таблицы - хороший вариант.

  • Табличные переменные могут иметь индексы с помощью ограничений PRIMARY KEY или UNIQUE. (Если вам нужен неуникальный индекс, просто включите столбец первичного ключа в качестве последнего столбца в ограничении уникальности. Если у вас нет уникального столбца, вы можете использовать столбец идентификаторов.) В SQL 2014 также есть неуникальные индексы ,

  • Табличные переменные не участвуют в транзакциях, а SELECTs неявно с NOLOCK. Поведение транзакции может быть очень полезным, например, если вы хотите выполнить ROLLBACK в середине процедуры, табличные переменные, заполненные во время этой транзакции, будут по-прежнему заполнены!

  • Временные таблицы могут привести к перекомпиляции хранимых процедур, возможно, часто. Табличные переменные не будут.

  • Вы можете создать временную таблицу с помощью SELECT INTO, которая может быть быстрее написана (хорошо для специальных запросов) и может позволить вам иметь дело с изменением типов данных с течением времени, так как вам не нужно заранее определять структуру временной таблицы.

  • Вы можете передавать табличные переменные обратно из функций, что позволяет вам гораздо проще инкапсулировать и повторно использовать логику (например, сделать функцию для разбиения строки в таблицу значений на некотором произвольном разделителе).

  • Использование табличных переменных в пользовательских функциях позволяет более широко использовать эти функции (подробнее см. Документацию CREATE FUNCTION). Если вы пишете функцию, вы должны использовать табличные переменные над временными таблицами, если нет острой необходимости.

  • Табличные переменные и временные таблицы хранятся в базе данных tempdb. Но для табличных переменных (с 2005 года) по умолчанию используется сопоставление текущей базы данных по сравнению с временными таблицами, которые принимают сопоставление по умолчанию для базы данных tempdb ( ref ). Это означает, что вы должны знать о проблемах сопоставления, если вы используете временные таблицы и ваша база данных отличается от базы данных tempdb, что создает проблемы, если вы хотите сравнить данные в временной таблице с данными в вашей базе данных.

  • Глобальные временные таблицы (## tmp) - это еще один тип временных таблиц, доступных для всех сеансов и пользователей.

Некоторое дальнейшее чтение:


26
Табличные переменные могут иметь индексы. Просто создайте уникальное ограничение, и вы автоматически получите индекс. Делает огромную разницу в производительности. (Если вам не нужен уникальный индекс, просто добавьте фактический первичный ключ в конец нужных полей. Если у вас его нет, создайте столбец идентификаторов).
Бен

7
@Ben И SQL Server 2014 позволяет указывать
Мартин Смит,

4
Табличные переменные, на которые не влияют транзакции, иногда удобны. Если у вас есть что-то, что вы хотите сохранить после отката, вы можете поместить это в табличную переменную.
Quillbreaker

3
Статистика создается для временных таблиц, что может улучшить планы запросов, но не для табличных переменных. Эта статистика кэшируется некоторое время вместе со страницами временной таблицы после удаления временной таблицы и может быть неточной, если кешированная таблица будет повторно активирована.
Майкл Грин,

Переменные таблицы по умолчанию будут использовать параметры сортировки определенного пользователем типа данных (если столбец имеет пользовательский тип данных) или параметры сортировки текущей базы данных, а не параметры сортировки по умолчанию для базы данных tempdb. В временных таблицах будет использоваться сопоставление по умолчанию для базы данных tempdb. См .: technet.microsoft.com/en-us/library/ms188927.aspx
PseudoToad

25

Просто посмотрите на утверждение в принятом ответе, что переменные таблицы не участвуют в ведении журнала.

Как правило, не соответствует действительности, что есть какое-либо различие в количестве журналирования (по крайней мере для insert/ update/ deleteопераций к самой таблице, хотя я с тех пор нашел что существует небольшая разница в этом отношении для кэшированных временных объектов в хранимых процедурах из-за дополнительной системной таблицы обновления).

Я посмотрел на поведение журналирования как @table_variableдля #tempтаблицы a, так и для следующих операций.

  1. Успешная вставка
  2. Multi Row Вставить, где оператор откатился из-за нарушения ограничения.
  3. Обновить
  4. удалять
  5. Освобождает

Записи журнала транзакций были практически идентичны для всех операций.

Версия табличной переменной на самом деле имеет несколько дополнительных записей журнала, потому что она получает запись, добавляемую (а затем удаляемую) из sys.syssingleobjrefsбазовой таблицы, но в целом было зарегистрировано лишь несколько меньших байтов, поскольку внутреннее имя для переменных таблицы потребляет на 236 меньше байтов, чем для #tempтаблиц. (118 nvarcharсимволов меньше ).

Полный сценарий для воспроизведения (лучший запуск на экземпляре, запущенном в однопользовательском режиме и sqlcmdрежиме использования )

:setvar tablename "@T" 
:setvar tablescript "DECLARE @T TABLE"

/*
 --Uncomment this section to test a #temp table
:setvar tablename "#T" 
:setvar tablescript "CREATE TABLE #T"
*/

USE tempdb 
GO    
CHECKPOINT

DECLARE @LSN NVARCHAR(25)

SELECT @LSN = MAX([Current LSN])
FROM fn_dblog(null, null) 


EXEC(N'BEGIN TRAN StartBatch
SAVE TRAN StartBatch
COMMIT

$(tablescript)
(
[4CA996AC-C7E1-48B5-B48A-E721E7A435F0] INT PRIMARY KEY DEFAULT 0,
InRowFiller char(7000) DEFAULT ''A'',
OffRowFiller varchar(8000) DEFAULT REPLICATE(''B'',8000),
LOBFiller varchar(max) DEFAULT REPLICATE(cast(''C'' as varchar(max)),10000)
)


BEGIN TRAN InsertFirstRow
SAVE TRAN InsertFirstRow
COMMIT

INSERT INTO $(tablename)
DEFAULT VALUES

BEGIN TRAN Insert9Rows
SAVE TRAN Insert9Rows
COMMIT


INSERT INTO $(tablename) ([4CA996AC-C7E1-48B5-B48A-E721E7A435F0])
SELECT TOP 9 ROW_NUMBER() OVER (ORDER BY (SELECT 0))
FROM sys.all_columns

BEGIN TRAN InsertFailure
SAVE TRAN InsertFailure
COMMIT


/*Try and Insert 10 rows, the 10th one will cause a constraint violation*/
BEGIN TRY
INSERT INTO $(tablename) ([4CA996AC-C7E1-48B5-B48A-E721E7A435F0])
SELECT TOP (10) (10 + ROW_NUMBER() OVER (ORDER BY (SELECT 0))) % 20
FROM sys.all_columns
END TRY
BEGIN CATCH
PRINT ERROR_MESSAGE()
END CATCH

BEGIN TRAN Update10Rows
SAVE TRAN Update10Rows
COMMIT

UPDATE $(tablename)
SET InRowFiller = LOWER(InRowFiller),
    OffRowFiller  =LOWER(OffRowFiller),
    LOBFiller  =LOWER(LOBFiller)


BEGIN TRAN Delete10Rows
SAVE TRAN Delete10Rows
COMMIT

DELETE FROM  $(tablename)
BEGIN TRAN AfterDelete
SAVE TRAN AfterDelete
COMMIT

BEGIN TRAN EndBatch
SAVE TRAN EndBatch
COMMIT')


DECLARE @LSN_HEX NVARCHAR(25) = 
        CAST(CAST(CONVERT(varbinary,SUBSTRING(@LSN, 1, 8),2) AS INT) AS VARCHAR) + ':' +
        CAST(CAST(CONVERT(varbinary,SUBSTRING(@LSN, 10, 8),2) AS INT) AS VARCHAR) + ':' +
        CAST(CAST(CONVERT(varbinary,SUBSTRING(@LSN, 19, 4),2) AS INT) AS VARCHAR)        

SELECT 
    [Operation],
    [Context],
    [AllocUnitName],
    [Transaction Name],
    [Description]
FROM   fn_dblog(@LSN_HEX, null) AS D
WHERE  [Current LSN] > @LSN  

SELECT CASE
         WHEN GROUPING(Operation) = 1 THEN 'Total'
         ELSE Operation
       END AS Operation,
       Context,
       AllocUnitName,
       COALESCE(SUM([Log Record Length]), 0) AS [Size in Bytes],
       COUNT(*)                              AS Cnt
FROM   fn_dblog(@LSN_HEX, null) AS D
WHERE  [Current LSN] > @LSN  
GROUP BY GROUPING SETS((Operation, Context, AllocUnitName),())

Результаты

+-----------------------+--------------------+---------------------------+---------------+------+---------------+------+------------------+
|                       |                    |                           |             @TV      |             #TV      |                  |
+-----------------------+--------------------+---------------------------+---------------+------+---------------+------+------------------+
| Operation             | Context            | AllocUnitName             | Size in Bytes | Cnt  | Size in Bytes | Cnt  | Difference Bytes |
+-----------------------+--------------------+---------------------------+---------------+------+---------------+------+------------------+
| LOP_ABORT_XACT        | LCX_NULL           |                           | 52            | 1    | 52            | 1    |                  |
| LOP_BEGIN_XACT        | LCX_NULL           |                           | 6056          | 50   | 6056          | 50   |                  |
| LOP_COMMIT_XACT       | LCX_NULL           |                           | 2548          | 49   | 2548          | 49   |                  |
| LOP_COUNT_DELTA       | LCX_CLUSTERED      | sys.sysallocunits.clust   | 624           | 3    | 624           | 3    |                  |
| LOP_COUNT_DELTA       | LCX_CLUSTERED      | sys.sysrowsets.clust      | 208           | 1    | 208           | 1    |                  |
| LOP_COUNT_DELTA       | LCX_CLUSTERED      | sys.sysrscols.clst        | 832           | 4    | 832           | 4    |                  |
| LOP_CREATE_ALLOCCHAIN | LCX_NULL           |                           | 120           | 3    | 120           | 3    |                  |
| LOP_DELETE_ROWS       | LCX_INDEX_INTERIOR | Unknown Alloc Unit        | 720           | 9    | 720           | 9    |                  |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.sysallocunits.clust   | 444           | 3    | 444           | 3    |                  |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.sysallocunits.nc      | 276           | 3    | 276           | 3    |                  |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.syscolpars.clst       | 628           | 4    | 628           | 4    |                  |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.syscolpars.nc         | 484           | 4    | 484           | 4    |                  |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.sysidxstats.clst      | 176           | 1    | 176           | 1    |                  |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.sysidxstats.nc        | 144           | 1    | 144           | 1    |                  |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.sysiscols.clst        | 100           | 1    | 100           | 1    |                  |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.sysiscols.nc1         | 88            | 1    | 88            | 1    |                  |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.sysobjvalues.clst     | 596           | 5    | 596           | 5    |                  |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.sysrowsets.clust      | 132           | 1    | 132           | 1    |                  |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.sysrscols.clst        | 528           | 4    | 528           | 4    |                  |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.sysschobjs.clst       | 1040          | 6    | 1276          | 6    | 236              |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.sysschobjs.nc1        | 820           | 6    | 1060          | 6    | 240              |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.sysschobjs.nc2        | 820           | 6    | 1060          | 6    | 240              |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.sysschobjs.nc3        | 480           | 6    | 480           | 6    |                  |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.syssingleobjrefs.clst | 96            | 1    |               |      | -96              |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.syssingleobjrefs.nc1  | 88            | 1    |               |      | -88              |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | Unknown Alloc Unit        | 72092         | 19   | 72092         | 19   |                  |
| LOP_DELETE_ROWS       | LCX_TEXT_MIX       | Unknown Alloc Unit        | 16348         | 37   | 16348         | 37   |                  |
| LOP_FORMAT_PAGE       | LCX_HEAP           | Unknown Alloc Unit        | 1596          | 19   | 1596          | 19   |                  |
| LOP_FORMAT_PAGE       | LCX_IAM            | Unknown Alloc Unit        | 252           | 3    | 252           | 3    |                  |
| LOP_FORMAT_PAGE       | LCX_INDEX_INTERIOR | Unknown Alloc Unit        | 84            | 1    | 84            | 1    |                  |
| LOP_FORMAT_PAGE       | LCX_TEXT_MIX       | Unknown Alloc Unit        | 4788          | 57   | 4788          | 57   |                  |
| LOP_HOBT_DDL          | LCX_NULL           |                           | 108           | 3    | 108           | 3    |                  |
| LOP_HOBT_DELTA        | LCX_NULL           |                           | 9600          | 150  | 9600          | 150  |                  |
| LOP_INSERT_ROWS       | LCX_CLUSTERED      | sys.sysallocunits.clust   | 456           | 3    | 456           | 3    |                  |
| LOP_INSERT_ROWS       | LCX_CLUSTERED      | sys.syscolpars.clst       | 644           | 4    | 644           | 4    |                  |
| LOP_INSERT_ROWS       | LCX_CLUSTERED      | sys.sysidxstats.clst      | 180           | 1    | 180           | 1    |                  |
| LOP_INSERT_ROWS       | LCX_CLUSTERED      | sys.sysiscols.clst        | 104           | 1    | 104           | 1    |                  |
| LOP_INSERT_ROWS       | LCX_CLUSTERED      | sys.sysobjvalues.clst     | 616           | 5    | 616           | 5    |                  |
| LOP_INSERT_ROWS       | LCX_CLUSTERED      | sys.sysrowsets.clust      | 136           | 1    | 136           | 1    |                  |
| LOP_INSERT_ROWS       | LCX_CLUSTERED      | sys.sysrscols.clst        | 544           | 4    | 544           | 4    |                  |
| LOP_INSERT_ROWS       | LCX_CLUSTERED      | sys.sysschobjs.clst       | 1064          | 6    | 1300          | 6    | 236              |
| LOP_INSERT_ROWS       | LCX_CLUSTERED      | sys.syssingleobjrefs.clst | 100           | 1    |               |      | -100             |
| LOP_INSERT_ROWS       | LCX_CLUSTERED      | Unknown Alloc Unit        | 135888        | 19   | 135888        | 19   |                  |
| LOP_INSERT_ROWS       | LCX_INDEX_INTERIOR | Unknown Alloc Unit        | 1596          | 19   | 1596          | 19   |                  |
| LOP_INSERT_ROWS       | LCX_INDEX_LEAF     | sys.sysallocunits.nc      | 288           | 3    | 288           | 3    |                  |
| LOP_INSERT_ROWS       | LCX_INDEX_LEAF     | sys.syscolpars.nc         | 500           | 4    | 500           | 4    |                  |
| LOP_INSERT_ROWS       | LCX_INDEX_LEAF     | sys.sysidxstats.nc        | 148           | 1    | 148           | 1    |                  |
| LOP_INSERT_ROWS       | LCX_INDEX_LEAF     | sys.sysiscols.nc1         | 92            | 1    | 92            | 1    |                  |
| LOP_INSERT_ROWS       | LCX_INDEX_LEAF     | sys.sysschobjs.nc1        | 844           | 6    | 1084          | 6    | 240              |
| LOP_INSERT_ROWS       | LCX_INDEX_LEAF     | sys.sysschobjs.nc2        | 844           | 6    | 1084          | 6    | 240              |
| LOP_INSERT_ROWS       | LCX_INDEX_LEAF     | sys.sysschobjs.nc3        | 504           | 6    | 504           | 6    |                  |
| LOP_INSERT_ROWS       | LCX_INDEX_LEAF     | sys.syssingleobjrefs.nc1  | 92            | 1    |               |      | -92              |
| LOP_INSERT_ROWS       | LCX_TEXT_MIX       | Unknown Alloc Unit        | 5112          | 71   | 5112          | 71   |                  |
| LOP_MARK_SAVEPOINT    | LCX_NULL           |                           | 508           | 8    | 508           | 8    |                  |
| LOP_MODIFY_COLUMNS    | LCX_CLUSTERED      | Unknown Alloc Unit        | 1560          | 10   | 1560          | 10   |                  |
| LOP_MODIFY_HEADER     | LCX_HEAP           | Unknown Alloc Unit        | 3780          | 45   | 3780          | 45   |                  |
| LOP_MODIFY_ROW        | LCX_CLUSTERED      | sys.syscolpars.clst       | 384           | 4    | 384           | 4    |                  |
| LOP_MODIFY_ROW        | LCX_CLUSTERED      | sys.sysidxstats.clst      | 100           | 1    | 100           | 1    |                  |
| LOP_MODIFY_ROW        | LCX_CLUSTERED      | sys.sysrowsets.clust      | 92            | 1    | 92            | 1    |                  |
| LOP_MODIFY_ROW        | LCX_CLUSTERED      | sys.sysschobjs.clst       | 1144          | 13   | 1144          | 13   |                  |
| LOP_MODIFY_ROW        | LCX_IAM            | Unknown Alloc Unit        | 4224          | 48   | 4224          | 48   |                  |
| LOP_MODIFY_ROW        | LCX_PFS            | Unknown Alloc Unit        | 13632         | 169  | 13632         | 169  |                  |
| LOP_MODIFY_ROW        | LCX_TEXT_MIX       | Unknown Alloc Unit        | 108640        | 120  | 108640        | 120  |                  |
| LOP_ROOT_CHANGE       | LCX_CLUSTERED      | sys.sysallocunits.clust   | 960           | 10   | 960           | 10   |                  |
| LOP_SET_BITS          | LCX_GAM            | Unknown Alloc Unit        | 1200          | 20   | 1200          | 20   |                  |
| LOP_SET_BITS          | LCX_IAM            | Unknown Alloc Unit        | 1080          | 18   | 1080          | 18   |                  |
| LOP_SET_BITS          | LCX_SGAM           | Unknown Alloc Unit        | 120           | 2    | 120           | 2    |                  |
| LOP_SHRINK_NOOP       | LCX_NULL           |                           |               |      | 32            | 1    | 32               |
+-----------------------+--------------------+---------------------------+---------------+------+---------------+------+------------------+
| Total                 |                    |                           | 410144        | 1095 | 411232        | 1092 | 1088             |
+-----------------------+--------------------+---------------------------+---------------+------+---------------+------+------------------+

1
+1 Просто из любопытства (и быть немного педантичным). Вопрос был / был довольно старым (август 2008 г.), поэтому речь шла о SQL 2005. Сейчас мы находимся в 2011 году (конец), и последний SQL - 2008 R2 плюс бета-версия Denali. Какую версию вы использовали?
Ксанатос

2
@xanatos - 2008. В 2005 году табличные переменные фактически были бы в невыгодном положении, так как они INSERT ... SELECTне были минимально зарегистрированы, и вы не можете SELECT INTO ... использовать табличную переменную.
Мартин Смит

1
Спасибо @MartinSmith, обновил мой ответ, чтобы убрать претензию о регистрации.
Рори

18

В каких случаях один превосходит другой?

Для небольших таблиц (менее 1000 строк) используйте временную переменную, в противном случае используйте временную таблицу.


17
Какие-либо подтверждающие данные? Это не очень полезно, как само по себе утверждение.
Майкл Майерс

8
Microsoft рекомендует ограничение в 100 строк: msdn.microsoft.com/en-us/library/ms175010.aspx (см. Раздел « Рекомендации »).
Артемикс

Смотрите мой ответ ниже для объяснения.
Вэйхуэй Го

17

@wcm - фактически, чтобы выбрать переменную Table Variable не только Ram - она ​​может быть частично сохранена на диске.

Временная таблица может иметь индексы, тогда как переменная таблицы может иметь только первичный индекс. Если скорость является проблемой, переменные таблицы могут быть быстрее, но, очевидно, если имеется много записей или требуется поиск во временной таблице кластерного индекса, тогда лучше использовать временную таблицу.

Хорошая справочная статья


2
Хорошая справочная статья +1. Я буду удалять свой ответ, так как его изменение не оставит много, и хороших ответов уже так много
wcm 19.10.10

12
  1. Временная таблица: Временную таблицу легко создавать и создавать резервные копии данных.

    Переменная таблицы: но переменная таблицы требует усилий, когда мы обычно создаем обычные таблицы.

  2. Временная таблица: результат временной таблицы может использоваться несколькими пользователями.

    Переменная таблицы: но переменная таблицы может использоваться только текущим пользователем. 

  3. Temp table: временная таблица будет храниться в базе данных tempdb. Это сделает сетевой трафик. Когда у нас есть большие данные во временной таблице, они должны работать по всей базе данных. Проблема с производительностью будет существовать.

    Табличная переменная: Но табличная переменная будет хранить в физической памяти некоторые данные, а затем, когда размер увеличится, она будет перемещена в базу данных tempdb.

  4. Временная таблица: Временная таблица может выполнять все операции DDL. Позволяет создавать индексы, отбрасывать, изменять и т. Д.,

    Табличная переменная: В то время как табличная переменная не позволяет выполнять операции DDL. Но табличная переменная позволяет нам создавать только кластерный индекс.

  5. Временная таблица: Временная таблица может использоваться для текущего сеанса или глобального. Так что многопользовательский сеанс может использовать результаты в таблице.

    Табличная переменная: но табличная переменная может использоваться до этой программы. (Хранимая процедура)

  6. Таблица Temp: переменная Temp не может использовать транзакции. Когда мы выполняем операции DML с временной таблицей, это может быть откат или фиксация транзакций.

    Табличная переменная: Но мы не можем сделать это для табличной переменной.

  7. Таблица Temp: Функции не могут использовать переменную temp. Более того, мы не можем выполнять операции DML в функциях.

    Переменная таблицы: Но функция позволяет нам использовать переменную таблицы. Но используя переменную таблицы, мы можем это сделать.

  8. Таблица Temp: хранимая процедура будет выполнять перекомпиляцию (не может использовать тот же план выполнения), когда мы используем переменную temp для каждого последующего вызова.

    Переменная таблицы: в то время как переменная таблицы не будет так делать.


8

Для всех, кто верит в миф о том, что временные переменные находятся только в памяти

Во-первых, переменная таблицы НЕ обязательно должна быть резидентной. Под давлением памяти страницы, принадлежащие табличной переменной, могут быть перенесены в базу данных tempdb.

Прочитайте статью здесь: TempDB :: Переменная таблицы против локальной временной таблицы


3
Можете ли вы отредактировать свои ответы в одном ответе на два вопроса?
Джошуа Дрейк

7

Другое основное отличие состоит в том, что у табличных переменных нет статистики по столбцам, в отличие от временных таблиц. Это означает, что оптимизатор запросов не знает, сколько строк находится в табличной переменной (он угадывает 1), что может привести к генерации крайне неоптимальных планов, если переменная таблицы на самом деле имеет большое количество строк.


2
rowsСтолбец sys.partitionsподдерживается для таблицы переменных , так что на самом деле знает , сколько строк в таблице. Это можно увидеть с помощью OPTION (RECOMPILE). Но отсутствие статистики по столбцам означает, что он не может оценить конкретные предикаты столбцов.
Мартин Смит

7

Цитата взята из; Профессиональные внутренние компоненты SQL Server 2012 и устранение неполадок

Статистика Основное различие между временными таблицами и табличными переменными состоит в том, что статистика не создается для табличных переменных. Это имеет два основных следствия, первое из которых заключается в том, что Оптимизатор запросов использует фиксированную оценку для числа строк в табличной переменной независимо от содержащихся в ней данных. Более того, добавление или удаление данных не меняет оценку.

Индексы Вы не можете создавать индексы для табличных переменных, хотя вы можете создавать ограничения. Это означает, что создавая первичные ключи или уникальные ограничения, вы можете иметь индексы (так как они созданы для поддержки ограничений) для табличных переменных. Даже если у вас есть ограничения и, следовательно, индексы, которые будут иметь статистику, индексы не будут использоваться при компиляции запроса, поскольку они не будут существовать во время компиляции и не будут вызывать перекомпиляцию.

Модификации схемы Модификации схемы возможны для временных таблиц, но не для переменных таблицы. Хотя модификации схем возможны для временных таблиц, избегайте их использования, поскольку они вызывают перекомпиляцию операторов, использующих таблицы.

Временные таблицы и переменные таблиц

ПЕРЕМЕННЫЕ ТАБЛИЦЫ НЕ СОЗДАЮТСЯ В ПАМЯТИ

Существует распространенное заблуждение, что табличные переменные являются структурами в памяти и поэтому будут работать быстрее, чем временные таблицы . Благодаря DMV под названием sys. Использование dm _ db _ session _ space _, которое показывает использование tempdb сессией, вы можете доказать, что это не так . После перезапуска SQL Server для очистки DMV запустите следующий сценарий, чтобы подтвердить, что идентификатор сеанса _ возвращает 0 для пользователя _ объекты _ alloc _ page _ count:

SELECT session_id,
database_id,
user_objects_alloc_page_count
FROM sys.dm_db_session_space_usage
WHERE session_id > 50 ;

Теперь вы можете проверить, сколько места занимает временная таблица, запустив следующий скрипт, чтобы создать временную таблицу с одним столбцом и заполнить ее одной строкой:

CREATE TABLE #TempTable ( ID INT ) ;
INSERT INTO #TempTable ( ID )
VALUES ( 1 ) ;
GO
SELECT session_id,
database_id,
user_objects_alloc_page_count
FROM sys.dm_db_session_space_usage
WHERE session_id > 50 ;

Результаты на моем сервере показывают, что таблице была выделена одна страница в базе данных tempdb. Теперь запустите тот же скрипт, но на этот раз используйте табличную переменную:

DECLARE @TempTable TABLE ( ID INT ) ;
INSERT INTO @TempTable ( ID )
VALUES ( 1 ) ;
GO
SELECT session_id,
database_id,
user_objects_alloc_page_count
FROM sys.dm_db_session_space_usage
WHERE session_id > 50 ;

Какой использовать?

Независимо от того, используете ли вы временные таблицы или переменные таблиц, должно быть решено тщательным тестированием, но лучше склоняться к временным таблицам в качестве значений по умолчанию, потому что гораздо меньше вещей могут пойти не так .

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


В статистике факт создаются таблицы переменных см stackoverflow.com/questions/42824366/...
YUFENG Shen

4

Еще одно отличие:

Доступ к таблице var возможен только из операторов внутри процедуры, которая ее создает, но не из других процедур, вызываемых этой процедурой или вложенного динамического SQL (через exec или sp_executesql).

Область временных таблиц, с другой стороны, включает код в вызываемых процедурах и вложенный динамический SQL.

Если таблица, созданная вашей процедурой, должна быть доступна из других вызываемых процедур или динамического SQL, вы должны использовать временную таблицу. Это может быть очень удобно в сложных ситуациях.


2

Различия между Temporary Tables (##temp/#temp)и Table Variables (@table)как:

  1. Table variable (@table)создается в memory. Принимая во внимание, что Temporary table (##temp/#temp)создается в tempdb database. Однако, если есть нехватка памяти, страницы, принадлежащие табличной переменной, могут быть помещены в tempdb.

  2. Table variablesне может быть вовлечен в transactions, logging or locking. Это делает @table faster then #temp. Таким образом, переменная таблицы быстрее, чем временная таблица.

  3. Temporary tableдопускает модификации схемы в отличие от Table variables.

  4. Temporary tablesвидны в созданной подпрограмме, а также в дочерних подпрограммах. Принимая во внимание, что переменные таблицы видны только в созданной подпрограмме.

  5. Temporary tablesразрешены, CREATE INDEXesтогда как, Table variablesне разрешены, CREATE INDEXвместо этого они могут иметь индекс с помощью Primary Key or Unique Constraint.


1

Также учтите, что вы часто можете заменить обе таблицы производными таблицами, что также может быть быстрее. Однако, как и при любой настройке производительности, только реальные тесты на основе ваших реальных данных могут предложить вам лучший подход для вашего конкретного запроса.


1

Меня удивляет, что никто не упомянул ключевое различие между этими двумя в том, что временная таблица поддерживает параллельную вставку, а переменная таблицы - нет. Вы должны увидеть разницу с планом выполнения. А вот видео с семинаров по SQL на 9 канале .

Это также объясняет, почему вы должны использовать временную переменную для небольших таблиц, в противном случае используйте временную таблицу, как SQLMenace отвечал ранее.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.