87

実行ごとに数千のレコードを持つインポートを実行しています。私の仮定の確認を探しているだけです:

これらのどれが最も理にかなっています:

  1. SaveChanges()すべてのAddToClassName()呼び出しを実行します。
  2. n回の呼び出しSaveChanges()ごとに実行します。AddToClassName()
  3. すべての呼び出しのSaveChanges()後に実行します。AddToClassName()

最初のオプションはおそらく遅いですよね?メモリ内のEFオブジェクトを分析する必要があるため、SQLなどを生成します。

2番目のオプションは、両方の長所であると思います。これは、その呼び出しの周りにtry catchをラップでき、一方が失敗した場合に一度にnSaveChanges()個のレコードしか失うことができないためです。たぶん、各バッチをリスト<>に保存します。呼び出しが成功した場合は、リストを削除します。失敗した場合は、アイテムをログに記録します。SaveChanges()

SaveChanges()最後のオプションも、が呼び出されるまですべてのEFオブジェクトがメモリ内にある必要があるため、おそらく非常に遅くなります。そして、保存が失敗した場合、何もコミットされませんよね?

4

5 に答える 5

67

確かに最初にテストします。パフォーマンスはそれほど悪くなくてもかまいません。

1つのトランザクションにすべての行を入力する必要がある場合は、すべてのAddToClassNameクラスの後で呼び出します。行を個別に入力できる場合は、すべての行の後に変更を保存します。データベースの一貫性は重要です。

私が好きではない2番目のオプション。システムにインポートした場合、(最終的なユーザーの観点から)混乱し、1が悪いという理由だけで、1000行のうち10行が減少します。10をインポートして失敗した場合は、1つずつ試してからログに記録してください。

時間がかかるかどうかをテストします。「適切に」と書かないでください。あなたはまだそれを知りません。それが実際に問題である場合にのみ、他の解決策(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()。
  • 1つのコミットが失敗した場合は、1つずつ試して、障害のある行を見つけてください。

テストクラス:

テーブル:

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);
    }
}
于 2009-12-18T22:22:42.477 に答える
20

私は自分のコードで非常によく似た問題を最適化したばかりで、自分に合った最適化を指摘したいと思います。

一度に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の間で変動するようにバッチごとにコミットされるレコードの数を調整できることがわかりました(コンシューマースレッドが1つ取った後)アイテム)。そうすれば、消費スレッドが最適に機能するのに十分な作業がありました。

もちろん、このシナリオでは、バッチごとに新しいコンテキストを作成する必要があります。これは、私のユースケースのシングルスレッドシナリオでも高速であることがわかりました。

于 2012-10-28T18:22:31.823 に答える
13

何千ものレコードをインポートする必要がある場合は、Entity Frameworkではなく、SqlBulkCopyのようなものを使用します。

于 2009-12-18T22:17:50.580 に答える
2

ストアドプロシージャを使用します。

  1. SQLServerでユーザー定義のデータ型を作成します。
  2. このタイプの配列を作成してコードに入力します(非常に高速です)。
  3. 1回の呼び出しで配列をストアドプロシージャに渡します(非常に高速)。

これが最も簡単で最速の方法だと思います。

于 2015-07-09T20:30:22.180 に答える
2

申し訳ありませんが、このスレッドは古いことは知っていますが、これはこの問題を抱えている他の人々を助けることができると思います。

同じ問題が発生しましたが、コミットする前に変更を検証する可能性があります。私のコードはこのように見え、正常に機能しています。私はchUser.LastUpdatedそれが新しいエントリなのか、それとも変更だけなのかをチェックします。まだデータベースにないエントリをリロードすることはできないためです。

// 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();
于 2017-06-14T09:32:31.840 に答える