복붙노트

[SQL] SQL Server 데이터베이스 호출 멀티 스레딩 C # 응용 프로그램

SQL

SQL Server 데이터베이스 호출 멀티 스레딩 C # 응용 프로그램

나는 테이블 주에서 50 만 개 기록을 가진 SQL Server 데이터베이스가 있습니다. 자식 1, 자식 2, 및 child3라는 세 개의 다른 테이블도 있습니다. main_child1_relationship, main_child2_relationship 및 main_child3_relationship : 자식 1, 자식 2, child3 사이에 많은 관계로 많은 메인은 세 가지 관계 테이블을 통해 구현된다. 나는 주, 업데이트 주에서 레코드를 읽고, 또한 관계 테이블에 새 행으로뿐만 아니라 자식 테이블에 새 레코드 삽입 삽입해야합니다. 자식 테이블의 레코드 고유성 제약이 있기 때문에 실제 계산을위한 의사 코드 (CalculateDetails는) 같은 것을 할 것입니다 :

for each record in main
{
   find its child1 like qualities
   for each one of its child1 qualities
   {
      find the record in child1 that matches that quality
      if found
      {
          add a record to main_child1_relationship to connect the two records
      }
      else
      {
          create a new record in child1 for the quality mentioned
          add a record to main_child1_relationship to connect the two records
      }
   }
   ...repeat the above for child2
   ...repeat the above for child3 
}

이 단일 스레드 응용 프로그램으로 잘 작동합니다. 그러나 그것은 너무 느립니다. C #에서의 처리는 매우 중장비이고 너무 오래 걸린다. 나는 다중 스레드 응용 프로그램에이를 켜려고합니다.

이 작업을 수행하는 가장 좋은 방법은 무엇입니까? 우리는 LINQ to SQL은을 사용하고 있습니다.

지금까지 나의 접근 방식은 주에서 레코드의 각 배치에 대해 새의 DataContext 객체를 생성하고 처리 할 수 ​​ThreadPool.QueueUserWorkItem를 사용하는 것이 었습니다. 그러나 하나 개의 스레드가 레코드를 추가하고 다음 스레드 시도가 동일한 하나를 추가 할 수 있기 때문에 이러한 배치는 서로의 발가락에 스테핑하고 ... 나는 흥미있는 SQL 서버 죽은 잠금 장치의 모든 종류를 얻고있다.

여기에 코드입니다 :

    int skip = 0;
    List<int> thisBatch;
    Queue<List<int>> allBatches = new Queue<List<int>>();
    do
    {
        thisBatch = allIds
                .Skip(skip)
                .Take(numberOfRecordsToPullFromDBAtATime).ToList();
        allBatches.Enqueue(thisBatch);
        skip += numberOfRecordsToPullFromDBAtATime;

    } while (thisBatch.Count() > 0);

    while (allBatches.Count() > 0)
    {
        RRDataContext rrdc = new RRDataContext();

        var currentBatch = allBatches.Dequeue();
        lock (locker)  
        {
            runningTasks++;
        }
        System.Threading.ThreadPool.QueueUserWorkItem(x =>
                    ProcessBatch(currentBatch, rrdc));

        lock (locker) 
        {
            while (runningTasks > MAX_NUMBER_OF_THREADS)
            {
                 Monitor.Wait(locker);
                 UpdateGUI();
            }
        }
    }

그리고 여기 ProcessBatch입니다 :

    private static void ProcessBatch( 
        List<int> currentBatch, RRDataContext rrdc)
    {
        var topRecords = GetTopRecords(rrdc, currentBatch);
        CalculateDetails(rrdc, topRecords);
        rrdc.Dispose();

        lock (locker)
        {
            runningTasks--;
            Monitor.Pulse(locker);
        };
    }

    private static List<Record> GetTopRecords(RecipeRelationshipsDataContext rrdc, 
                                              List<int> thisBatch)
    {
        List<Record> topRecords;

        topRecords = rrdc.Records
                    .Where(x => thisBatch.Contains(x.Id))
                    .OrderBy(x => x.OrderByMe).ToList();
        return topRecords;
    }

CalculateDetails는 가장 상단에있는 의사 코드에 의해 설명된다.

나는 이것을 할 수있는 더 좋은 방법이있을 것 같아요. 도와주세요. 많은 감사합니다!

해결법

  1. ==============================

    1.여기에 문제에 대한 필자는 다음과 같습니다

    여기에 문제에 대한 필자는 다음과 같습니다

    교착 상태에 대한 몇 가지 이유 :

    이것은 내가 당신의 문제를 해결하는 방법입니다 :

    테이블이 자연적으로 데이터의 여러 별개의 세트로 분할 될 수 있다면, 당신도 SQL 서버는 테이블과 인덱스를 분할 사용하거나 수동으로 테이블의 여러 세트로 기존 테이블을 분할 할 수있다. 두 번째 옵션이 지저분한 것 때문에 나는, SQL 서버의 파티션을 사용하는 것이 좋습니다 것입니다. 또한 내장 된 파티셔닝은 SQL 엔터프라이즈 에디션에서만 사용할 수 있습니다.

    분할 당신을 위해 가능하다면, 당신은 8 개 별개의 세트를 말할 수 당신에게 데이터를 파산 partion을 방식을 선택할 수 있습니다. 지금 당신은 당신의 원래 하나의 스레드 코드를 사용하지만, 8 개 스레드를 별도의 파티션을 표적으로 각을 가질 수있다. 이제 교착 모든 (또는 적어도 최소 번호)가되지 않습니다.

    난 그 말이 바랍니다.

  2. ==============================

    2.문제의 뿌리는 엔티티 프레임 워크의 ObjectContext는 같은 L2S의 DataContext는, thread 세이프가되지 않는 것입니다. AS는 .NET ORM 솔루션의 비동기 작업에 대한 지원은 여전히 ​​.NET 4.0로 보류,이 MSDN 포럼 교환 설명; 당신은 당신이 발견 한대로 프레임 워크는 단일 쓰레드 특성을 가정 할 때 쉽게 할 수 항상없는 자신 만의 솔루션을 출시해야합니다.

    문제의 뿌리는 엔티티 프레임 워크의 ObjectContext는 같은 L2S의 DataContext는, thread 세이프가되지 않는 것입니다. AS는 .NET ORM 솔루션의 비동기 작업에 대한 지원은 여전히 ​​.NET 4.0로 보류,이 MSDN 포럼 교환 설명; 당신은 당신이 발견 한대로 프레임 워크는 단일 쓰레드 특성을 가정 할 때 쉽게 할 수 항상없는 자신 만의 솔루션을 출시해야합니다.

    단지 있는지 확인하기 위해, 자신을 개인적으로, 나는 그만큼 하위 계층과 직접 거래하는 것을 선호하고 SQL 쓰기 - 나는 L2S 자체가 완전히 비동기 작업을 지원하는 ADO.NET의 상단에 내장되어 있습니다이 기회를 할게요 나는 완전히 네트워크를 통해 발산되고 있었는지 이해.

    이 C #을 솔루션을해야합니다 - 존재가 말했다, 나는 물어 봐야? 당신은 삽입 / 업데이트 문장의 집합으로 솔루션을 구성 할 수 있다면, 당신은 단지 SQL을 직접 보내 수 및 스레딩 및 성능 문제가 사라진다. * 문제가 될 실제 데이터 변환에없는 관련이 나에게 보인다 만들었지 만, 센터 주변에 그들을 .NET에서 성능이 좋은 만들기. .NET 방정식에서 제거되면, 당신의 작업은 간단해진다. 결국, 가장 좋은 방법은 바로, 자주 코드의 작은 금액을 기입 한 일입니까? ;)

    논리가 엄격하게 설정 관계형 방식으로 표현할 수없는 넣 업데이트 / SQL Server가있는 경우에도 내장 메커니즘 레코드를 반복하고 로직을 수행하기위한 - 그들은 정당하게 많은 사용 사례에 대한 비방하는 동안, 커서의 수도 사실은 당신의 작업에 적합.

    이 반복적으로 발생하는 작업 인 경우에는 저장 프로 시저로 코딩에서 크게 혜택을 누릴 수 있습니다.

    * 물론, 장기 실행 SQL은 당신이 주장해야한다는 잠금 에스컬레이션 및 인덱스 사용과 같은 자신의 문제를 제공합니다.

    물론, 그것은 SQL에서이 일을하는 것은 밖으로 질문이라고 할 수있다 - 어쩌면 코드의 결정은, 예를 들어, 다른 곳에서 오는 데이터에 따라, 또는 어쩌면 당신의 프로젝트는 엄격한 '노 SQL-허용'규칙을 가지고있다. 당신은 몇 가지 일반적인 멀티 스레딩 버그를 언급하지만, 코드를 보지 않고 정말 특별히 그들과 함께 도움이 될 수 없습니다.

    C #을에서 이렇게하면 분명히 가능한,하지만 당신은 지연의 고정 금액이 각각 당신이 만드는 모든 호출을 위해 존재하는 것이라는 사실을 처리해야합니다. 여러 활성 결과 집합을 가능하게 풀링 된 연결을 사용하고 비동기 쿼리를 실행 / 종료 방법을 시작 사용하여 네트워크 대기 시간의 영향을 완화 할 수 있습니다. 심지어 그 모두와 함께, 당신은 여전히 ​​당신의 응용 프로그램에 SQL 서버에서 데이터를 운송하는 비용이 있음을 받아 들여야 할 것입니다.

    자체에 대한 모든 스테핑에서 코드를 유지하는 가장 좋은 방법 중 하나는 가능한 한 스레드 사이에 가변 데이터를 공유하지 않도록하는 것입니다. 즉 여러 스레드에서 동일한의 DataContext를 공유하지 않는 것을 의미한다. 최종 쓰기에 대한 첫 번째 읽기에서 모든 DataContext에 액세스 주위에 잠금 블록 - 다음 가장 좋은 방법은 중요 공유 데이터를 만지지 코드의 섹션을 잠그는 것입니다. 이러한 접근은 완전히 멀티 스레딩의 장점을 미연에 방지 할 수; 당신은 가능성이 세분화하여 잠금을 더 만들지 만, 너희가이 고통의 경로 경고가 될 수 있습니다.

    멀리 더 나은 작업이 완전히 서로 분리 유지하는 것입니다. 당신이 '주'기록에서 논리를 분할 할 수 있다면 그 이상이다 - 오래하지 다양한 자식 테이블 간의 관계, 그리고이 있기 때문에, 말을하는 것입니다에 대한 영향이없는 한 '주'의 기록 하나 다른 하나는,이 같은 여러 스레드에 걸쳐 작업을 분할 할 수 있습니다 :

    private IList<int> GetMainIds()
    {
        using (var context = new MyDataContext())
            return context.Main.Select(m => m.Id).ToList();
    }
    
    private void FixUpSingleRecord(int mainRecordId)
    {
        using (var localContext = new MyDataContext())
        {
            var main = localContext.Main.FirstOrDefault(m => m.Id == mainRecordId);
    
            if (main == null)
                return;
    
            foreach (var childOneQuality in main.ChildOneQualities)
            {
                // If child one is not found, create it
                // Create the relationship if needed
            }
    
            // Repeat for ChildTwo and ChildThree
    
            localContext.SaveChanges();
        }
    }
    
    public void FixUpMain()
    {
        var ids = GetMainIds();
        foreach (var id in ids)
        {
            var localId = id; // Avoid closing over an iteration member
            ThreadPool.QueueUserWorkItem(delegate { FixUpSingleRecord(id) });
        }
    }
    

    물론이 질문에서 의사만큼 장난감 예입니다, 그러나 희망은 그들 사이에 (또는 최소한의) 공유 상태가 없다는 것을 어떻게 범위로 작업 같은 생각을 가져옵니다. 즉, 생각, 올바른 C #을 솔루션의 핵심이 될 것입니다.

    당신은 데이터 일관성 문제를보고하는 경우, 내가 트랜잭션 의미를 시행 좋을 걸 - 당신이 System.Transactions.TransactionScope을 (System.Transactions를 참조를 추가)를 사용하여이 작업을 수행 할 수 있습니다. 다른 방법으로, 당신은 내부 연결에 액세스하고 그것을 들어 BeginTransaction을 호출하여 ADO.NET 수준에서이 작업을 수행 할 수있을 것 (또는 무엇이든 DataConnection 메서드가 호출됩니다).

    또한 교착 상태를 언급. 당신은 SQL 서버 교착 상태를 싸우고 있다는 것을 실제 SQL 쿼리가 서로의 발가락에 스테핑을 나타냅니다. 실제로 와이어를 통해 전송되는 것을 모른 채,이 일이 어떻게 문제를 해결하는 무엇을 구체적으로 말을하기는 어렵습니다. 충분는 SQL 교착 상태가 SQL 쿼리의 결과라고하고, 반드시 C # 스레딩 구조에서 - 정확히 와이어를 통해 무슨 일이 일어나고 있는지 검토 할 필요가있다. 내 직감은 각각 '주요'레코드가 다른 사람의 진정으로 독립적 인 경우, 다음 행과 테이블 잠금이 필요 없을 것을 저에게 말한다, 그리고 그 Linq은 SQL 여기 가능성이 범인입니다.

    당신은 뭔가 예에 DataContext.Log 속성을 설정하여 코드에서 L2S에 의해 방출되는 원시 SQL의 덤프를 얻을 수 있습니다 있는 console.out. 나는 개인적으로 사용한 적이 있지만, 나는 LINQPad의 이벤트 시설을 L2S 이해하고, 너무 거기에 SQL에서받을 수 있습니다.

    SQL Server Management Studio를 당신이 길의 나머지 얻을 것이다 - 활동 모니터를 사용하여, 당신은 실시간으로 잠금 에스컬레이션 볼 수 있습니다. 쿼리 분석기를 사용하면 SQL Server가 쿼리를 실행하는 방법을 정확하게의 전망을 얻을 수 있습니다. 그와 함께, 당신은 당신의 코드는 서버 측을 무엇을하고 있는지의 좋은 개념을 얻을 수 있어야하고, 다시 그것을 어떻게 해결에 대한 이동합니다.

  3. ==============================

    3.나도 SQL 서버에 모든 XML 처리를 이동 추천 할 것입니다. 모든 교착 상태가 사라지지만 다시 가고 싶지 않을 것임을 성능이 같은 후원을 볼 수 있습니다뿐만 아니라.

    나도 SQL 서버에 모든 XML 처리를 이동 추천 할 것입니다. 모든 교착 상태가 사라지지만 다시 가고 싶지 않을 것임을 성능이 같은 후원을 볼 수 있습니다뿐만 아니라.

    그것은 가장 좋은 예로 설명한다. 이 예에서 나는 XML의 덩어리가 이미 기본 테이블로 예정되어 있다고 가정 (나는 옷장 전화). 나는 다음과 같은 스키마를 가정합니다 :

    CREATE TABLE closet (id int PRIMARY KEY, xmldoc ntext) 
    CREATE TABLE shoe(id int PRIMARY KEY IDENTITY, color nvarchar(20))
    CREATE TABLE closet_shoe_relationship (
        closet_id int REFERENCES closet(id),
        shoe_id int REFERENCES shoe(id)
    )
    

    그리고 난 당신의 데이터 (기본 테이블 만 해당) 처음과 같이 보입니다 것으로 예상 :

    INSERT INTO closet(id, xmldoc) VALUES (1, '<ROOT><shoe><color>blue</color></shoe></ROOT>')
    INSERT INTO closet(id, xmldoc) VALUES (2, '<ROOT><shoe><color>red</color></shoe></ROOT>')
    

    그런 다음 전체 작업은 다음과 같은 간단하다 :

    INSERT INTO shoe(color) SELECT DISTINCT CAST(CAST(xmldoc AS xml).query('//shoe/color/text()') AS nvarchar) AS color from closet
    INSERT INTO closet_shoe_relationship(closet_id, shoe_id) SELECT closet.id, shoe.id FROM shoe JOIN closet ON CAST(CAST(closet.xmldoc AS xml).query('//shoe/color/text()') AS nvarchar) = shoe.color
    

    그러나 비슷한 처리를 많이 할 것이라는 점을 주어,이에 간단하게 추가 XML 형식으로 주요 덩어리를 선언하고,에 의해, 여러분의 인생을 더 쉽게 만들 수 있습니다 :

    INSERT INTO shoe(color)
        SELECT DISTINCT CAST(xmldoc.query('//shoe/color/text()') AS nvarchar)
        FROM closet
    INSERT INTO closet_shoe_relationship(closet_id, shoe_id)
        SELECT closet.id, shoe.id
        FROM shoe JOIN closet
            ON CAST(xmldoc.query('//shoe/color/text()') AS nvarchar) = shoe.color
    

    이 가능한 추가 성능 최적화를 반복해서 미리 계산 임시 또는 영구 테이블에 XPath는 결과를 호출, 또는 대량 삽입에 기본 테이블의 초기 인구를 변환처럼,하지만 난 당신이 성공하는 사람들을 필요로 정말 것이라고 기대하지 않습니다 .

  4. ==============================

    4.SQL 서버 교착 상태가 정상 이러한 유형의 시나리오에서 예상 할 수 있습니다 - MS의 추천이 응용 프로그램 측면보다는 dB 측에서 처리해야한다는 것입니다.

    SQL 서버 교착 상태가 정상 이러한 유형의 시나리오에서 예상 할 수 있습니다 - MS의 추천이 응용 프로그램 측면보다는 dB 측에서 처리해야한다는 것입니다.

    당신이해야합니까 그러나 확실 저장 프로 시저가 한 번만를 호출 해 있음은 sp_getapplock를 사용하여 SQL 뮤텍스 잠금을 사용할 수 있습니다. 다음은이를 구현하는 방법의 예

    BEGIN TRAN
    DECLARE @mutex_result int;
    EXEC @mutex_result = sp_getapplock @Resource = 'CheckSetFileTransferLock',
     @LockMode = 'Exclusive';
    
    IF ( @mutex_result < 0)
    BEGIN
        ROLLBACK TRAN
    
    END
    
    -- do some stuff
    
    EXEC @mutex_result = sp_releaseapplock @Resource = 'CheckSetFileTransferLock'
    COMMIT TRAN  
    
  5. ==============================

    5.이것은 분명하지만, 각 튜플 통해 반복하고 서블릿 컨테이너에서 작업을하는 것은 당 레코드 많은 오버 헤드를 포함 할 수있다.

    이것은 분명하지만, 각 튜플 통해 반복하고 서블릿 컨테이너에서 작업을하는 것은 당 레코드 많은 오버 헤드를 포함 할 수있다.

    가능하면, 하나 이상의 저장 프로 시저와 같은 논리를 다시 작성하여 SQL 서버에 그 처리의 일부 또는 전부를 이동합니다.

  6. ==============================

    6.만약

    만약

    그럼 ... 당신은 MSSQL이 잠금을 적용하지 않습니다 그래서 귀하의 질의에 "WITH NO LOCK"을 추가 할 수 있습니다.

    사용에주의해야합니다 :)

    시간이 (모노 스레드 버전) 분실되는 경우 어쨌든, 당신은 우리에게 말하지 않았다. 이 코드에 있다면, 나는 DB에서 모든 것을 작성하는 방법을 조언테니까 직접 연속 데이터 교환을 방지 할 수 있습니다. 이 DB에 있다면, 나는 (너무 많이?) 인덱스를 확인하기 위해 조언을 것입니다, I / O, CPU 등

  7. from https://stackoverflow.com/questions/9952137/multi-threading-c-sharp-application-with-sql-server-database-calls by cc-by-sa and MIT license