1000 개의 Entity Framework 개체를 만들 때 SaveChanges ()를 언제 호출해야합니까? (가져 오는 동안처럼)
각 실행에 대해 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 %를 넘지 않았습니다.
By offloading the saving to multiple threads, you have the ability to tune both the number of records prior to commit and the number of threads performing the commit operations.
I found that creating 1 producer thread and (# of CPU Cores)-1 consumer threads allowed me to tune the number of records committed per batch such that the count of items in the BlockingCollection fluctuated between 0 and 1 (after a consumer thread took one item). That way, there was just enough work for the consuming threads to work optimally.
This scenario of course requires creating a new context for every batch, which I find to be faster even in a single-threaded scenario for my use case.
If you need to import thousands of records, I'd use something like SqlBulkCopy, and not the Entity Framework for that.
- MSDN docs on SqlBulkCopy
- Use SqlBulkCopy to Quickly Load Data from your Client to SQL Server
- Transferring Data Using SqlBulkCopy
Use a stored procedure.
- Create a User-Defined Data Type in Sql Server.
- Create and populate an array of this type in your code (very fast).
- Pass the array to your stored procedure with one call (very fast).
I believe this would be the easiest and fastest way to do this.
Sorry, I know this thread is old, but I think this could help other people with this problem.
I had the same problem, but there is a possibility to validate the changes before you commit them. My code looks like this and it is working fine. With the chUser.LastUpdated
I check if it is a new entry or only a change. Because it is not possible to reload an Entry that is not in the database yet.
// 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();
'IT TIP' 카테고리의 다른 글
CUDA 고정 메모리가 그렇게 빠른 이유는 무엇입니까? (0) | 2020.10.24 |
---|---|
변수가 반복 가능하지만 문자열이 아니라는 것을 알려주는 방법 (0) | 2020.10.24 |
ASP.NET MVC에서 Error.cshtml은 어떻게 호출됩니까? (0) | 2020.10.24 |
어레이가 동일하게 유지 될 확률은 얼마입니까? (0) | 2020.10.24 |
-D 매개 변수 또는 환경 변수를 Spark 작업에 전달하는 방법은 무엇입니까? (0) | 2020.10.24 |