web-dev-qa-db-ja.com

C#を使用してSQLServerの一時テーブルに3万行を挿入する最速の方法

C#を使用してSQLServerの一時テーブルへの挿入パフォーマンスを改善する方法を見つけようとしています。 SQLBulkCopyを使用する必要があると言う人もいますが、SQL挿入文字列を作成するよりも動作がはるかに遅いように見えるため、何か間違ったことをしているに違いありません。

SQLBulkCopyを使用してテーブルを作成するための私のコードは以下のとおりです。

public void MakeTable(string tableName, List<string> ids, SqlConnection connection)
    {

        SqlCommand cmd = new SqlCommand("CREATE TABLE ##" + tableName + " (ID int)", connection);
        cmd.ExecuteNonQuery();

        DataTable localTempTable = new DataTable(tableName);

        DataColumn id = new DataColumn();
        id.DataType = System.Type.GetType("System.Int32");
        id.ColumnName = "ID";
        localTempTable.Columns.Add(id);

        foreach (var item in ids)
        {
             DataRow row = localTempTable.NewRow();
             row[0] = item;
             localTempTable.Rows.Add(row);
             localTempTable.AcceptChanges();
        }


        using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connection))
        {
            bulkCopy.DestinationTableName = "##" + tableName;
            bulkCopy.WriteToServer(localTempTable);

        }
    }

このように、私の挿入物は実行するのに長い時間がかかります。別の方法でインサートをより速く動作させることができました:

挿入ビットを文字列として作成し、SQL create temptableステートメントに結合しました。

挿入文字列の作成:

public string prepareInserts(string tableName, List<string> ids)
    {
        List<string> inserts = new List<string>();

        var total = ids.Select(p => p).Count();
        var size = 1000;

        var insert = 1;

        var skip = size * (insert - 1);

        var canPage = skip < total;

        while (canPage)
        {
            inserts.Add(" insert into ##" + tableName + @" (ID) values " + String.Join(",", ids.Select(p => string.Format("({0})", p))
                        .Skip(skip)
                        .Take(size)
                        .ToArray()));
            insert++;
            skip = size * (insert - 1);
            canPage = skip < total;
        }

        string joinedInserts = String.Join("\r\n", inserts.ToArray());

        return joinedInserts;

    }

クエリを作成した後、SQLステートメントでそれらを使用します。

inserts = prepareInserts(tableName, ids);

var query = @"IF EXISTS
                                            (
                                            SELECT *
                                            FROM tempdb.dbo.sysobjects
                                            WHERE ID = OBJECT_ID(N'tempdb..##" + tableName + @"')
                                            )
                                                BEGIN
                                                    DELETE FROM ##" + tableName + @"
                                                END
                                            ELSE
                                                BEGIN
                                                    CREATE TABLE ##" + tableName + @"
                                                    (ID int)
                                                END " + inserts;

            var command = new SqlCommand(query, sqlConnection);
...

私は人々が私に言っているのを見たので(スタック交換で https://dba.stackexchange.com/questions/44217/fastest-way-to-insert-30-thousand-rows-in-sql-server/ 44222?noredirect = 1#comment78137_44222 )SQLBulkCopyを使用する必要があり、それがより高速になると私はそれを行う方法を改善する必要があると信じています。したがって、SQLBulkCopyコードを改善する方法を誰かが提案できる場合はORアプリケーションのパフォーマンスを改善できる、より優れた挿入ステートメントがあるかどうかを教えてください。

16
Jenninha

変更がコミットされるため、問題はlocalTempTable.AcceptChanges();にある可能性があります。
次のことをすれば、もっと速く走ると思います

    foreach (var item in ids)
    {
         DataRow row = localTempTable.NewRow();
         row[0] = item;
         localTempTable.Rows.Add(row);

    }

    localTempTable.AcceptChanges();

    using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connection))
    {
        bulkCopy.DestinationTableName = "##" + tableName;
        bulkCopy.WriteToServer(localTempTable);

    }

From MSDN --DataSet.AcceptChanges

このデータセットがロードされてから、または最後にAcceptChangesが呼び出されてから、このデータセットに加えられたすべての変更をコミットします。

14
Mzf

このコードをStopWatchオブジェクトを使用して自分で実行し、時間を測定します。遅くなるのは、すべての反復でのAcceptChangesです。

public void MakeTable(string tableName, List<string> ids, SqlConnection connection)
{
    SqlCommand cmd = new SqlCommand("CREATE TABLE ##" + tableName + " (ID int)", connection);
    cmd.ExecuteNonQuery();

    DataTable localTempTable = new DataTable(tableName);

    DataColumn id = new DataColumn();
    id.DataType = System.Type.GetType("System.Int32");
    id.ColumnName = "ID";
    localTempTable.Columns.Add(id);

    System.Diagnostics.Stopwatch sw1 = new System.Diagnostics.Stopwatch();        

    sw1.Start();
    foreach (var item in ids)
    {
        DataRow row = localTempTable.NewRow();
        row[0] = item;
        localTempTable.Rows.Add(row);

    }
    localTempTable.AcceptChanges();
    long temp1 = sw1.ElapsedMilliseconds;
    sw1.Reset();
    using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connection))
    {
        bulkCopy.DestinationTableName = "##" + tableName;
        bulkCopy.WriteToServer(localTempTable);

    }
    long temp2 = sw1.ElapsedMilliseconds;
}

AccpetChangesがforeachループ内にある場合の結果

enter image description here

そしてそうでないとき

enter image description here

違いは3桁です:)

4
Ron Biggs

IDataReader を使用すると、さらに高速に実行されます

cmd.ExecuteNonQuery();の代わりに実行

cmd.ExecuteReader()
0
Enfantcool