C# SQLBulkCopy 기능에 대해서

데이터 동기화를 위해서 SQLBulkCopy를 쓰면서 의아했던 점,

그리고 궁금했던 점을 하나씩 짚고 넘어가면서

최선의 방법을 찾아보자는 생각으로 시작해봅니다.

p.s @Vincent 님 덕분에 공부 많이 됐습니다. 감사합니다!

3개의 좋아요

BatchSize를 조정하는 이유
DB의 부담을 줄이기 위해서도 맞는 말이다. 실제 BatchSize를 조절해서 테스트한 결과에 따르면 Batch 속도의 차이가 많이 난다.

하지만 정작 중요한 이유는 따로 있더라.

DB 데이터는 어찌됐든 TCP로 전송된다.
한번에 주고 받을 수 있는 길이의 한계가 존재한다.

결국 BatchSize의 조정은 네트워크 대응을 위해 반드시 필요한 것 같다.
확실한지는 모르겠지만 다양한 네트웍 환경에서 BatchSize를 다르게 해서 적용해보면서 경과를 지켜보려 한다.
네트웍이 불안정하거나, 내부 트래픽의 제한이 있는 망에서 테스트한 결과 BatchSize를 3,000 으로만 해도 문제가 되는 것을 확인했다.

5개의 좋아요

관련 있는 내용인 것 같습니다.

2개의 좋아요

올려주신 해당 글의 요지는 ‘개발자는 MSS에 대해 그렇게 신경 쓸 필요가 없다’ 인 것 같습니다.
그리고 그게 맞는 말씀 같습니다…

2개의 좋아요

시간이 조금 지났지만, 후기를 반드시 올려야 할 것 같은 압박감에 댓글을 써봅니다.
결론적으로는 BatchSize의 조정도, MSS를 고려한 효율적인 설계 또한 필요가 없었습니다!!
단지 BulkCopy를 하는 과정에서 '보안프로그램’이라는 무시무시한 놈이 방해공작을 펼치고 있다는 사실을 알게 되었고…
(BulkCopy를 안하는데도 이미지를 저장하는 로직에서 멍때리는 것 보고 직감했음…)
BulkCopy를 과감하게(?) 포기를 하고 상위 데이터베이스에 직접 연결해 그때그때 select하고 처리하는 방식으로 우회하게 되었습니다.

프로젝트의 결과적으로는 대성공이지만… 씁쓸합니다…
DB Write하는 과정 조차 보안프로그램이라는 놈이 이렇게 방해를 할 줄 몰랐으니까요…

5개의 좋아요

윗 댓글에서 TCP와 통신 데이터 사이즈를 언급하셔서 링크 드린 것입니다.

3개의 좋아요

프로젝트 성공하신 것 축하드리고…저도 보안 프로그램이 그런 것도 할 수 있다는 것을 배울 수 있었습니다.

후기 감사드립니다.

3개의 좋아요

네 알고 있습니다. 감사합니다.

4개의 좋아요

늦은 후기를 또 올리자면, 상위 DB에 필요시마다 Select하지 않고

BulkCopy를 통해 테이블을 복사해서 쓰도록 해놓았으며, 현재 4개월 넘게 잘 돌아가고 있습니다.

명색이 slog인데, 소스코드 하나도 없이 올렸었네요…

늦게나마 현재 사용중인 소스코드 올려봅니다.


        public bool BulkInsertDataTable(string tableName, DataTable dataTable)
        {
            try
            {
                using (SqlConnection Conn = new SqlConnection(global.LocalConnStr))
                {
                    Conn.Open();

                    // 주로 쓰는 SqlBulkCopyOptions : KeepIdentity, UseInternalTransaction, TableLock.
                    // InternalTransaction을 쓴다면 TableLock은 비활성화 하자.
                    using (SqlBulkCopy bulkCopy = new SqlBulkCopy(Conn, SqlBulkCopyOptions.KeepIdentity | SqlBulkCopyOptions.UseInternalTransaction, null))
                    {
                        // 테이블을 먼저 비운다 (원본에는 이미 삭제되어도, 복사본에 데이터가 계속 남을 수도 있어서 복사본 테이블을 비우도록 함)
                        using (SqlCommand cmd = new SqlCommand($"Truncate table {tableName}", Conn))
                        {
                            cmd.CommandTimeout = 5;
                            cmd.CommandType = CommandType.Text;
                            int affected = cmd.ExecuteNonQuery();
                        }

                        // 원래는 BatchSize 문제인가 싶어서 크기 조정으로 조져보려고 하다가 그게 아님을 깨닫고 주석 처리.
                        // bulkCopy.BatchSize = config.instance.DB_SYNC_UNIT;
                        bulkCopy.BulkCopyTimeout = 5;
                        bulkCopy.DestinationTableName = tableName;

                        try
                        {
                            bulkCopy.WriteToServer(dataTable);
                            WriteLog($"[BulkInsertDataTable] {tableName} - 완료");
                        }
                        catch (Exception ex)
                        {
                            WriteLog(ex);
                            WriteLog($"[BulkInsertDataTable] {tableName} - Commit 실패!");
                            return false;
                        }
                        finally
                        {
                            bulkCopy.Close(); // 지금 보니 using문을 써놔서 finally 부분은 필요없네요..
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                WriteLog(ex);
                return false;
            }

            return true;
        }

6개의 좋아요