bcp / BULK INSERT와 테이블 값 매개 변수의 성능


84

BULK INSERT스키마가 변경 되었기 때문에 SQL Server의 명령을 사용하여 다소 오래된 코드를 다시 작성 해야합니다. 대신 TVP를 사용하여 저장 프로 시저로 전환하는 것에 대해 생각해야한다는 생각이 들었지만 어떤 효과가 있는지 궁금합니다. 성능에 영향을 미칠 수 있습니다.

이 질문을하는 이유를 설명하는 데 도움이 될 수있는 몇 가지 배경 정보 :

  • 데이터는 실제로 웹 서비스를 통해 들어옵니다. 웹 서비스는 데이터베이스 서버의 공유 폴더에 텍스트 파일을 작성하여 BULK INSERT. 이 프로세스는 원래 SQL Server 2000에서 구현되었으며 당시에 INSERT는 서버에서 수백 개의 명령문을 처리하는 것 외에 다른 대안이 없었습니다. 실제로는 원래 프로세스 였고 성능에 문제가있었습니다.

  • 데이터는 영구 준비 테이블에 대량 삽입 된 다음 훨씬 더 큰 테이블에 병합됩니다 (그 후에는 준비 테이블에서 삭제됨).

  • 삽입 할 데이터의 양은 "큰"이지만 "거대한"것은 아닙니다. 일반적으로 몇 백 행, 드물게 5-10k 행이 맨 위에있을 수 있습니다. 따라서 내 직감은 BULK INSERT로그되지 않은 작업이 큰 차이를 만들지 않는다는 것 입니다 (물론 확실하지 않으므로 질문).

  • 삽입은 실제로 훨씬 더 큰 파이프 라인 배치 프로세스의 일부이며 연속적으로 여러 번 발생해야합니다. 따라서 성능 중요합니다.

BULK INSERTTVP 로 바꾸고 싶은 이유는 다음과 같습니다.

  • NetBIOS를 통해 텍스트 파일을 작성하는 데는 이미 시간이 많이 소요될 수 있으며 아키텍처 관점에서 보면 매우 끔찍합니다.

  • 나는 스테이징 테이블이 제거 될 수 있다고 믿습니다. 주된 이유는 삽입 된 데이터를 삽입과 동시에 몇 가지 다른 업데이트에 사용해야하기 때문이며, 거의 비어있는 스테이징을 사용하는 것보다 대규모 프로덕션 테이블에서 업데이트를 시도하는 것이 훨씬 더 비쌉니다. 표. TVP으로, 매개 변수는 기본적 이다 내가 주 삽입 후 내가 전에 그것으로 원하는 모든 것을 / 할 수있는 스테이징 테이블.

  • 중복 검사, 정리 코드 및 대량 삽입과 관련된 모든 오버 헤드를 거의 없앨 수 있습니다.

  • 서버가 이러한 트랜잭션 중 일부를 한 번에 가져 오는 경우 스테이징 테이블 또는 tempdb의 잠금 경합에 대해 걱정할 필요가 없습니다.

나는 분명히 이것을 생산에 투입하기 전에 프로파일 할 것이지만, 그 시간을 보내기 전에 먼저 물어 보는 것이 좋은 생각이라고 생각했습니다. 누군가가 이러한 목적으로 TVP를 사용하는 것에 대해 엄격한 경고가 있는지 확인하십시오.

그래서-SQL Server 2008에 대해 충분히 아는 사람이 이것을 시도하거나 적어도 조사한 적이 있다면 평결은 무엇입니까? 꽤 자주 발생하는 수백에서 수천 개의 행을 삽입하는 경우 TVP가 겨자를 자르나요? 벌크 인서트와 비교했을 때 성능에 큰 차이가 있습니까?


업데이트 : 이제 물음표가 92 % 감소했습니다!

(일명 : 테스트 결과)

최종 결과는 이제 36 단계 배포 프로세스처럼 느껴지는 프로덕션에 있습니다. 두 솔루션 모두 광범위하게 테스트되었습니다.

  • 공유 폴더 코드를 추출하고 SqlBulkCopy클래스를 직접 사용합니다 .
  • TVP를 사용하여 저장 프로 시저로 전환.

독자들이 정확히 무엇 을 테스트 했는지에 대한 아이디어 를 얻고이 데이터의 신뢰성에 대한 의구심을 완화 할 수 있도록 이 가져 오기 프로세스가 실제로 수행하는 작업에 대한 자세한 설명 다음과 같습니다.

  1. 일반적으로 약 20-50 개의 데이터 포인트 (때로는 수백 개까지 될 수 있지만) 인 시간 데이터 시퀀스로 시작합니다.

  2. 대부분 데이터베이스와 무관 한 미친 처리를 모두 수행하십시오. 이 프로세스는 병렬화되므로 (1)의 시퀀스 중 약 8-10 개가 동시에 처리됩니다. 각 병렬 프로세스는 3 개의 추가 시퀀스를 생성합니다.

  3. 3 개의 시퀀스와 원본 시퀀스를 모두 가져 와서 배치로 결합합니다.

  4. 8 ~ 10 개의 모든 처리 작업의 배치를 하나의 큰 수퍼 배치로 결합하십시오.

  5. BULK INSERT전략 (다음 단계 참조) 또는 TVP 전략 (8 단계로 건너 뛰기)을 사용하여 가져옵니다 .

  6. SqlBulkCopy클래스를 사용하여 전체 수퍼 배치를 4 개의 영구 스테이징 테이블에 덤프합니다.

  7. (a) 여러 JOIN조건을 포함하여 2 개의 테이블에서 여러 집계 단계를 수행 한 다음 (b) MERGE집계 된 데이터와 집계되지 않은 데이터를 모두 사용하여 6 개의 프로덕션 테이블에서 수행 하는 저장 프로 시저를 실행합니다 . (끝마친)

    또는

  8. DataTable병합 할 데이터를 포함하는 4 개의 개체를 생성 합니다. 그중 3 개에는 ADO.NET TVP에서 제대로 지원하지 않는 CLR 유형이 포함되어 있으므로 문자열 표현으로 삽입해야하므로 성능이 약간 저하됩니다.

  9. 기본적으로 (7)과 동일한 처리를 수행하지만 수신 된 테이블을 직접 사용하는 저장 프로 시저에 TVP를 공급합니다. (끝마친)

결과는 상당히 비슷했지만, TVP 접근 방식은 데이터가 1000 행을 조금 넘었을 때에도 평균적으로 더 나은 성과를 거두었습니다.

이 가져 오기 프로세스는 연속으로 수천 번 실행되므로 모든 병합을 완료하는 데 걸린 시간 (예, 시간)을 계산하여 평균 시간을 얻는 것이 매우 쉬웠습니다.

원래 평균 병합은 완료하는 데 거의 정확히 8 초가 걸렸습니다 (정상로드시). NetBIOS 클러지를 제거하고 전환 SqlBulkCopy하여 거의 정확히 7 초로 시간을 단축했습니다. TVP로 전환하면 배치 당 5.2 초로 시간이 더욱 단축되었습니다 . 이는 실행 시간이 몇 시간 단위로 측정되는 프로세스의 처리량 이 35 % 개선 된 것이므로 전혀 나쁘지 않습니다. 또한 SqlBulkCopy.

나는 실제로 진정한 개선이 이것보다 훨씬 더 많았다 고 상당히 확신합니다. 테스트 중에 최종 병합이 더 이상 중요한 경로가 아니라는 것이 분명해졌습니다. 대신 모든 데이터 처리를 수행하는 웹 서비스가 들어오는 요청 수에 따라 버클 링되기 시작했습니다. CPU와 데이터베이스 I / O 모두 실제로 최대 값이 초과되지 않았고 중요한 잠금 작업도 없었습니다. 어떤 경우에는 연속적인 병합 사이에 몇 초의 유휴 시간 간격이 나타났습니다. 약간의 간격이 있었지만을 사용할 때는 훨씬 더 작습니다 (0.5 초 정도) SqlBulkCopy. 그러나 나는 그것이 다른 날에 대한 이야기가 될 것이라고 생각합니다.

결론 : 테이블 값 매개 변수 BULK INSERT는 중간 크기의 데이터 세트에서 작동하는 복잡한 가져 오기 + 변환 프로세스의 작업 보다 실제로 더 나은 성능을 발휘 합니다.


프로 스테이징 테이블에있는 사람들의 걱정을 덜어주기 위해 다른 요점을 추가하고 싶습니다. 어떤면에서이 전체 서비스는 하나의 거대한 스테이징 프로세스입니다. 프로세스의 모든 단계는 심하게 감사되므로 특정 병합이 실패한 이유를 확인하기 위해 스테이징 테이블이 필요 하지 않습니다 (실제로는 거의 발생하지 않음). 우리가해야 할 일은 서비스에 디버그 플래그를 설정하는 것뿐입니다. 그러면 디버거가 중단되거나 데이터베이스 대신 파일에 데이터가 덤프됩니다.

즉, 우리는 이미 프로세스에 대한 충분한 통찰력을 가지고 있으며 스테이징 테이블의 안전성이 필요하지 않습니다. 처음에 스테이징 테이블이있는 유일한 이유는 다른 방법으로 사용해야했던 모든 INSERTUPDATE명령문 에 대한 스 래싱을 피하기 위해서였습니다 . 원래 프로세스에서 스테이징 데이터는 어쨌든 몇 분의 1 초 동안 만 스테이징 테이블에 있었으므로 유지 보수 / 유지 보수 측면에서 가치를 추가하지 않았습니다.

또한 우리가주의 하지 매일 교체 BULK INSERTTVPs와 작업을. 더 많은 양의 데이터를 처리하거나 데이터를 DB에 던지는 것 외에 특별한 작업을 수행 할 필요가없는 여러 작업은 여전히 SqlBulkCopy. 나는 TVP가 성능 만병 통치약이라고 말하는 것이 아니라 SqlBulkCopy초기 스테이징과 최종 병합 사이의 여러 변환을 포함하는이 특정 인스턴스에서 성공했다는 것 입니다.

그래서 거기에 있습니다. 포인트는 가장 관련성이 높은 링크를 찾기 위해 TToni로 이동하지만 다른 응답도 감사합니다. 다시 한 번 감사드립니다!


이것은 그 자체로 놀라운 질문입니다. 업데이트 부분에 대한 답변이 있어야한다고 생각합니다.)
Marc.2377

답변:


10

아직 TVP에 대한 경험은 없지만 MSDN의 BULK INSERT에 대한 멋진 성능 비교 차트가 있습니다 .

그들은 BULK INSERT가 시작 비용이 더 높지만 그 이후로는 더 빠르다고 말합니다. 원격 클라이언트 시나리오에서는 약 1000 개의 행에 선을 그립니다 ( "간단한"서버 로직의 경우). 그들의 설명으로 판단하면 TVP를 사용하는 것이 좋습니다. 성능 저하 (있는 경우)는 무시할 수있을 것이며 아키텍처상의 이점은 매우 좋아 보입니다.

편집 : 참고로 서버 로컬 파일을 피하고 SqlBulkCopy 개체를 사용하여 대량 복사를 계속 사용할 수 있습니다. DataTable을 채우고 SqlBulkCopy 인스턴스의 "WriteToServer"-Method에 공급하면됩니다. 사용하기 쉽고 매우 빠릅니다.


링크 덕분에 MS가 데이터가 복잡한 논리를 제공 할 때 TVP를 권장하는 것처럼 보이며 배치 크기를 늘리거나 줄일 수있는 기능이 있으므로 실제로는 매우 유용합니다. 1k 행의 문제점. 이를 바탕으로 너무 느려지더라도 적어도 시도해 볼 시간은 가치가 있습니다.
Aaronaught 2010-01-27

네, 링크가 흥미 롭습니다. @Aaronaught-이와 같은 상황에서는 항상 잠재적 인 접근 방식의 성능을 탐색하고 분석 할 가치가 있으므로 여러분의 결과를 듣고 싶습니다!
AdaTheDev

7

@TToni의 답변에 제공된 링크와 관련하여 언급 된 차트는 컨텍스트에서 가져와야합니다. 이러한 권장 사항에 대한 실제 연구가 얼마나되었는지 잘 모르겠습니다 (차트 는 해당 문서 의 20082008 R2버전 에서만 사용할 수있는 것으로 보입니다 ).

반면에 SQL Server 고객 자문 팀의 백서 : TVP를 통한 처리량 극대화

저는 2009 년부터 TVP를 사용해 왔으며 적어도 내 경험상 추가 논리가 필요하지 않은 대상 테이블에 대한 간단한 삽입 이외의 다른 경우 (드물게 드물게 발생하는 경우 임) TVP가 일반적으로 더 나은 옵션이라는 것을 발견했습니다.

데이터 유효성 검사가 앱 계층에서 수행되어야하므로 스테이징 테이블을 피하는 경향이 있습니다. TVP를 사용하면 쉽게 수용되고 저장 프로 시저의 TVP 테이블 변수는 본질적으로 지역화 된 스테이징 테이블입니다 (따라서 스테이징을 위해 실제 테이블을 사용할 때와 같이 동시에 실행되는 다른 프로세스와 충돌하지 않습니다). ).

질문에서 수행 된 테스트와 관련하여 원래 발견 된 것보다 훨씬 빠르다는 것을 보여줄 수 있다고 생각합니다.

  1. 응용 프로그램에서 값을 TVP로 보내는 것 외에 사용하지 않는 한 DataTable을 사용해서는 안됩니다. IEnumerable<SqlDataRecord>인터페이스를 사용하는 것이 더 빠르고 메모리를 적게 사용합니다. 수집을 메모리에 복제하지 않고 DB로 전송하는 것뿐입니다. 이 문서는 다음 위치에 있습니다.
  2. TVP는 테이블 변수이므로 통계를 유지하지 않습니다. 즉, 쿼리 최적화 프로그램에 1 개의 행만보고합니다. 따라서 프로 시저에서 다음 중 하나를 수행하십시오.
    • 간단한 SELECT 이외의 항목에 TVP를 사용하는 모든 쿼리에서 문 수준 재 컴파일을 사용합니다. OPTION (RECOMPILE)
    • 로컬 임시 테이블 (예 : single #)을 만들고 TVP의 내용을 임시 테이블에 복사합니다.

4

나는 여전히 대량 삽입 방식을 고수 할 것이라고 생각합니다. 적절한 수의 행이있는 TVP를 사용하여 tempdb가 여전히 적중되는 것을 알 수 있습니다. 이것은 내 직감입니다. TVP 사용 성능을 테스트했다고 말할 수는 없습니다 (다른 사람의 의견도 듣고 싶습니다).

.NET을 사용하는지 여부는 언급하지 않았지만 이전 솔루션을 최적화하기 위해 취한 접근 방식은 SqlBulkCopy 클래스를 사용하여 대량의 데이터로드를 수행하는 것이 었습니다. 이전에 먼저 파일에 데이터를 쓸 필요가 없습니다. 로드하는 경우 SqlBulkCopy 클래스 (예 : DataTable)에 데이터를 DB에 삽입하는 가장 빠른 방법입니다. 5-10K 행은 많지 않습니다. 최대 750K 행에 이것을 사용했습니다. 일반적으로 수백 개의 행이 있으면 TVP를 사용하여 큰 차이를 만들지 못할 것이라고 생각합니다. 그러나 확장은 IMHO로 제한됩니다.

SQL 2008 의 새로운 MERGE 기능이 도움이 될까요?

또한 기존 스테이징 테이블이이 프로세스의 각 인스턴스에 사용되는 단일 테이블이고 경합 등이 걱정된다면 매번 새로운 "임시"이지만 물리적 스테이징 테이블을 생성 한 다음 끝났어?

인덱스없이 채움으로써이 스테이징 테이블로의로드를 최적화 할 수 있습니다. 그런 다음 채워지면 해당 시점에 필요한 인덱스를 추가합니다 (최적의 읽기 성능을 위해 FILLFACTOR = 100,이 시점에서는 업데이트되지 않음).


나는 .NET을 사용하고 있으며 프로세스는 이전에 발생했으며 SqlBulkCopy결코 변경되지 않았습니다. 알려 주셔서 감사합니다. 다시 방문해 보시기 바랍니다. MERGE또한 이미 광범위하게 사용되고 있으며 임시 테이블은 이전에 한 번 시도되었지만 더 느리고 관리하기가 더 어렵다는 것을 알았습니다. 입력 해 주셔서 감사합니다!
Aaronaught 2010-01-27

-2

스테이징 테이블이 좋습니다! 정말 다른 방법으로하고 싶지 않습니다. 왜? 데이터 가져 오기가 예기치 않게 변경 될 수 있기 때문에 (예를 들어 열이 여전히 이름과 성으로 불렸지만 성 열에 이름 데이터가있는 경우와 같이 예측할 수없는 경우가 종종 있습니다. 무작위로.) 스테이징 테이블을 사용하여 문제를 쉽게 조사 할 수 있으므로 가져 오기가 처리 한 열에있는 데이터가 정확히 무엇인지 확인할 수 있습니다. 메모리 테이블을 사용하면 찾기 힘들다고 생각합니다. 저처럼 생계를 위해 수입을하는 많은 사람들을 알고 있으며 그들 모두는 준비 테이블 사용을 권장합니다. 나는 이것에 대한 이유가 있다고 생각합니다.

작업 프로세스에 대한 작은 스키마 변경 사항을 추가로 수정하는 것이 프로세스를 재 설계하는 것보다 쉽고 시간이 적게 걸립니다. 작동 중이고 아무도 그것을 변경하는 데 몇 시간을 지불하지 않으려는 경우 스키마 변경으로 인해 수정해야 할 부분 만 수정하십시오. 전체 프로세스를 변경하면 기존의 테스트 된 작업 프로세스를 약간 변경하는 것보다 훨씬 더 많은 잠재적 인 새로운 버그가 발생합니다.

그리고 모든 데이터 정리 작업을 어떻게 제거 하시겠습니까? 다르게 수행 할 수 있지만 여전히 수행해야합니다. 다시 말하지만, 설명하는 방식으로 프로세스를 변경하는 것은 매우 위험합니다.

개인적으로 새 장난감을 가지고 놀 수있는 기회를 얻지 않고 예전의 기술을 사용함으로써 기분이 상하는 것 같습니다. 벌크 삽입이 2000 년이 아닌 다른 변경을 원하는 실제 근거가없는 것 같습니다.


27
SQL 2008은 2 년 동안 사용되었으며이 프로세스는 오랫동안 사용되어 왔으며 변경을 고려한 것은 이번이 처음 입니다. 마지막에 비꼬는 말이 정말 필요 했나요?
Aaronaught
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.