DELETE가 성능에 느린 영향을 미치는 이유는 무엇입니까?


20

마지막에는 @table 변수와 #temp 테이블 간의 성능을 비교하기위한 테스트 스크립트가 있습니다. 올바르게 설정했다고 생각합니다. 성능 타이밍은 DELETE / TRUNCATE 명령 외부 에서 가져옵니다 . 내가 얻는 결과는 다음과 같습니다 (밀리 초 단위).

@Table Variable  #Temp (delete)  #Temp (truncate)
---------------  --------------  ----------------
5723             5180            5506
15636            14746           7800
14506            14300           5583
14030            15460           5386
16706            16186           5360

제정신임을 확인하기 위해 GetDate()배치가 아닌 명령문 시점에 CURRENT_TIMESTAMP (일명 )가 취해 졌으므로 SET @StartTime = CURRENT_TIMESTAMP명령문 과 TRUNCATE / DELETE 사이에 상호 작용이 없어야합니다 .

select current_timestamp
waitfor delay '00:00:04'
select current_timestamp

-----------------------
2012-10-21 11:29:20.290

-----------------------
2012-10-21 11:29:24.290

DELETE를 사용하여 테이블을 지우는 경우 첫 번째 실행과 후속 실행 사이에 점프가 일관됩니다. DELETE에 대한 이해에서 무엇을 놓치고 있습니까? 나는 이것을 여러 번 반복하고, 순서를 바꾸었고, tempdb 크기를 늘려서 성장하지 않아도됩니다.

CREATE TABLE #values (
  id int identity primary key, -- will be clustered
  name varchar(100) null,
  number int null,
  type char(3) not null,
  low int null,
  high int null,
  status smallint not null
);
GO
SET NOCOUNT ON;

DECLARE @values TABLE (
  id int identity primary key clustered,
  name varchar(100) null,
  number int null,
  type char(3) not null,
  low int null,
  high int null,
  status smallint not null
);
DECLARE  @ExecutionTime  TABLE(      Duration bigINT    ) 
DECLARE  @StartTime DATETIME,  @i INT = 1; 
WHILE (@i <= 5) 
  BEGIN 
    DELETE @values;
    DBCC freeproccache With NO_InfoMSGS;
    DBCC DROPCLEANBUFFERS With NO_InfoMSGS;
    SET @StartTime = CURRENT_TIMESTAMP -- alternate getdate() 
    /****************** measured process ***********************/ 

    INSERT @values SELECT a.* FROM master..spt_values a join master..spt_values b on b.type='P' and b.number < 1000;

    /**************** end measured process *********************/ 
    INSERT @ExecutionTime 
    SELECT DurationInMilliseconds = datediff(ms,@StartTime,CURRENT_TIMESTAMP) 
    SET @i +=  1 
  END -- WHILE 

SELECT DurationInMilliseconds = Duration FROM   @ExecutionTime 
GO 

-- Temporary table
DECLARE  @ExecutionTime  TABLE(      Duration bigINT    ) 
DECLARE  @StartTime DATETIME,  @i INT = 1; 
WHILE (@i <= 5) 
  BEGIN 
    delete #values;
    -- TRUNCATE TABLE #values;
    DBCC freeproccache With NO_InfoMSGS;
    DBCC DROPCLEANBUFFERS With NO_InfoMSGS;
    SET @StartTime = CURRENT_TIMESTAMP -- alternate getdate() 
    /****************** measured process ***********************/ 

    INSERT #values SELECT a.* FROM master..spt_values a join master..spt_values b on b.type='P' and b.number < 1000;

    /**************** end measured process *********************/ 
    INSERT @ExecutionTime 
    SELECT DurationInMilliseconds = datediff(ms,@StartTime,CURRENT_TIMESTAMP) 
    SET @i +=  1 
  END -- WHILE 

SELECT DurationInMilliseconds = Duration FROM   @ExecutionTime 
GO

DROP TABLE  #values 
SET NOCOUNT OFF;

답변:


20

이 차이는 개체가 B + 트리 인 경우에만 적용됩니다. primary key테이블 변수를 제거 할 때 힙이므로 다음 결과를 얻습니다.

2560
2120
2080
2130
2140

그러나 PK를 사용하면 테스트에서 비슷한 패턴과 아래의 일반적인 결과를 발견했습니다.

+--------+--------+---------+-------------------+
| @table | #table | ##table | [permanent_table] |
+--------+--------+---------+-------------------+
|   2670 |   2683 |    9603 |              9703 |
|   6823 |   6840 |    9723 |              9790 |
|   6813 |   6816 |    9626 |              9703 |
|   6883 |   6816 |    9600 |              9716 |
|   6840 |   6856 |    9610 |              9673 |
+--------+--------+---------+-------------------+

내 이론은 로컬 임시 B + 트리에 대량 삽입을 수행 할 때 이미 할당 된 페이지가없는 경우에만 적용 할 수있는 최적화가 있다는 것입니다.

나는 이것을 다음 관찰에 근거한다.

  1. 다양한 버전의 테스트 코드를 실행할 때이 패턴 @table_variables#temp표 만 보았습니다 . 영구 테이블 tempdb이나 테이블이 아닙니다 ##.

  2. 성능을 저하시키기 위해 테이블에서 많은 양의 행을 미리 추가하고 제거 할 필요는 없습니다. 단순히 하나의 행을 추가하고 그대로 두는 것으로 충분합니다.

  3. TRUNCATE테이블에서 모든 페이지를 할당 해제합니다. DELETE테이블의 마지막 페이지가 할당 해제되지 않습니다.

  4. VS 2012 프로파일 러를 사용하면 더 빠른 경우 SQL Server가 다른 코드 경로를 사용한다는 것을 알 수 있습니다. 시간의 36 % 는 느린 경우에 sqlmin.dll!RowsetBulk::InsertRow소비 된 시간의 61 %에 소요됩니다 sqlmin.dll!RowsetNewSS::InsertRow.

달리는

SELECT * 
FROM sys.dm_db_index_physical_stats(2,OBJECT_ID('tempdb..#values'),1,NULL, 'DETAILED')

삭제가 반환 된 후

+-------------+------------+--------------+--------------------+
| index_level | page_count | record_count | ghost_record_count |
+-------------+------------+--------------+--------------------+
|           0 |          1 |            0 |                  1 |
|           1 |          1 |            1 |                  0 |
|           2 |          1 |            1 |                  0 |
+-------------+------------+--------------+--------------------+

추적 플래그 610활성화 하여 시간 불일치를 다소 줄일 수 있음을 알았습니다 .

이것은 줄이는 효과 없었다 (더 이상 개별 삽입 된 행 값을 기록으로 아래로 3백50메가바이트 103 MB에서) 이후의 삽입을 위해 실질적으로 로깅을하지만이 2 이후에 대한 타이밍 만 사소한 개선했다 @table, #table케이스 그리고 그 차이는 여전히 남아 있습니다. 추적 플래그는 다른 두 테이블 유형에 대한 삽입의 일반 성능을 크게 향상 시켰습니다.

+--------+--------+---------+-------------------+
| @table | #table | ##table | [permanent_table] |
+--------+--------+---------+-------------------+
|   2663 |   2670 |    5403 |              5426 |
|   5390 |   5396 |    5410 |              5403 |
|   5373 |   5390 |    5410 |              5403 |
|   5393 |   5410 |    5406 |              5433 |
|   5386 |   5396 |    5390 |              5420 |
+--------+--------+---------+-------------------+

트랜잭션 로그를 살펴보면 빈 로컬 임시 테이블에 대한 초기 삽입이 훨씬 적게 기록되는 것처럼 보입니다 (96MB).

특히 이러한 더 빠른 인서트는 더 느린 경우에 비해 657트랜잭션 ( LOP_BEGIN_XACT/ LOP_COMMIT_XACT쌍) 만 처리했습니다 10,000. 특히 LOP_FORMAT_PAGE작업이 크게 줄어든 것 같습니다. 느린 경우에는 테이블의 각 페이지 (약 10,270) 에 대해이 경우에 대한 트랜잭션 로그 항목이 4빠른 경우의 해당 항목 과 비교됩니다 .

세 경우 모두에 사용 된 로그는 다음과 같습니다 (텍스트 양을 줄이기 위해 시스템 기본 테이블 업데이트 로그 레코드를 삭제했지만 여전히 총계에 포함됨)

첫 번째 삽입에 대한 로깅 @table_var(96.5MB)

+-----------------------+----------+----------------------------------------------+---------------+---------+
|       Operation       | Context  |                AllocUnitName                 | Size in Bytes |   Cnt   |
+-----------------------+----------+----------------------------------------------+---------------+---------+
| LOP_BEGIN_XACT        | LCX_NULL | NULL                                         |         83876 |     658 |
| LOP_COMMIT_XACT       | LCX_NULL | NULL                                         |         34164 |     657 |
| LOP_CREATE_ALLOCCHAIN | LCX_NULL | NULL                                         |           120 |       3 |
| LOP_FORMAT_PAGE       | LCX_HEAP | dbo.#531856C7                                |            84 |       1 |
| LOP_FORMAT_PAGE       | LCX_IAM  | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |            84 |       1 |
| LOP_FORMAT_PAGE       | LCX_IAM  | dbo.#531856C7                                |            84 |       1 |
| LOP_FORMAT_PAGE       | LCX_IAM  | Unknown Alloc Unit                           |            84 |       1 |
| LOP_HOBT_DDL          | LCX_NULL | NULL                                         |           216 |       6 |
| LOP_HOBT_DELTA        | LCX_NULL | NULL                                         |           320 |       5 |
| LOP_IDENT_NEWVAL      | LCX_NULL | NULL                                         |     100240000 | 2506000 |
| LOP_INSERT_ROWS       | LCX_HEAP | dbo.#531856C7                                |            72 |       1 |
| LOP_MODIFY_ROW        | LCX_IAM  | dbo.#531856C7                                |            88 |       1 |
| LOP_MODIFY_ROW        | LCX_PFS  | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |        158592 |    1848 |
| LOP_MODIFY_ROW        | LCX_PFS  | dbo.#531856C7                                |            80 |       1 |
| LOP_MODIFY_ROW        | LCX_PFS  | Unknown Alloc Unit                           |        216016 |    2455 |
| LOP_SET_BITS          | LCX_GAM  | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |         84360 |    1406 |
| LOP_SET_BITS          | LCX_GAM  | Unknown Alloc Unit                           |        147120 |    2452 |
| LOP_SET_BITS          | LCX_IAM  | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |         84360 |    1406 |
| LOP_SET_BITS          | LCX_IAM  | Unknown Alloc Unit                           |        147120 |    2452 |
| Total                 | NULL     | NULL                                         |     101209792 | 2519475 |
+-----------------------+----------+----------------------------------------------+---------------+---------+

후속 삽입 로깅 TF 610 끄기 (350MB)

+-----------------------+--------------------+----------------------------------------------+---------------+---------+
|       Operation       |      Context       |                AllocUnitName                 | Size in Bytes |   Cnt   |
+-----------------------+--------------------+----------------------------------------------+---------------+---------+
| LOP_BEGIN_CKPT        | LCX_NULL           | NULL                                         |            96 |       1 |
| LOP_BEGIN_XACT        | LCX_NULL           | NULL                                         |       1520696 |   12521 |
| LOP_COMMIT_XACT       | LCX_NULL           | NULL                                         |        651040 |   12520 |
| LOP_CREATE_ALLOCCHAIN | LCX_NULL           | NULL                                         |            40 |       1 |
| LOP_DELETE_SPLIT      | LCX_INDEX_INTERIOR | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |          2160 |      36 |
| LOP_END_CKPT          | LCX_NULL           | NULL                                         |           136 |       1 |
| LOP_FORMAT_PAGE       | LCX_HEAP           | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |        859236 |   10229 |
| LOP_FORMAT_PAGE       | LCX_IAM            | Unknown Alloc Unit                           |            84 |       1 |
| LOP_FORMAT_PAGE       | LCX_INDEX_INTERIOR | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |          3108 |      37 |
| LOP_HOBT_DDL          | LCX_NULL           | NULL                                         |           648 |      18 |
| LOP_HOBT_DELTA        | LCX_NULL           | NULL                                         |        657088 |   10267 |
| LOP_IDENT_NEWVAL      | LCX_NULL           | NULL                                         |     100239960 | 2505999 |
| LOP_INSERT_ROWS       | LCX_CLUSTERED      | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |     258628000 | 2506000 |
| LOP_INSERT_ROWS       | LCX_HEAP           | dbo.#531856C7                                |            72 |       1 |
| LOP_INSERT_ROWS       | LCX_INDEX_INTERIOR | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |       1042776 |   10302 |
| LOP_MODIFY_HEADER     | LCX_HEAP           | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |        859236 |   10229 |
| LOP_MODIFY_HEADER     | LCX_INDEX_INTERIOR | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |          3192 |      38 |
| LOP_MODIFY_ROW        | LCX_IAM            | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |           704 |       8 |
| LOP_MODIFY_ROW        | LCX_PFS            | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |        934264 |   11550 |
| LOP_MODIFY_ROW        | LCX_PFS            | Unknown Alloc Unit                           |        783984 |    8909 |
| LOP_SET_BITS          | LCX_GAM            | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |         76980 |    1283 |
| LOP_SET_BITS          | LCX_GAM            | Unknown Alloc Unit                           |        534480 |    8908 |
| LOP_SET_BITS          | LCX_IAM            | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |         76980 |    1283 |
| LOP_SET_BITS          | LCX_IAM            | Unknown Alloc Unit                           |        534480 |    8908 |
| LOP_SHRINK_NOOP       | LCX_NULL           | NULL                                         |            32 |       1 |
| LOP_XACT_CKPT         | LCX_NULL           | NULL                                         |            92 |       1 |
| Total                 | NULL               | NULL                                         |     367438748 | 5119297 |
+-----------------------+--------------------+----------------------------------------------+---------------+---------+

후속 삽입 로깅 TF 610 on (103 MB)

+-------------------------+-------------------------+----------------------------------------------+---------------+---------+
|        Operation        |         Context         |                AllocUnitName                 | Size in Bytes |   Cnt   |
+-------------------------+-------------------------+----------------------------------------------+---------------+---------+
| LOP_BEGIN_CKPT          | LCX_NULL                | NULL                                         |           192 |       2 |
| LOP_BEGIN_XACT          | LCX_NULL                | NULL                                         |       1339796 |   11099 |
| LOP_BULK_EXT_ALLOCATION | LCX_NULL                | NULL                                         |         20616 |     162 |
| LOP_COMMIT_XACT         | LCX_NULL                | NULL                                         |        577096 |   11098 |
| LOP_CREATE_ALLOCCHAIN   | LCX_NULL                | NULL                                         |            40 |       1 |
| LOP_DELETE_SPLIT        | LCX_INDEX_INTERIOR      | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |          2160 |      36 |
| LOP_END_CKPT            | LCX_NULL                | NULL                                         |           272 |       2 |
| LOP_FORMAT_PAGE         | LCX_BULK_OPERATION_PAGE | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |        863520 |   10280 |
| LOP_FORMAT_PAGE         | LCX_IAM                 | Unknown Alloc Unit                           |            84 |       1 |
| LOP_FORMAT_PAGE         | LCX_INDEX_INTERIOR      | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |          3108 |      37 |
| LOP_HOBT_DELTA          | LCX_NULL                | NULL                                         |        666496 |   10414 |
| LOP_IDENT_NEWVAL        | LCX_NULL                | NULL                                         |     100239960 | 2505999 |
| LOP_INSERT_ROWS         | LCX_CLUSTERED           | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |         23544 |     218 |
| LOP_INSERT_ROWS         | LCX_HEAP                | dbo.#719CDDE7                                |            72 |       1 |
| LOP_INSERT_ROWS         | LCX_INDEX_INTERIOR      | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |       1042776 |   10302 |
| LOP_MODIFY_HEADER       | LCX_BULK_OPERATION_PAGE | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |        780216 |   10266 |
| LOP_MODIFY_HEADER       | LCX_HEAP                | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |       1718472 |   20458 |
| LOP_MODIFY_HEADER       | LCX_INDEX_INTERIOR      | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |          3192 |      38 |
| LOP_MODIFY_ROW          | LCX_IAM                 | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |           704 |       8 |
| LOP_MODIFY_ROW          | LCX_PFS                 | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |        114832 |    1307 |
| LOP_MODIFY_ROW          | LCX_PFS                 | Unknown Alloc Unit                           |        231696 |    2633 |
| LOP_RANGE_INSERT        | LCX_NULL                | NULL                                         |            48 |       1 |
| LOP_SET_BITS            | LCX_GAM                 | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |         77100 |    1285 |
| LOP_SET_BITS            | LCX_GAM                 | Unknown Alloc Unit                           |        157920 |    2632 |
| LOP_SET_BITS            | LCX_IAM                 | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |         77100 |    1285 |
| LOP_SET_BITS            | LCX_IAM                 | Unknown Alloc Unit                           |        157920 |    2632 |
| LOP_XACT_CKPT           | LCX_NULL                | NULL                                         |            92 |       1 |
| Total                   | NULL                    | NULL                                         |     108102960 | 2602218 |
+-------------------------+-------------------------+----------------------------------------------+---------------+---------+

자세한 확인에 감사드립니다. 따라서 질문은 여전히 ​​그렇습니다. 왜 DELETE가 용어를 사용하여 테이블을 실제로 비워 두지 않는지 묻습니다 . 또한 배치 처리 루프에서 clear / populate가 사용되는 경우 #temp 테이블을 사용하도록 주장합니다.
孔夫子

1
@RichardTheKiwi- 그 자체 로 TRUNCATE오버 의 이점도 DELETE그것을 주장합니다. 또한 많은 수의 행에 대한 테이블 변수를 거의 고려하지 않습니다.
Martin Smith

이것은 게으른 것처럼 들리지만 배치에서 1-10 레코드 (가변) 삽입을 1000 번 반복하여 동일한 증상을 나타내지 않습니까? 많은 수의 행을 사용하면 문제를 악화시키고 차이를 더 잘 볼 수있는 규모를 제공 할 수 있습니다. 문제의 요점은 일단 차이점이 무엇인지 알면 #temp 테이블이 더 나을 것이라는 점을 증명하는 것입니다.
孔夫子

내 이론은 10,000+훨씬 더 최적화 된 방식으로 발생 하는 페이지 할당이며 페이지 당 일부 오버 헤드를 피하는 것 같습니다. 작은 인서트의 경우 이러한 차이가 덜 중요 할 것으로 기대합니다.
Martin Smith

@RichardTheKiwi-감사합니다! 아마 이것에 대해 더 말할 것이 있습니다. SQL Kiwi와 동일한 버전으로 업그레이드하고 다른 코드 경로가 계속 표시되는지 확인합니다. 그렇다면 하드웨어에 따라 차이가있을 수 있습니다 (테스트는 동일한 SSD에있는 모든 데이터 및 로그 파일을 사용하여 데스크탑 PC에서 수행되었습니다)
Martin Smith

0

관찰과 추측. . .

일부 시스템에서 CURRENT_TIMESTAMP는 현재 트랜잭션이 시작되는 시간으로 정의됩니다. 빠른 검색은 CURRENT_TIMESTAMP가 SQL Server에서 작동하는 방식에 대한 명확한 문서를 찾지 못했습니다. 그러나 SQL 서버의 기본 모드는 트랜잭션을 자동 커밋 할 수 없으며,이 때문에 더 여기 TRANSACTION이 BEGIN 있어요 한다고 INSERT 문 앞에 바로 시간이 될 수 있습니다. DELETE 문은 자동으로 커밋해야하며 CURRENT_TIMESTAMP가 SQL Server에서 작동하는 방식에 관계없이 자동 커밋 된 트랜잭션을 사용할 때는 DELETE 문과 관련이 없습니다.

첫 번째 반복에서 DELETE 문에는 수행 할 실제 작업이 없으며 기록 할 개별 행이 없습니다. 아마 옵티마이 저는 그것을 알고있을 것이고, 그것은 첫 번째 반복의 시간을 줄입니다. (삭제할 행과 기록 할 개별 행이없는 조합)

삭제하기 전에 삽입하여 테스트 할 수 있습니다.


오늘은 질문에 대답하지 않겠습니다. 또는 상자에 내용을 입력 할 때 수행하는 작업이 무엇이든 상관 없습니다.
Mike Sherrill 'Cat

이 답변을 구식, 접선 및 산만하게 삭제해야합니까?
孔夫子
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.