4

私はすでに知っているべきだと思うので、この質問をするのは恥ずかしいです. ただし、私はそうではありません.... OutOfMemory例外を発生させずに、ディスクからデータベースに大きなファイルを読み取る方法を知りたいです。具体的には、CSV (または実際にはタブ区切りファイル) を読み込む必要があります。

私はCSVReader特にこのコードサンプルを試していますが、間違っていると確信しています。彼らの他のコーディング サンプルのいくつかは、任意のサイズのストリーミング ファイルを読み取る方法を示しています。これは、私が望んでいるものです (ディスクから読み取る必要があるだけです) IDataReader

私はディスクから直接読み込んでいますが、一度に大量のデータを読み込んでメモリ不足にならないようにする試みを以下に示します。BufferedFileReaderファイルの場所を指定してバッファサイズを指定し、最初のパラメータとしてa をCsvDataReader期待できる a または同様のものを使用できるはずだと考えずにはいられません。それを使用することができます。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());
    }
4

6 に答える 6

3

実際、コードはファイルからすべてのデータを読み取り、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
于 2012-01-31T23:01:46.017 に答える
3

おそらくあなたが探している答えではありませんが、これがBULK INSERTが設計されたものです。

于 2012-02-02T18:08:45.310 に答える
1

readLine メソッドで BufferedFileReader を使用し、上記の方法で正確に実行することを追加します。

ここでの責任を基本的に理解しています。

BufferedFileReader は、ファイルからデータを読み取るクラスです (バッファに関して) LineReader もある必要があります。CSVReader は、正しい形式であると仮定してデータを読み取るためのユーティリティ クラスです。

とにかく使用しているSQlBulkCopy。

2 番目のオプション

データベースのインポート機能に直接アクセスできます。ファイルのフォーマットが正しければ、プログラムの穴ポイ​​ントはこれだけです。それも速いでしょう。

于 2012-02-02T09:47:24.233 に答える
1

データのサイズに問題があると思います。この問題に遭遇するたびに、それはデータのサイズではなく、データをループするときに作成されるオブジェクトの量です。

メソッド button3_Click(object sender, EventArgs e) 内で db にレコードを追加する while ループを調べます。

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

ここでは、反復ごとに 2 つのオブジェクトを宣言してインスタンス化します。つまり、読み取るファイルのチャンクごとに、200,000 個のオブジェクトをインスタンス化します。ガベージコレクターは追いつきません。

while ループの外側でオブジェクトを宣言しないのはなぜですか?

TextReader tr = null;
CsvDataReader csvData = null;

このように、GC は半分のチャンスを維持します。while ループをベンチマークすることで違いを証明できます。数千のオブジェクトを作成しただけで、大幅なパフォーマンスの低下に気付くことは間違いありません。

于 2012-02-02T19:53:37.473 に答える
0

擬似コード:

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 です:)。

操作が十分に大きく、途中で失敗する可能性が現実的である場合、または途中で失敗すると誰かに多大な費用がかかる可能性がある場合は、これまでに処理されたレコードの総数を 2 番目のテーブルにも書き込むことをお勧めします。ファイル(スキップしたものを含む) を同じトランザクションの一部として処理して、部分的に完了した場合に中断したところから再開できるようにします。

于 2012-02-03T04:44:33.027 に答える
0

csv の行を 1 つずつ読み取って 1 つずつ db に挿入する代わりに、チャンクを読み取ってデータベースに挿入することをお勧めします。ファイル全体が読み込まれるまで、このプロセスを繰り返します。

一度に 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
  }
}
于 2012-02-03T05:38:21.757 に答える