각 실행에 대해 1000 개의 레코드가있는 가져 오기를 실행하고 있습니다. 내 가정에 대한 확인을 찾고 있습니다.
다음 중 가장 의미가있는 것은 무엇입니까?
SaveChanges()
모든AddToClassName()
통화를 실행하십시오 .- n 번의 호출
SaveChanges()
마다 실행 합니다.AddToClassName()
- 실행
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와 같은 것을 사용합니다.
답변
저장 프로 시저를 사용하십시오.
- SQL Server에서 사용자 정의 데이터 형식을 만듭니다.
- 코드에서이 유형의 배열을 만들고 채 웁니다 (매우 빠름).
- 한 번의 호출로 저장 프로 시저에 배열을 전달합니다 (매우 빠름).
이것이 가장 쉽고 빠른 방법이라고 생각합니다.
답변
죄송합니다.이 스레드가 오래되었다는 것을 알고 있지만이 문제가 다른 사람들에게 도움이 될 수 있다고 생각합니다.
나는 같은 문제가 있었지만 커밋하기 전에 변경 사항을 확인할 가능성이 있습니다. 내 코드는 다음과 같이 잘 작동합니다. 으로 chUser.LastUpdated
I 검사는 새 항목 또는 변경 만있는 경우. 아직 데이터베이스에없는 항목을 다시로드 할 수 없기 때문입니다.
// 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();