SQL Server에서 임시 테이블과 테이블 변수의 차이점은 무엇입니까?


답변:


668

내용

내용

경고

이 답변에서는 SQL Server 2000에 도입 된 "기본"테이블 변수에 대해 설명합니다. 메모리 OLTP의 SQL Server 2014에는 메모리 최적화 테이블 유형이 도입되었습니다. 이것들의 테이블 변수 인스턴스는 아래에서 논의되는 것들과 많은 점에서 다릅니다! ( 자세한 내용 ).

보관 위치

차이 없음. 둘 다에 저장됩니다 tempdb.

테이블 변수의 경우 항상 그런 것은 아니지만 아래에서 확인할 수 있음을 제안했습니다.

DECLARE @T TABLE(X INT)

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

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

결과 예 ( tempdb2 개의 행에 위치 표시 가 저장 됨)

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

논리적 위치

@table_variables마치 #temp테이블 보다 현재 데이터베이스의 일부인 것처럼 더 많이 작동합니다 . 테이블 변수 (2005 년 이후)의 경우 명시 적으로 지정하지 않은 경우 열 데이터 정렬은 현재 데이터베이스의 #temp데이터 정렬이 되고 테이블의 경우 기본 데이터 정렬 tempdb( 세부 정보 )이 사용됩니다. 또한 #temp테이블 에 사용하려면 사용자 정의 데이터 유형 및 XML 콜렉션이 tempdb에 있어야 하지만 테이블 변수는 현재 데이터베이스 ( Source ) 에서이를 사용할 수 있습니다 .

SQL Server 2012에는 포함 된 데이터베이스가 도입되었습니다. 이것들에서 임시 테이블의 동작이 다릅니다 (h / t Aaron)

포함 된 데이터베이스에서 임시 테이블 데이터는 포함 된 데이터베이스의 데이터 정렬에서 데이터 정렬됩니다.

  • 임시 테이블과 관련된 모든 메타 데이터 (예 : 테이블 및 열 이름, 인덱스 등)는 카탈로그 데이터 정렬에 있습니다.
  • 임시 테이블에서는 명명 된 제약 조건을 사용할 수 없습니다.
  • 임시 테이블은 사용자 정의 형식, XML 스키마 컬렉션 또는 사용자 정의 함수를 참조하지 않을 수 있습니다.

다른 범위에 대한 가시성

@table_variables배치 및 배치 범위 내에서만 액세스 할 수 있습니다. #temp_tables하위 배치 내에서 액세스 할 수 있습니다 (중첩 트리거, 프로 시저, exec호출). #temp_tables외부 범위 ( @@NESTLEVEL=0) 에서 생성 된 세션은 세션이 끝날 때까지 지속되므로 배치에 걸쳐있을 수 있습니다. 다음 유형 (글로벌 ##temp테이블 가능할 있음) 에 따라 하위 유형으로 오브젝트 유형을 작성하고 호출 범위에서 액세스 할 수 없습니다.

일생

@table_variablesDECLARE @.. TABLE명령문이 포함 된 배치 가 실행 되면 (해당 배치의 사용자 코드가 실행되기 전에) 내재적으로 삭제됩니다.

구문 분석기는 DECLARE명령문 전에 테이블 변수를 사용하여 시도 할 수 없지만 암시 적 작성은 아래에서 볼 수 있습니다.

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

--Works fine
SELECT *
FROM @T

#temp_tables배치는 TSQL CREATE TABLE문이 발견 될 때 명시 적으로 DROP TABLE작성되며 배치가 종료 @@NESTLEVEL > 0되거나 ( 로 하위 배치에서 작성된 경우 ) 세션이 그렇지 않으면 종료 될 때 명시 적으로 삭제 되거나 내재적으로 삭제 됩니다.

NB : 저장된 루틴 내에서 반복적으로 새 테이블을 작성 및 삭제하지 않고 두 유형의 오브젝트 를 캐시 할 수 있습니다 . 이 캐싱이 발생할 수 #temp_tables있는 시간에 대한 제한이 있지만 위반할 수는 있지만 @table_variables어쨌든이 를 막는 제한이 있습니다 . 캐시 된 #temp테이블 의 유지 관리 오버 헤드는 여기에 설명 된 테이블 변수보다 약간 큽니다 .

객체 메타 데이터

이것은 두 유형의 객체에 대해 본질적으로 동일합니다. 에있는 시스템 기본 테이블에 저장됩니다 tempdb. 시스템 테이블을 입력하는 데 사용될 수 있는 #temp테이블 을 보는 것이 더 간단 하지만 OBJECT_ID('tempdb..#T')내부적으로 생성 된 이름은 CREATE TABLE명령문에 정의 된 이름과 더 밀접한 상관 관계가 있습니다. 테이블 변수의 경우 object_id함수가 작동하지 않으며 내부 이름은 변수 이름과 관계없이 시스템에서 완전히 생성됩니다. 아래는 메타 데이터가 여전히 존재 함을 보여줍니다 (유일하게 고유 한) 열 이름을 입력합니다. 고유 한 열 이름이없는 테이블의 경우, DBCC PAGE비어 있지 않은 한 object_id를 사용하여 판별 할 수 있습니다 .

/*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 행 @ 폴 화이트에 의해 설명되는 제로 행 테이블에서 방출됩니다 추정 왜 행을 여기 ).

그러나 이전 섹션에 표시된 결과는의 정확한 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이 도입되었습니다. 자세한 내용은 여기 "관계형 엔진"에 있습니다 .

이 추적 플래그를 사용하면 아주 짧은 시간 안에 논의 된대로 변경된 재 기호를 자동 재 컴파일에 반영 할 수 있습니다.

NB : 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영구 테이블과 동일한 테이블.

이 모든 것의 최종 효과는 SQL Server가 작업하기에 더 나은 정보를 가지고 있기 때문에 #temp테이블에 대해 생성 된 실행 계획 이 @table_variables많은 행이 관련 될 때보 다 훨씬 더 우수하다는 것입니다.

NB1 : 테이블 변수에 통계가 없지만 추적 플래그 2453에서 "통계 변경"재 컴파일 이벤트가 계속 발생할 수 있음 ( "사소한"계획에는 적용되지 않음) 위의 임시 테이블에 대해 표시된 것과 동일한 재 컴파일 임계 값에서 발생합니다. 추가하는 경우 N=0 -> RT = 1. 즉, 테이블 변수가 비어있을 때 컴파일 된 모든 명령문은 다시 컴파일되어 결국 비어 있지 않을 때 TableCardinality처음 실행될 때 수정 됩니다. 컴파일 시간 테이블 카디널리티는 계획에 저장되며 명령문이 동일한 카디널리티로 다시 실행되면 (제어문의 흐름 또는 캐시 된 계획의 재사용으로 인해) 재 컴파일이 발생하지 않습니다.

NB2 : 저장 프로 시저의 캐시 된 임시 테이블의 경우 재 컴파일 스토리는 위에서 설명한 것보다 훨씬 복잡합니다. 모든 세부 사항 은 저장 프로 시저의 임시 테이블을 참조 하십시오.

재 컴파일

위에서 설명한 수정 기반 재 컴파일뿐만 아니라 컴파일 을 트리거하는 테이블 변수에 대해 금지 된 작업 (예 : DDL 변경 , ) 을 허용하기 때문에 추가#temp 테이블과 간단히 컴파일 할 수 있습니다.CREATE INDEXALTER TABLE

잠금

언급 된 테이블 변수는 잠금에 참여하지 않는다. 그렇지 않다. 아래 출력을 SSMS 메시지 탭으로 실행하면 insert 문에 대해 취해 놓은 잠금에 대한 세부 정보가 표시됩니다.

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테이블 변수에서 나온 쿼리의 경우 Paul White는 주석에서 자동으로 암시 적 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 개의 행이 리턴되어 롤백을 허용하고 다른 트랜잭션이 커미트되지 않은 데이터를 읽지 못하도록 오브젝트 자체와 시스템 테이블 행에 잠금이 유지됨을 표시합니다. 동등한 테이블 변수 조작은 사용자 트랜잭션으로 롤백되지 않으며 다음 명령문을 체크인하기 위해 이러한 잠금을 보유 할 필요가 없지만 프로파일 러에서 획득 및 릴리스 된 추적 잠금 또는 추적 플래그 1200을 사용하여 많은 잠금 이벤트가 여전히 표시됨 나오다.

인덱스

SQL Server 2014 이전 버전의 경우 고유 제약 조건 또는 기본 키 추가의 부작용으로 테이블 변수에서만 암시 적으로 인덱스를 만들 수 있습니다. 이것은 물론 고유 인덱스 만 지원됨을 의미합니다. 고유 한 클러스터형 인덱스가있는 테이블에서 고유하지 않은 비 클러스터형 인덱스는 간단히 선언 UNIQUE NONCLUSTERED하고 원하는 NCI 키 끝에 CI 키를 추가하여 시뮬레이션 할 수 있습니다 (SQL Server는 고유하지 않은 경우에도 장면 뒤에서이를 수행합니다) NCI를 지정할 수 있음)

시연 이전에 다양한으로 index_option의이 제약 선언에 지정 될 수 있습니다 포함 DATA_COMPRESSION, IGNORE_DUP_KEYFILLFACTOR(그 하나는 인덱스에 어떤 차이가 다시 만들 것입니다 당신이 테이블 변수에 인덱스를 다시 할 수없는 설정에 아무 소용이없는 불구하고!)

또한 테이블 변수는 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 Hidden 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프로세스 모니터 1 을 사용하여 데이터 파일에 기록합니다 (때로는 데이터베이스 부트 페이지에 오프셋 73,728을 제외하고는 제외). 변경 한 후 250to 251I 아래와 같이 쓰기를 참조하기 시작했다.

프록 몬

위의 스크린 샷은 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 페이지가 디스크에 기록되었고 더티 플래그가 지워 졌음을 보여줍니다. 또한 디스크에 기록되었다고해서 페이지가 버퍼 풀에서 즉시 제거된다는 의미는 아닙니다. 이 테이블 변수에 대한 쿼리는 여전히 메모리에서 완전히 만족 될 수 있습니다.

약 1,843,000KB (23,000 페이지)로 할당 된 버퍼 풀 페이지 를 max server memory설정 2000 MB하고 DBCC MEMORYSTATUS보고 한 유휴 서버 에서 위의 표에 1,000 행 / 페이지의 배치로 기록 된 각 반복에 대해 삽입했습니다.

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가 동등한 테이블 변수와 비교하여 임시 테이블을 만들 때 (캐싱 포함) 훨씬 많은 래치를 얻는다는 것을 알았습니다. Latch_acquired 디버그 XE를 사용하고 약 35 열 정도의 테이블을 작성하여 테스트 할 수 있습니다. 테이블 변수는 4 개의 래치를 사용하고 임시 테이블은 약 70 개의 래치를 사용하는 것으로 나타났습니다.
Joe Obbish

40

연구보다는 특정 경험을 바탕으로 더 지적하고 싶은 것이 몇 가지 있습니다. DBA는 매우 새롭기 때문에 필요한 곳에서 바로 정정하십시오.

  1. #temp 테이블은 기본적으로 SQL Server 인스턴스의 기본 데이터 정렬을 사용합니다. 따라서 달리 지정하지 않으면 masterdb와 데이터베이스의 데이터 정렬이 다른 경우 #temp 테이블과 데이터베이스 테이블 간의 값을 비교하거나 업데이트하는 데 문제가 발생할 수 있습니다. 참조 : http://www.mssqltips.com/sqlservertip/2440/create-sql-server-temporary-tables-with-the-correct-collation/
  2. 개인 경험을 바탕으로, 사용 가능한 메모리는 더 나은 성능에 영향을 미치는 것으로 보입니다. MSDN은 더 작은 결과 집합을 저장하기 위해 테이블 ​​변수를 사용하는 것이 좋지만 대부분 차이는 눈에 띄지 않습니다. 그러나 더 큰 집합에서는 경우에 따라 테이블 변수가 훨씬 더 많은 메모리를 사용하므로 쿼리 속도가 느려질 수 있습니다.

6
또한 SQL Server 2012를 사용 중이고 데이터베이스가 포함 된 경우 #temp 테이블 의 데이터 정렬이 호출 데이터베이스의 데이터 정렬을 상속 할 수 있습니다 .
Aaron Bertrand

소형 및 대형 세트의 # 2에 대한 설명 stackoverflow.com/a/14465163/5224021
GibralterTop
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.