Как я могу прочитать большой файл с диска в базу данных без нехватки памяти - PullRequest
3 голосов
/ 24 января 2012

Мне стыдно задавать этот вопрос, так как я чувствую, что уже должен знать. Однако, учитывая, что я не ... Я хочу знать, как читать большие файлы с диска в базу данных, не получая исключение OutOfMemory. В частности, мне нужно загрузить CSV (или действительно файлы с разделителями табуляции).

Я экспериментирую с CSVReader и, в частности, с этим примером кода , но я уверен, что я делаю это неправильно. Некоторые из их других примеров кодирования показывают, как вы можете читать потоковые файлы любого размера, что довольно много, чего я хочу (только мне нужно читать с диска), но я не не знаю, какой тип IDataReader я мог бы создать, чтобы разрешить это.

Я читаю прямо с диска, и моя попытка убедиться, что у меня никогда не кончится память, читая слишком много данных сразу, приведена ниже. Я не могу удержаться от мысли, что я смогу использовать BufferedFileReader или что-то подобное, где я могу указать местоположение файла и указать размер буфера, а затем CsvDataReader ожидает IDataReader в качестве первого параметра, это может просто использовать это. Пожалуйста, покажите мне ошибку моих способов, позвольте мне избавиться от моего GetData метода с его произвольным механизмом разбиения файлов и помогите мне с этой основной проблемой.

    private void button3_Click(object sender, EventArgs e)
    {   
        totalNumberOfLinesInFile = GetNumberOfRecordsInFile();
        totalNumberOfLinesProcessed = 0; 

        while (totalNumberOfLinesProcessed < totalNumberOfLinesInFile)
        {
            TextReader tr = GetData();
            using (CsvDataReader csvData = new CsvDataReader(tr, '\t'))
            {
                csvData.Settings.HasHeaders = false;
                csvData.Settings.SkipEmptyRecords = true;
                csvData.Settings.TrimWhitespace = true;

                for (int i = 0; i < 30; i++) // known number of columns for testing purposes
                {
                    csvData.Columns.Add("varchar");
                }

                using (SqlBulkCopy bulkCopy = new SqlBulkCopy(@"Data Source=XPDEVVM\XPDEV;Initial Catalog=MyTest;Integrated Security=SSPI;"))
                {
                    bulkCopy.DestinationTableName = "work.test";

                    for (int i = 0; i < 30; i++)
                    {
                        bulkCopy.ColumnMappings.Add(i, i); // map First to first_name
                    }

                    bulkCopy.WriteToServer(csvData);

                }
            }
        }
    }

    private TextReader GetData()
    {
        StringBuilder result = new StringBuilder();
        int totalDataLines = 0;
        using (FileStream fs = new FileStream(pathToFile, FileMode.Open, System.IO.FileAccess.Read, FileShare.ReadWrite))
        {
            using (StreamReader sr = new StreamReader(fs))
            {
                string line = string.Empty;
                while ((line = sr.ReadLine()) != null)
                {
                    if (line.StartsWith("D\t"))
                    {
                        totalDataLines++;
                        if (totalDataLines < 100000) // Arbitrary method of restricting how much data is read at once.
                        {
                            result.AppendLine(line);
                        }
                    }
                }
            }
        }
        totalNumberOfLinesProcessed += totalDataLines;
        return new StringReader(result.ToString());
    }

Ответы [ 6 ]

3 голосов
/ 02 февраля 2012

Вероятно, не тот ответ, который вы ищете, но для этого BULK INSERT был разработан.

3 голосов
/ 01 февраля 2012

На самом деле ваш код читает все данные из файла и сохраняет в TextReader (в памяти).Затем вы читаете данные с TextReader на сервер сохранения.

Если данные настолько велики, размер данных в TextReader вызван нехваткой памяти.Пожалуйста, попробуйте этот способ.

1) Считайте данные (каждую строку) из файла.

2) Затем вставьте каждую строку в сервер.

Проблема с нехваткой памяти будет решенапотому что только каждая запись в памяти при обработке.

Псевдокод

begin tran

While (data = FilerReader.ReadLine())
{
  insert into Table[col0,col1,etc] values (data[0], data[1], etc)
}

end tran
1 голос
/ 02 февраля 2012

Я думаю, у вас может быть красная сельдь с размером данных.Каждый раз, когда я сталкиваюсь с этой проблемой, это не размер данных, а количество объектов, созданных при циклическом цикле данных.

Просмотрите свой цикл while, добавляя записи в БД в методе button3_Click (отправитель объекта, EventArgs e):

TextReader tr = GetData();
using (CsvDataReader csvData = new CsvDataReader(tr, '\t'))

Здесь вы объявляете и создаете экземпляры двух объектов на каждой итерации - то есть для каждого фрагмента файла, который вы читаете, создается 200 000 объектов;сборщик мусора не будет работать.

Почему бы не объявить объекты вне цикла while?

TextReader tr = null;
CsvDataReader csvData = null;

Таким образом, gc будет иметь половину шансов.Вы можете доказать разницу, сравнив цикл while, вы без сомнения заметите значительное снижение производительности после того, как создали всего пару тысяч объектов.

1 голос
/ 02 февраля 2012

Я бы просто добавил, используя BufferedFileReader с методом readLine и действуя точно так же, как описано выше.

По существу, здесь понимаем resposnisbilties.

BufferedFileReader - это класс, считывающий данные из файла (buffe wise) Также должен быть LineReader.CSVReader является утилитарным классом для чтения данных, при условии, что он в правильном формате.

SQlBulkCopy, который вы в любом случае используете.

Второй параметр

Вы можетеперейти к объекту импорта базы данных напрямую.Если формат файла правильный, и точка программы - это только.это тоже будет быстрее.

0 голосов
/ 03 февраля 2012

Вместо того, чтобы читать строки csv одну за другой и вставлять в базу данных одну за другой, я предлагаю прочитать кусок и вставить его в базу данных. Повторяйте этот процесс, пока весь файл не будет прочитан.

Вы можете буферизовать в памяти, скажем, 1000 строк CSV за один раз, а затем вставить их в базу данных.

int MAX_BUFFERED=1000;
int counter=0;
List<List<String>> bufferedRows= new ...

while (scanner.hasNext()){
  List<String> rowEntries= getData(scanner.getLine())
  bufferedRows.add(rowEntries);

  if (counter==MAX_BUFFERED){
    //INSERT INTO DATABASE
    //append all contents to a string buffer and create your SQL INSERT statement
    bufferedRows.clearAll();//remove data so it could be GCed when GC kicks in
  }
}
0 голосов
/ 03 февраля 2012

псевдокод:

while (!EOF) {
   while (chosenRecords.size() < WRITE_BUFFER_LIST_SIZE) {
      MyRecord record = chooseOrSkipRecord(file.readln());
      if (record != null) {
         chosenRecords.add(record)
      }
   }  
   insertRecords(chosenRecords) // <== writes data and clears the list
}

WRITE_BUFFER_LIST_SIZE - это просто константа, которую вы устанавливаете ... больше означает большие партии, а меньшее означает меньшие партии. Размер 1 - это RBAR :).

Если ваша операция достаточно велика, чтобы сбой на полпути был реалистичной возможностью, или если сбой на полпути мог стоить кому-то нетривиальной суммы денег, вы, вероятно, захотите также записать во вторую таблицу общее количество обработанных записей до сих пор из файла (включая пропущенные вами) как часть одной и той же транзакции, чтобы вы могли выбрать, где остановились в случае частичного завершения.

...