Недостаточно памяти при создании большого количества объектов С#
Я обрабатываю 1 миллион записей в своем приложении, которые я извлекаю из базы данных MySQL. Для этого я использую Linq для получения записей и использования .Skip() и .Take() для обработки 250 записей за раз. Для каждой полученной записи мне нужно создать от 0 до 4 элементов, которые я затем добавляю в базу данных. Таким образом, среднее количество общих элементов, которые должны быть созданы, составляет около 2 миллионов.
IQueryable<Object> objectCollection = dataContext.Repository<Object>();
int amountToSkip = 0;
IList<Object> objects = objectCollection.Skip(amountToSkip).Take(250).ToList();
while (objects.Count != 0)
{
using (dataContext = new LinqToSqlContext(new DataContext()))
{
foreach (Object objectRecord in objects)
{
// Create 0 - 4 Random Items
for (int i = 0; i < Random.Next(0, 4); i++)
{
Item item = new Item();
item.Id = Guid.NewGuid();
item.Object = objectRecord.Id;
item.Created = DateTime.Now;
item.Changed = DateTime.Now;
dataContext.InsertOnSubmit(item);
}
}
dataContext.SubmitChanges();
}
amountToSkip += 250;
objects = objectCollection.Skip(amountToSkip).Take(250).ToList();
}
Теперь возникает проблема при создании элементов. При запуске приложения (и даже без использования dataContext) память постоянно увеличивается. Это похоже на то, что предметы никогда не попадают. Кто-нибудь замечает, что я делаю неправильно?
Спасибо заранее!
Ответы
Ответ 1
Хорошо, я только что обсуждал эту ситуацию с моим коллегой, и мы пришли к следующему решению, которое работает!
int amountToSkip = 0;
var finished = false;
while (!finished)
{
using (var dataContext = new LinqToSqlContext(new DataContext()))
{
var objects = dataContext.Repository<Object>().Skip(amountToSkip).Take(250).ToList();
if (objects.Count == 0)
finished = true;
else
{
foreach (Object object in objects)
{
// Create 0 - 4 Random Items
for (int i = 0; i < Random.Next(0, 4); i++)
{
Item item = new Item();
item.Id = Guid.NewGuid();
item.Object = object.Id;
item.Created = DateTime.Now;
item.Changed = DateTime.Now;
dataContext.InsertOnSubmit(item);
}
}
dataContext.SubmitChanges();
}
// Cumulate amountToSkip with processAmount so we don't go over the same Items again
amountToSkip += processAmount;
}
}
В этой реализации мы каждый раз используем кеш Skip() и Take() и, таким образом, не утечка памяти!
Ответ 2
Ahhh, хорошая старая утечка памяти InsertOnSubmit
. Я столкнулся с этим и много раз ударился головой о стену, пытаясь загрузить данные из больших CVS файлов, используя LINQ to SQL. Проблема в том, что даже после вызова SubmitChanges
, DataContext
продолжает отслеживать все объекты, которые были добавлены с помощью InsertOnSubmit
. Решение будет SubmitChanges
после определенного количества объектов, а затем создайте новый DataContext
для следующей партии. Когда старый DataContext
собирает мусор, так и все вставленные объекты, которые его отслеживают (и что вам больше не требуется).
"Но подождите!" вы говорите: "Создание и удаление многих DataContext будет иметь огромные накладные расходы!". Нет, если вы создадите одно соединение с базой данных и передаете его каждому конструктору DataContext
. Таким образом, единое соединение с базой данных поддерживается повсюду, а объект DataContext
в противном случае является легким объектом, который представляет собой небольшой рабочий блок и должен быть отброшен после его завершения (в вашем примере, отправив определенное количество записей).
Ответ 3
Мое лучшее предположение: IQueryable может вызвать утечку памяти.
Может быть, нет подходящей реализации для MySQL методов Take/Skip, и он выполняет пейджинг в памяти? Незнакомые вещи произошли, но ваша петля выглядит хорошо. Все ссылки должны выходить за рамки и собирать мусор.
Ответ 4
Ну да.
Итак, в конце этого цикла вы попытаетесь иметь 2 миллиона элементов в своем списке, нет? Мне кажется, что ответ тривиален: Храните меньше предметов или получите больше памяти.
- Изменить:
Возможно, я прочитал это неправильно, мне, вероятно, нужно будет скомпилировать и протестировать его, но я не могу этого сделать сейчас. Я оставлю это здесь, но я могу ошибаться, я недостаточно тщательно рассмотрел его, чтобы быть окончательным, но ответ может оказаться полезным, или нет. (Судя по нисходящему, я не думаю: P)
Ответ 5
Попробовали ли вы объявить элемент вне цикла следующим образом:
IQueryable<Object> objectCollection = dataContext.Repository<Object>();
int amountToSkip = 0;
IList<Object> objects = objectCollection.Skip(amountToSkip).Take(250).ToList();
Item item = null;
while (objects.Count != 0)
{
using (dataContext = new LinqToSqlContext(new DataContext()))
{
foreach (Object objectRecord in objects)
{
// Create 0 - 4 Random Items
for (int i = 0; i < Random.Next(0, 4); i++)
{
item = new Item();
item.Id = Guid.NewGuid();
item.Object = objectRecord.Id;
item.Created = DateTime.Now;
item.Changed = DateTime.Now;
dataContext.InsertOnSubmit(item);
}
}
dataContext.SubmitChanges();
}
amountToSkip += 250;
objects = objectCollection.Skip(amountToSkip).Take(250).ToList();
}