이 특정한 경우에 테이블 변수를 #temp 테이블보다 두 배 이상 빠른 이유는 무엇입니까?


37

나는 임시 테이블과 테이블 변수 및 SQL Server 성능에 미치는 영향 및 SQL Server 2008 의 기사를보고 2005 년에 표시된 것과 유사한 결과를 재현 할 수있었습니다.

10 개의 행만으로 저장 프로 시저 (아래 정의)를 실행할 때 테이블 변수 버전 출력은 임시 테이블 버전을 두 번 이상 수행합니다.

프로 시저 캐시를 지우고 두 저장 프로 시저를 10,000 번 실행 한 후 다른 4 번의 실행에 대해 프로세스를 반복했습니다. 아래 결과 (배치 당 시간 (ms)

T2_Time     V2_Time
----------- -----------
8578        2718      
6641        2781    
6469        2813   
6766        2797
6156        2719

내 질문은 : 테이블 변수 버전의 성능이 향상된 이유는 무엇입니까?

조사를 마쳤습니다. 예를 들어 성능 카운터를보고

SELECT cntr_value
from sys.dm_os_performance_counters
where counter_name = 'Temp Tables Creation Rate';

두 경우 모두 첫 번째 실행 후 모든 호출에 대해 처음부터 다시 작성하지 않고 임시 오브젝트가 예상대로 캐시되고 있음을 확인합니다 .

마찬가지로 추적 Auto Stats, SP:Recompile, SQL:StmtRecompile이러한 이벤트는 (의 첫 번째 호출에 한 번 발생하는 것을 알 수 프로파일 러의 이벤트 (아래 스크린 샷) #temp테이블에 저장 프로 시저) 및 다른 9999 개 실행 이러한 이벤트 중 하나를 제기하지 않는다. (테이블 변수 버전은 이러한 이벤트를받지 않습니다)

자취

저장 프로 시저의 첫 번째 실행에서 약간 더 큰 오버 헤드는 전체적인 큰 차이를 설명 할 수는 없지만 프로 시저 캐시를 지우고 두 프로 시저를 한 번 실행하는 데 몇 ms 만 걸리므로 통계 또는 재 컴파일이 원인 일 수 있습니다.

필요한 데이터베이스 객체 생성

CREATE DATABASE TESTDB_18Feb2012;

GO

USE TESTDB_18Feb2012;

CREATE TABLE NUM 
  ( 
     n INT PRIMARY KEY, 
     s VARCHAR(128) 
  ); 

WITH NUMS(N) 
     AS (SELECT TOP 1000000 ROW_NUMBER() OVER (ORDER BY $/0) 
         FROM   master..spt_values v1, 
                master..spt_values v2) 
INSERT INTO NUM 
SELECT N, 
       'Value: ' + CONVERT(VARCHAR, N) 
FROM   NUMS 

GO

CREATE PROCEDURE [dbo].[T2] @total INT 
AS 
  CREATE TABLE #T 
    ( 
       n INT PRIMARY KEY, 
       s VARCHAR(128) 
    ) 

  INSERT INTO #T 
  SELECT n, 
         s 
  FROM   NUM 
  WHERE  n%100 > 0 
         AND n <= @total 

  DECLARE @res VARCHAR(128) 

  SELECT @res = MAX(s) 
  FROM   NUM 
  WHERE  n <= @total 
         AND NOT EXISTS(SELECT * 
                        FROM   #T 
                        WHERE  #T.n = NUM.n) 
GO

CREATE PROCEDURE [dbo].[V2] @total INT 
AS 
  DECLARE @V TABLE ( 
    n INT PRIMARY KEY, 
    s VARCHAR(128)) 

  INSERT INTO @V 
  SELECT n, 
         s 
  FROM   NUM 
  WHERE  n%100 > 0 
         AND n <= @total 

  DECLARE @res VARCHAR(128) 

  SELECT @res = MAX(s) 
  FROM   NUM 
  WHERE  n <= @total 
         AND NOT EXISTS(SELECT * 
                        FROM   @V V 
                        WHERE  V.n = NUM.n) 


GO

테스트 스크립트

SET NOCOUNT ON;

DECLARE @T1 DATETIME2,
        @T2 DATETIME2,
        @T3 DATETIME2,  
        @Counter INT = 0

SET @T1 = SYSDATETIME()

WHILE ( @Counter < 10000)
BEGIN
EXEC dbo.T2 10
SET @Counter += 1
END

SET @T2 = SYSDATETIME()
SET @Counter = 0

WHILE ( @Counter < 10000)
BEGIN
EXEC dbo.V2 10
SET @Counter += 1
END

SET @T3 = SYSDATETIME()

SELECT DATEDIFF(MILLISECOND,@T1,@T2) AS T2_Time,
       DATEDIFF(MILLISECOND,@T2,@T3) AS V2_Time

프로파일 러 추적은 통계 #temp가 지워지고 이후에 9,999 번 더 다시 채워지더라도 통계가 테이블에 한 번만 작성됨을 나타냅니다 .
Martin Smith

답변:


31

SET STATISTICS IO ON둘 다 의 출력은 비슷해 보입니다.

SET STATISTICS IO ON;
PRINT 'V2'
EXEC dbo.V2 10
PRINT 'T2'
EXEC dbo.T2 10

준다

V2
Table '#58B62A60'. Scan count 0, logical reads 20
Table 'NUM'. Scan count 1, logical reads 3

Table '#58B62A60'. Scan count 10, logical reads 20
Table 'NUM'. Scan count 1, logical reads 3

T2
Table '#T__ ... __00000000E2FE'. Scan count 0, logical reads 20
Table 'NUM'. Scan count 1, logical reads 3

Table '#T__ ... __00000000E2FE'. Scan count 0, logical reads 20
Table 'NUM'. Scan count 1, logical reads 3

아론이 코멘트에서 지적 하듯에서 추구 중첩 루프 인덱스에 의해 구동 계획이다 모두 동안으로 그리고 테이블 변수 버전에 대한 계획은 실제로 효율성이 떨어집니다 A가에 인덱스로 추구 테이블 버전 수행 잔류 조건과 테이블 변수 반면, version은 잔존 술어 로 인덱스 검색을 수행 하므로 더 많은 행을 처리합니다 (이 계획은 더 많은 수의 행에 대해 성능이 좋지 않은 이유)dbo.NUM#temp[#T].n = [dbo].[NUM].[n][#T].[n]<=[@total]@V.n <= [@total]@V.[n]=[dbo].[NUM].[n]

확장 이벤트 를 사용 하여 특정 spid에 대한 대기 유형을 확인하면이 결과가 10,000 번 실행됩니다.EXEC dbo.T2 10

+---------------------+------------+----------------+----------------+----------------+
|                     |            |     Total      | Total Resource |  Total Signal  |
| Wait Type           | Wait Count | Wait Time (ms) | Wait Time (ms) | Wait Time (ms) |
+---------------------+------------+----------------+----------------+----------------+
| SOS_SCHEDULER_YIELD | 16         | 19             | 19             | 0              |
| PAGELATCH_SH        | 39998      | 14             | 0              | 14             |
| PAGELATCH_EX        | 1          | 0              | 0              | 0              |
+---------------------+------------+----------------+----------------+----------------+

이 결과는 10,000 회 실행 EXEC dbo.V2 10

+---------------------+------------+----------------+----------------+----------------+
|                     |            |     Total      | Total Resource |  Total Signal  |
| Wait Type           | Wait Count | Wait Time (ms) | Wait Time (ms) | Wait Time (ms) |
+---------------------+------------+----------------+----------------+----------------+
| PAGELATCH_EX        | 2          | 0              | 0              | 0              |
| PAGELATCH_SH        | 1          | 0              | 0              | 0              |
| SOS_SCHEDULER_YIELD | 676        | 0              | 0              | 0              |
+---------------------+------------+----------------+----------------+----------------+

따라서 테이블 케이스 PAGELATCH_SH에서 대기 수가 훨씬 더 높습니다 #temp. 확장 이벤트 추적에 대기 자원을 추가하는 방법을 알지 못 하므로이 추가 조사를 수행했습니다.

WHILE 1=1
EXEC dbo.T2 10

다른 연결 폴링 중에 sys.dm_os_waiting_tasks

CREATE TABLE #T(resource_description NVARCHAR(2048))

WHILE 1=1
INSERT INTO #T
SELECT resource_description
FROM sys.dm_os_waiting_tasks
WHERE session_id=<spid_of_other_session> and wait_type='PAGELATCH_SH'

약 15 초 동안 실행 한 후 다음 결과를 수집했습니다.

+-------+----------------------+
| Count | resource_description |
+-------+----------------------+
|  1098 | 2:1:150              |
|  1689 | 2:1:146              |
+-------+----------------------+

래칭되는이 두 페이지는 모두 tempdb.sys.sysschobjs기본 테이블에서 ( 'nc1'및 ) 다른 클러스터되지 않은 인덱스에 속합니다 'nc2'.

tempdb.sys.fn_dblog실행 중에 쿼리 하면 각 저장 프로 시저의 첫 번째 실행에 의해 추가 된 로그 레코드의 수는 다소 가변적이지만 후속 실행의 경우 각 반복에 의해 추가 된 수는 매우 일관되고 예측 가능합니다. 프로 시저 계획이 캐시되면 로그 항목 수는 #temp버전에 필요한 것의 약 절반 입니다.

+-----------------+----------------+------------+
|                 | Table Variable | Temp Table |
+-----------------+----------------+------------+
| First Run       |            126 | 72 or 136  |
| Subsequent Runs |             17 | 32         |
+-----------------+----------------+------------+

#tempSP 의 테이블 버전 SP 에 대한 트랜잭션 로그 항목을 자세히 보면 저장 프로 시저를 호출 할 때마다 3 개의 트랜잭션과 테이블 변수가 1 개만 생성됩니다.

+---------------------------------+----+---------------------------------+----+
|           #Temp Table                |         @Table Variable              |
+---------------------------------+----+---------------------------------+----+
| CREATE TABLE                    |  9 |                                 |    |
| INSERT                          | 12 | TVQuery                         | 12 |
| FCheckAndCleanupCachedTempTable | 11 | FCheckAndCleanupCachedTempTable |  5 |
+---------------------------------+----+---------------------------------+----+

INSERT/ TVQUERY트랜잭션 이름을 제외하고 동일합니다. 여기에는 임시 테이블 또는 테이블 변수에 삽입 된 10 개의 행 각각에 대한 로그 레코드와 LOP_BEGIN_XACT/ LOP_COMMIT_XACT항목이 포함됩니다.

CREATE TABLE거래에만 나타납니다 #Temp버전으로 다음과 같습니다.

+-----------------+-------------------+---------------------+
|    Operation    |      Context      |    AllocUnitName    |
+-----------------+-------------------+---------------------+
| LOP_BEGIN_XACT  | LCX_NULL          |                     |
| LOP_SHRINK_NOOP | LCX_NULL          |                     |
| LOP_MODIFY_ROW  | LCX_CLUSTERED     | sys.sysschobjs.clst |
| LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.sysschobjs.nc1  |
| LOP_INSERT_ROWS | LCX_INDEX_LEAF    | sys.sysschobjs.nc1  |
| LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.sysschobjs.nc2  |
| LOP_INSERT_ROWS | LCX_INDEX_LEAF    | sys.sysschobjs.nc2  |
| LOP_MODIFY_ROW  | LCX_CLUSTERED     | sys.sysschobjs.clst |
| LOP_COMMIT_XACT | LCX_NULL          |                     |
+-----------------+-------------------+---------------------+

FCheckAndCleanupCachedTempTable트랜잭션이 모두 나타납니다하지만 6 개 추가 항목이 #temp버전. 이들은 6 개의 행을 참조 sys.sysschobjs하며 위와 정확히 동일한 패턴을 갖습니다.

+-----------------+-------------------+----------------------------------------------+
|    Operation    |      Context      |                AllocUnitName                 |
+-----------------+-------------------+----------------------------------------------+
| LOP_BEGIN_XACT  | LCX_NULL          |                                              |
| LOP_DELETE_ROWS | LCX_NONSYS_SPLIT  | dbo.#7240F239.PK__#T________3BD0199374293AAB |
| LOP_HOBT_DELTA  | LCX_NULL          |                                              |
| LOP_HOBT_DELTA  | LCX_NULL          |                                              |
| LOP_MODIFY_ROW  | LCX_CLUSTERED     | sys.sysschobjs.clst                          |
| LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.sysschobjs.nc1                           |
| LOP_INSERT_ROWS | LCX_INDEX_LEAF    | sys.sysschobjs.nc1                           |
| LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.sysschobjs.nc2                           |
| LOP_INSERT_ROWS | LCX_INDEX_LEAF    | sys.sysschobjs.nc2                           |
| LOP_MODIFY_ROW  | LCX_CLUSTERED     | sys.sysschobjs.clst                          |
| LOP_COMMIT_XACT | LCX_NULL          |                                              |
+-----------------+-------------------+----------------------------------------------+

두 트랜잭션에서이 6 개의 행을 보면 동일한 작업에 해당합니다. 첫 번째 LOP_MODIFY_ROW, LCX_CLUSTERED는의 modify_date열에 대한 업데이트 입니다 sys.objects. 나머지 5 개 행은 모두 개체 이름 변경과 관련이 있습니다. name영향을받는 NCI ( nc1nc2) 모두의 키 열 이기 때문에이 항목 은 삭제 / 삽입으로 수행되며 클러스터 된 인덱스로 돌아가서 업데이트됩니다.

을위한 것으로 보인다 #temp테이블 버전에 의해 수행되는 정리의 저장 프로 시저의 끝 부분 때 FCheckAndCleanupCachedTempTable트랜잭션이 같은 것을에서 임시 테이블의 이름을 변경하는 #T__________________________________________________________________________________________________________________00000000E316등의 다른 내부 이름 #2F4A0079과 입력 할 때 CREATE TABLE트랜잭션이 다시 이름을 바꿉니다. 이 플립 플롭 이름은 한 연결에서 다른 연결 dbo.T2에서는 루프에서 실행될 수 있습니다.

WHILE 1=1
SELECT name, object_id, create_date, modify_date
FROM tempdb.sys.objects 
WHERE name LIKE '#%'

결과 예

스크린 샷

따라서 Alex가 언급 한 성능 차이에 대한 잠재적 설명 중 하나는 시스템 테이블을 유지 관리하는 것이 추가 작업 tempdb이라는 점입니다.


루프에서 Visual Studio Code 프로파일 러를 사용하여 두 절차를 모두 실행하면 다음이 드러납니다.

+-------------------------------+--------------------+-------+-----------+
|           Function            |    Explanation     | Temp  | Table Var |
+-------------------------------+--------------------+-------+-----------+
| CXStmtDML::XretExecute        | Insert ... Select  | 16.93 | 37.31     |
| CXStmtQuery::ErsqExecuteQuery | Select Max         | 8.77  | 23.19     |
+-------------------------------+--------------------+-------+-----------+
| Total                         |                    | 25.7  | 60.5      |
+-------------------------------+--------------------+-------+-----------+

테이블 변수 버전은 삽입 명령문 및 후속 선택을 수행하는 데 약 60 %의 시간을 소비하는 반면 임시 테이블은 절반 미만입니다. 이것은 OP에 표시된 타이밍과 일치하며 위의 결론과 함께 성능의 차이는 쿼리 실행 자체에 소비 된 시간이 아니라 보조 작업을 수행하는 데 소요되는 시간에 달려 있습니다.

임시 테이블 버전에서 "누락"75 %에 기여하는 가장 중요한 기능은 다음과 같습니다.

+------------------------------------+-------------------+
|              Function              | Inclusive Samples |
+------------------------------------+-------------------+
| CXStmtCreateTableDDL::XretExecute  | 26.26%            |
| CXStmtDDL::FinishNormalImp         | 4.17%             |
| TmpObject::Release                 | 27.77%            |
+------------------------------------+-------------------+
| Total                              | 58.20%            |
+------------------------------------+-------------------+

작성 및 해제 기능 모두에서이 함수 CMEDProxyObject::SetName는 포함 샘플 값으로 표시 19.6%됩니다. 여기서 나는 임시 테이블 케이스에서 39.2 %의 시간이 앞에서 설명한 이름 바꾸기를 사용한다고 추론합니다.

다른 40 %에 기여하는 테이블 변수 버전에서 가장 큰 것은

+-----------------------------------+-------------------+
|             Function              | Inclusive Samples |
+-----------------------------------+-------------------+
| CTableCreate::LCreate             | 7.41%             |
| TmpObject::Release                | 12.87%            |
+-----------------------------------+-------------------+
| Total                             | 20.28%            |
+-----------------------------------+-------------------+

임시 테이블 프로파일

여기에 이미지 설명을 입력하십시오

테이블 변수 프로파일

여기에 이미지 설명을 입력하십시오


10

디스코 인페르노

이것은 오래된 질문이므로 동일한 버전의 성능 프로필이 여전히 존재하는지 또는 특성이 전혀 변경되었는지 확인하기 위해 최신 버전의 SQL Server에서 문제를 다시 검토하기로 결정했습니다.

특히 SQL Server 2019에 대한 메모리 내 시스템 테이블을 추가 하면 다시 테스트 할 가치가있는 것으로 보입니다.

다른 작업을하는 동안이 문제가 발생했기 때문에 약간 다른 테스트 장치를 사용하고 있습니다.

테스트, 테스트

2013 버전의 Stack Overflow를 사용하면 이 색인과 다음 두 가지 절차가 있습니다.

인덱스:

CREATE INDEX ix_whatever 
    ON dbo.Posts(OwnerUserId) INCLUDE(Score);
GO

임시 테이블 :

    CREATE OR ALTER PROCEDURE dbo.TempTableTest(@Id INT)
    AS
    BEGIN
    SET NOCOUNT ON;

        CREATE TABLE #t(i INT NOT NULL);
        DECLARE @i INT;

        INSERT #t ( i )
        SELECT p.Score
        FROM dbo.Posts AS p
        WHERE p.OwnerUserId = @Id;

        SELECT @i = AVG(t.i)
        FROM #t AS t;

    END;
    GO 

테이블 변수 :

    CREATE OR ALTER PROCEDURE dbo.TableVariableTest(@Id INT)
    AS
    BEGIN
    SET NOCOUNT ON;

        DECLARE @t TABLE (i INT NOT NULL);
        DECLARE @i INT;

        INSERT @t ( i )
        SELECT p.Score
        FROM dbo.Posts AS p
        WHERE p.OwnerUserId = @Id;

        SELECT @i = AVG(t.i)
        FROM @t AS t;

    END;
    GO 

잠재적 인 ASYNC_NETWORK_IO 대기 를 방지하기 위해 랩퍼 프로 시저를 사용하고 있습니다.

CREATE PROCEDURE #TT AS
SET NOCOUNT ON;
    DECLARE @i INT = 1;
    DECLARE @StartDate DATETIME2(7) = SYSDATETIME();

    WHILE @i <= 50000
        BEGIN
            EXEC dbo.TempTableTest @Id = @i;
            SET @i += 1;
        END;
    SELECT DATEDIFF(MILLISECOND, @StartDate, SYSDATETIME()) AS [ElapsedTimeMilliseconds];
GO

CREATE PROCEDURE #TV AS
SET NOCOUNT ON;
    DECLARE @i INT = 1;
    DECLARE @StartDate DATETIME2(7) = SYSDATETIME();

    WHILE @i <= 50000
        BEGIN
            EXEC dbo.TableVariableTest @Id = @i;
            SET @i += 1;
        END;
    SELECT DATEDIFF(MILLISECOND, @StartDate, SYSDATETIME()) AS [ElapsedTimeMilliseconds];
GO

SQL Server 2017

이 시점에서 2014와 2016이 기본적으로 RELICS이므로 2017 년부터 테스트를 시작하고 있습니다. 또한 간결하게 Perfview로 코드를 프로파일 링 하려고 합니다. 실생활에서 나는 기다림, 걸쇠, 스핀 락, 미친 흔적 플래그 및 기타 물건을 보았습니다.

코드를 프로파일 링하는 것만으로도 관심있는 것이 드러났습니다.

시차:

  • 임시 테이블 : 17891 ms
  • 테이블 변수 : 5891 ms

여전히 분명한 차이점이 있습니까? 그러나 지금 SQL Server는 무엇을하고 있습니까?

견과류

상단에있는 diffed 샘플이 증가를 찾고, 우리는 볼 sqlminsqlsqllang!TCacheStore<CacheClockAlgorithm>::GetNextUserDataInHashBucket두 개의 가장 큰 범죄자이다.

견과류

호출 스택의 이름으로 판단하여 임시 테이블을 정리하고 내부적으로 이름을 바꾸는 것은 임시 테이블 호출과 테이블 변수 호출에서 가장 큰 시간 인 것 같습니다.

테이블 변수가 임시 테이블에 의해 내부적으로 지원되지만 문제가되지는 않습니다.

SET STATISTICS IO ON;
DECLARE @t TABLE(id INT);
SELECT * FROM @t AS t;

표 '# B98CE339'. 스캔 카운트 1

테이블 변수 테스트에 대한 호출 스택을 살펴보면 주요 위반자 중 하나가 전혀 표시되지 않습니다.

견과류

SQL Server 2019 (바닐라)

자, 이것은 여전히 ​​SQL Server 2017에서 문제이며 2019 년에 다른 점이 있습니까?

첫째, 내 소매에는 아무것도 없다는 것을 보여주기 위해 :

SELECT c.name,
       c.value_in_use,
       c.description
FROM sys.configurations AS c
WHERE c.name = 'tempdb metadata memory-optimized';

견과류

시차:

  • 임시 테이블 : 15765ms
  • 테이블 변수 : 7250 ms

두 절차 모두 다릅니다. 임시 테이블 호출은 몇 초 빨랐으며 테이블 변수 호출은 약 1.5 초 느 렸습니다. 테이블 변수 속도 저하 는 2019 년의 새로운 최적화 프로그램 인 테이블 변수 지연 컴파일에 의해 부분적으로 설명 될 수 있습니다 .

Perfview의 차이점을 살펴보면 sqlmin이 더 이상 존재하지 않지만 약간 변경되었습니다 sqllang!TCacheStore<CacheClockAlgorithm>::GetNextUserDataInHashBucket.

견과류

SQL Server 2019 (메모리 내 Tempdb 시스템 테이블)

메모리 시스템 테이블의 새로운 점은 무엇입니까? 흠? 그걸로 Sup?

켜 보자!

EXEC sys.sp_configure @configname = 'advanced', 
                      @configvalue = 1  
RECONFIGURE;

EXEC sys.sp_configure @configname = 'tempdb metadata memory-optimized', 
                      @configvalue = 1 
RECONFIGURE;

이 작업을 시작하려면 SQL Server를 다시 시작해야하므로 금요일 오후에 SQL을 재부팅하는 동안 나를 용서하십시오.

이제 상황이 다르게 보입니다.

SELECT c.name,
       c.value_in_use,
       c.description
FROM sys.configurations AS c
WHERE c.name = 'tempdb metadata memory-optimized';

SELECT *, 
       OBJECT_NAME(object_id) AS object_name, 
       @@VERSION AS sql_server_version
FROM tempdb.sys.memory_optimized_tables_internal_attributes;

견과류

시차:

  • 임시 테이블 : 11638ms
  • 테이블 변수 : 7403ms

임시 테이블은 약 4 초 더 나았습니다! 그 거에요

나는 무언가를 좋아한다.

이번에는 Perfview diff가 그리 재미 있지 않습니다. 나란히, 시간이 전반적으로 얼마나 근접해 있는지 주목하는 것은 흥미 롭습니다.

견과류

diff의 흥미로운 점 중 하나는에 대한 호출 hkengine!이며, 이는 hekaton-ish 기능이 현재 사용 중이므로 명백해 보일 수 있습니다.

견과류

diff에서 상위 2 개 항목까지는 많은 것을 만들 수 없습니다 ntoskrnl!?.

견과류

또는 sqltses!CSqlSortManager_80::GetSortKeySmrtr Ppl ™에서 다음을 확인하십시오.

견과류

문서화되지 않았고 생산에는 안전하지 않은 것이 있으므로 메모리 내 기능에 포함 된 추가 임시 테이블 시스템 오브젝트 (sysrowset, sysallocunits 및 sysseobjvalues)를 갖는 데 사용할 수있는 시작 추적 플래그 를 사용하지 마십시오. 이 경우 실행 시간에 눈에 띄는 차이가 없었습니다.

모으다

최신 버전의 SQL Server에서도 테이블 변수에 대한 고주파수 호출은 임시 테이블에 대한 고주파수 호출보다 훨씬 빠릅니다.

컴파일, 재 컴파일, 자동 통계, 래치, 스핀 록, 캐싱 또는 기타 문제를 비난하려는 유혹이 있지만, 여전히 문제는 임시 테이블 정리 관리와 관련이 있습니다.

인 메모리 시스템 테이블을 사용하는 SQL Server 2019에서는 더 가까이 호출하지만 호출 빈도가 높을 때 테이블 변수의 성능이 여전히 우수합니다.

물론, vaing sage가 한 번 생각했던 것처럼 : "계획 선택이 문제가되지 않을 때 테이블 변수를 사용하십시오".


Nice – 죄송합니다. "debugging"블로그 게시물의 링크를 따라 가기 전까지는 답변을 추가하지 않으
Martin Smith
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.