[entity-framework] 1000 개의 Entity Framework 개체를 만들 때 SaveChanges ()를 언제 호출해야합니까? (가져 오는 동안처럼)

각 실행에 대해 1000 개의 레코드가있는 가져 오기를 실행하고 있습니다. 내 가정에 대한 확인을 찾고 있습니다.

다음 중 가장 의미가있는 것은 무엇입니까?

  1. SaveChanges()모든 AddToClassName()통화를 실행하십시오 .
  2. n 번의 호출 SaveChanges()마다 실행 합니다.AddToClassName()
  3. 실행 SaveChanges()모든AddToClassName()호출.

첫 번째 옵션은 아마 느립니다. 메모리의 EF 개체를 분석하고 SQL을 생성하는 등의 작업이 필요하기 때문입니다.

두 번째 옵션이 두 세계 모두에서 최고라고 가정합니다. 그 SaveChanges()호출에 대해 try catch를 래핑하고 둘 중 하나가 실패 할 경우 한 번에 n 개의 레코드 만 잃을 수 있기 때문입니다. 각 배치를 List <>에 저장할 수 있습니다. 경우 SaveChanges()호출이 성공 목록을 없애. 실패하면 항목을 기록하십시오.

모든 단일 EF 개체 SaveChanges()가 호출 될 때까지 메모리에 있어야하므로 마지막 옵션도 매우 느려질 수 있습니다 . 그리고 저장이 실패하면 아무것도 커밋되지 않을 것입니다.



답변

나는 그것을 확인하기 위해 먼저 테스트 할 것입니다. 성능이 그렇게 나쁠 필요는 없습니다.

하나의 트랜잭션에 모든 행을 입력해야하는 경우 모든 AddToClassName 클래스 다음에 호출하십시오. 행을 독립적으로 입력 할 수있는 경우 모든 행 후에 변경 사항을 저장합니다. 데이터베이스 일관성이 중요합니다.

두 번째 옵션이 마음에 들지 않습니다. 시스템으로 가져 오기를 수행하면 (최종 사용자 관점에서) 혼란스럽고 1이 나쁘기 때문에 1000 행 중 10 행이 감소합니다. 10 개 가져 오기를 시도 할 수 있으며 실패 할 경우 하나씩 시도한 다음 기록합니다.

시간이 오래 걸리는지 테스트하십시오. ‘적절하게’쓰지 마십시오. 당신은 아직 그것을 모릅니다. 실제로 문제가되는 경우에만 다른 솔루션 (marc_s)을 생각하십시오.

편집하다

몇 가지 테스트를 수행했습니다 (밀리 초 단위).

10000 개의 행 :

1 행 이후 SaveChanges () : 18510,534
100 행 이후
SaveChanges () : 4350,3075 10000 행 이후 SaveChanges () : 5233,0635

50000 행 :

1 행 이후 SaveChanges () : 78496,929
500 행 이후

SaveChanges () : 22302,2835 50000 행 이후 SaveChanges () : 24022,8765

따라서 실제로는 결국 n 행 이후에 커밋하는 것이 더 빠릅니다.

내 추천은 다음과 같습니다.

  • n 행 이후 SaveChanges ().
  • 하나의 커밋이 실패하면 하나씩 시도하여 잘못된 행을 찾습니다.

테스트 클래스 :

표:

CREATE TABLE [dbo].[TestTable](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [SomeInt] [int] NOT NULL,
    [SomeVarchar] [varchar](100) NOT NULL,
    [SomeOtherVarchar] [varchar](50) NOT NULL,
    [SomeOtherInt] [int] NULL,
 CONSTRAINT [PkTestTable] PRIMARY KEY CLUSTERED
(
    [ID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

수업:

public class TestController : Controller
{
    //
    // GET: /Test/
    private readonly Random _rng = new Random();
    private const string _chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

    private string RandomString(int size)
    {
        var randomSize = _rng.Next(size);

        char[] buffer = new char[randomSize];

        for (int i = 0; i < randomSize; i++)
        {
            buffer[i] = _chars[_rng.Next(_chars.Length)];
        }
        return new string(buffer);
    }


    public ActionResult EFPerformance()
    {
        string result = "";

        TruncateTable();
        result = result + "SaveChanges() after 1 row:" + EFPerformanceTest(10000, 1).TotalMilliseconds + "<br/>";
        TruncateTable();
        result = result + "SaveChanges() after 100 rows:" + EFPerformanceTest(10000, 100).TotalMilliseconds + "<br/>";
        TruncateTable();
        result = result + "SaveChanges() after 10000 rows:" + EFPerformanceTest(10000, 10000).TotalMilliseconds + "<br/>";
        TruncateTable();
        result = result + "SaveChanges() after 1 row:" + EFPerformanceTest(50000, 1).TotalMilliseconds + "<br/>";
        TruncateTable();
        result = result + "SaveChanges() after 500 rows:" + EFPerformanceTest(50000, 500).TotalMilliseconds + "<br/>";
        TruncateTable();
        result = result + "SaveChanges() after 50000 rows:" + EFPerformanceTest(50000, 50000).TotalMilliseconds + "<br/>";
        TruncateTable();

        return Content(result);
    }

    private void TruncateTable()
    {
        using (var context = new CamelTrapEntities())
        {
            var connection = ((EntityConnection)context.Connection).StoreConnection;
            connection.Open();
            var command = connection.CreateCommand();
            command.CommandText = @"TRUNCATE TABLE TestTable";
            command.ExecuteNonQuery();
        }
    }

    private TimeSpan EFPerformanceTest(int noOfRows, int commitAfterRows)
    {
        var startDate = DateTime.Now;

        using (var context = new CamelTrapEntities())
        {
            for (int i = 1; i <= noOfRows; ++i)
            {
                var testItem = new TestTable();
                testItem.SomeVarchar = RandomString(100);
                testItem.SomeOtherVarchar = RandomString(50);
                testItem.SomeInt = _rng.Next(10000);
                testItem.SomeOtherInt = _rng.Next(200000);
                context.AddToTestTable(testItem);

                if (i % commitAfterRows == 0) context.SaveChanges();
            }
        }

        var endDate = DateTime.Now;

        return endDate.Subtract(startDate);
    }
}


답변

방금 내 코드에서 매우 유사한 문제를 최적화했으며 저에게 효과가있는 최적화를 지적하고 싶습니다.

한 번에 100 개 또는 1000 개의 레코드를 처리하든 SaveChanges를 처리하는 데 많은 시간이 CPU 제한이라는 것을 알았습니다. 따라서 생산자 / 소비자 패턴 (BlockingCollection으로 구현 됨)으로 컨텍스트를 처리함으로써 CPU 코어를 훨씬 더 잘 사용할 수 있었고 초당 총 4000 회의 변경 (SaveChanges의 반환 값으로보고 됨)에서 초당 14,000 개 이상의 변경. CPU 사용률이 약 13 % (코어가 8 개 있음)에서 약 60 %로 이동했습니다. 여러 소비자 스레드를 사용하더라도 (매우 빠른) 디스크 IO 시스템에 거의 부담을주지 않았고 SQL Server의 CPU 사용률은 15 %를 넘지 않았습니다.

저장을 여러 스레드로 오프로드하면 커밋 이전의 레코드 수와 커밋 작업을 수행하는 스레드 수를 모두 조정할 수 있습니다.

1 개의 생산자 스레드와 (CPU 코어 수) -1 개의 소비자 스레드를 생성하면 BlockingCollection의 항목 수가 0과 1 사이에서 변동하도록 배치 당 커밋 된 레코드 수를 조정할 수 있다는 것을 발견했습니다 (소비자 스레드가 안건). 그렇게하면 소비하는 스레드가 최적으로 작동 할 수있는 작업이 충분했습니다.

물론이 시나리오에서는 모든 배치에 대해 새로운 컨텍스트를 만들어야하는데, 이는 제 사용 사례에 대한 단일 스레드 시나리오에서도 더 빠릅니다.


답변

수천 개의 레코드를 가져와야하는 경우 Entity Framework가 아닌 SqlBulkCopy와 같은 것을 사용합니다.


답변

저장 프로 시저를 사용하십시오.

  1. SQL Server에서 사용자 정의 데이터 형식을 만듭니다.
  2. 코드에서이 유형의 배열을 만들고 채 웁니다 (매우 빠름).
  3. 한 번의 호출로 저장 프로 시저에 배열을 전달합니다 (매우 빠름).

이것이 가장 쉽고 빠른 방법이라고 생각합니다.


답변

죄송합니다.이 스레드가 오래되었다는 것을 알고 있지만이 문제가 다른 사람들에게 도움이 될 수 있다고 생각합니다.

나는 같은 문제가 있었지만 커밋하기 전에 변경 사항을 확인할 가능성이 있습니다. 내 코드는 다음과 같이 잘 작동합니다. 으로 chUser.LastUpdatedI 검사는 새 항목 또는 변경 만있는 경우. 아직 데이터베이스에없는 항목을 다시로드 할 수 없기 때문입니다.

// Validate Changes
var invalidChanges = _userDatabase.GetValidationErrors();
foreach (var ch in invalidChanges)
{
    // Delete invalid User or Change
    var chUser  =  (db_User) ch.Entry.Entity;
    if (chUser.LastUpdated == null)
    {
        // Invalid, new User
        _userDatabase.db_User.Remove(chUser);
        Console.WriteLine("!Failed to create User: " + chUser.ContactUniqKey);
    }
    else
    {
        // Invalid Change of an Entry
        _userDatabase.Entry(chUser).Reload();
        Console.WriteLine("!Failed to update User: " + chUser.ContactUniqKey);
    }
}

_userDatabase.SaveChanges();


답변