Bulk Insert

作っているアプリケーションで一部バッチ的な処理があるんですが。
OracleにODP.NETでアクセスして、データを何万件か登録するという処理です。


で、普通にOracleCommandでINSERTしてる分には200〜300TPSくらいしかでないです。
まあ、パフォーマンス的にはそんなもんでしょうけど(´ω`)
#環境の詳細は省きますが、特殊な設定をしていない普通のPCです

// 実験用ソース1
using( OracleConnection conn = new OracleConnection( OracleHelper.ConnectionString ) ) 
using( OracleCommand cmd = new OracleCommand() )
using( OracleParameter parameter1 = new OracleParameter( ":id", OracleDbType.Int32 ) )
using( OracleParameter parameter2 = new OracleParameter( ":data", OracleDbType.NVarchar2, 50 ) )
{
    conn.Open();

    cmd.Connection = conn;
    cmd.CommandText = "INSERT INTO ptest ( id, data ) VALUES ( :id, :data )";
    cmd.CommandType = CommandType.Text;

    cmd.Parameters.Add( parameter1 );
    cmd.Parameters.Add( parameter2 );

    for( int count = 0; count < 10000; count++ )
    {
        cmd.Parameters[ 0 ].Value = count;
        cmd.Parameters[ 1 ].Value = "data" + count.ToString();
    
        int val = cmd.ExecuteNonQuery();
}

で、性能を上げる方法を調べたら、OracleCommandはバッチ配列に対応してるんですね。
OracleCommand.ArrayBindCountに処理件数を設定して、パラメータにも件数分の配列を設定。
これで処理を実行したところ、処理性能は10倍(当社比)にアップしてイイ感じ(・∀・)

// 実験用ソース2
using( OracleConnection conn = new OracleConnection( OracleHelper.ConnectionString ) ) 
using( OracleCommand cmd = new OracleCommand() )
using( OracleParameter parameter1 = new OracleParameter( ":id", OracleDbType.Int32 ) )
using( OracleParameter parameter2 = new OracleParameter( ":data", OracleDbType.NVarchar2, 50 ) )
{
    conn.Open();

    cmd.Connection = conn;
    cmd.CommandText = "INSERT INTO ptest ( id, data ) VALUES ( :id, :data )";
    cmd.CommandType = CommandType.Text;

    cmd.Parameters.Add( parameter1 );
    cmd.Parameters.Add( parameter2 );

    int bindCount = 1000;
    for( int count = 0; count < 10000; count += bindCount )
    {
        cmd.ArrayBindCount = bindCount;
        int[] param1 = new int[ bindCount ];
        string[] param2 = new string[ bindCount ];
        for( int i = 0; i < bindCount; i++ )
        {
            param1[ i ] = count + i;
            param2[ i ] = "data" + ( count + i ).ToString();;
        }

        cmd.Parameters[ 0 ].Value = param1;
        cmd.Parameters[ 1 ].Value = param2;
    
        int val = cmd.ExecuteNonQuery();
    }
}

あとバッチ配列を使用しない場合のパフォーマンスについて比較しておくと、OracleCommandやOracleParameterを使い回す場合とそうでない場合で20%くらいの性能差がありました。
思ったより差があるもんですが。


ちなみにBulk Insertをやる場合、ADO.NET 2.0でSQL Serverが相手ならSqlBulkCopyを使うって所ですね(´∀`)