31

.NET を使用して Oracle に一括挿入する最速の方法は何ですか? .NET を使用して約 160K のレコードを Oracle に転送する必要があります。現在、insert ステートメントを使用して 160K 回実行しています。完了するまでに約 25 分かかります。ソース データは、別のデータベース (MySQL) からのクエリの結果として、DataTable に格納されます。

これを行うより良い方法はありますか?

編集:私は現在 System.Data.OracleClient を使用していますが、別のプロバイダー (ODP.NET、DevArt など) を使用したソリューションを受け入れる用意があります。

4

10 に答える 10

29

ODP.NET の配列バインディングを使用して、15 秒ほどで 50,000 レコードをロードしています

指定した (更新/挿入/削除を実行できる) ストアド プロシージャを繰り返し呼び出すことによって機能しますが、複数のパラメーター値を .NET からデータベースに一括で渡します。

ストアド プロシージャの各パラメーターに単一の値を指定する代わりに、各パラメーターに値の配列を指定します。

Oracle は、パラメータ配列を .NET からデータベースに一度に渡し、指定したパラメータ値を使用して、指定したストアド プロシージャを繰り返し呼び出します。

http://www.oracle.com/technetwork/issue-archive/2009/09-sep/o59odpnet-085168.html

/ダミアン

于 2009-04-28T12:28:34.723 に答える
25

私は最近、バルクインサート(ODP.NET)に最適な特殊なクラスを発見しました。Oracle.DataAccess.Client.OracleBulkCopy!それはパラメータとしてデータテーブルを取り、それからあなたはWriteTOServerメソッドを呼び出します...それは非常に速くて効果的です、頑張ってください!!

于 2010-08-30T17:29:50.273 に答える
17

Rob Stevenson-Legget のソリューションは、値をバインドせずに string.Format( ) を使用しているため、時間がかかります。

Oracle に sql ステートメントを実行するように要求すると、このステートメントの has 値の計算から始まります。その後、このステートメントを既に認識しているかどうかをハッシュ テーブルで調べます。ステートメントが既にわかっている場合は、このハッシュ テーブルから実行パスを取得し、このステートメントを非常に高速に実行できます。これは、Oracle が以前にこのステートメントを実行したためです。これはライブラリ キャッシュと呼ばれ、SQL ステートメントをバインドしないと正しく機能しません。

たとえば、次のことはしないでください。

int n;

    for (n = 0; n < 100000; n ++)
    {
      mycommand.CommandText = String.Format("INSERT INTO [MyTable] ([MyId]) VALUES({0})", n + 1);
      mycommand.ExecuteNonQuery();
    }

ただし、次のことを行います。

      OracleParameter myparam = new OracleParameter();
      int n;

      mycommand.CommandText = "INSERT INTO [MyTable] ([MyId]) VALUES(?)";
      mycommand.Parameters.Add(myparam);

      for (n = 0; n < 100000; n ++)
      {
        myparam.Value = n + 1;
        mycommand.ExecuteNonQuery();
      }

パラメータを使用しない場合も、SQL インジェクションが発生する可能性があります。

于 2008-12-05T10:17:41.943 に答える
8

SQL Server の SQLBulkCopy は驚くほど高速です。残念ながら、OracleBulkCopy ははるかに遅いことがわかりました。また、問題があります:

  • OracleBulkCopy を使用する場合は、入力データがクリーンであることを十分に確認する必要があります。主キー違反が発生すると、ORA-26026 が発生し、回復不能のように見えます。インデックスを再構築しようとしても役に立たず、テーブルへの後続の挿入は失敗し、通常の挿入も失敗します。
  • データがクリーンな場合でも、OracleBulkCopy が WriteToServer 内でスタックすることがあります。問題はバッチサイズに依存しているようです。私のテスト データでは、テストを繰り返すと、テストのまったく同じポイントで問題が発生します。より大きいまたはより小さいバッチ サイズを使用すると、問題は発生しません。バッチサイズが大きくなると速度が不規則になることがわかります。これは、メモリ管理に関連する問題を示しています。

実際には、テーブルを小さなレコードで多くの行で埋めたい場合、 System.Data.OracleClient.OracleDataAdapter は OracleBulkCopy よりも高速です。ただし、バッチ サイズを調整する必要があります。OracleDataAdapter の最適な BatchSize は、OracleBulkCopy の場合よりも小さくなります。

x86 実行可能ファイルと 32 ビット ODP.Net クライアント 2.112.1.0 を使用して、Windows 7 マシンでテストを実行しました。. OracleDataAdapter は System.Data.OracleClient 2.0.0.0 の一部です。私のテスト セットは、レコード サイズが最大の約 600,000 行です。102 バイト (平均サイズ 43 文字)。データ ソースは 25 MB のテキスト ファイルで、1 行ずつストリームとして読み取られます。

テストでは、入力データ テーブルを固定テーブル サイズに構築し、OracleBulkCopy または OracleDataAdapter を使用してデータ ブロックをサーバーにコピーしました。OracleBulkCopy で BatchSize を 0 のままにし (現在のテーブルの内容が 1 つのバッチとしてコピーされるようにするため)、OracleDataAdapter でテーブル サイズに設定します (ここでも内部で単一のバッチを作成する必要があります)。最良の結果:

  • OracleBulkCopy: テーブル サイズ = 500、合計所要時間 4'22"
  • OracleDataAdapter: テーブル サイズ = 100、合計期間 3'03"

比較のために:

  • SqlBulkCopy: テーブル サイズ = 1000、合計期間 0'15"
  • SqlDataAdapter: テーブル サイズ = 1000、合計期間 8'05"

同じクライアント マシン、テスト サーバーは SQL Server 2008 R2 です。SQL Server の場合、一括コピーが最適な方法であることは明らかです。全体的に最速なだけでなく、サーバー負荷もデータアダプタ使用時よりも低くなっています。OracleBulkCopy がまったく同じエクスペリエンスを提供しないのは残念です。BulkCopy API は DataAdapter よりもはるかに使いやすいです。

于 2012-04-11T06:23:06.587 に答える
4

この問題を解決するための非常に高速な方法は、OracleデータベースからMySQLデータベースへのデータベースリンクを作成することです。Oracle以外のデータベースへのデータベースリンクを作成できます。データベースリンクを作成した後、select *from...ステートメントとして...createtablemydataを使用してMySQLデータベースからデータを取得できます。これは、異種接続と呼ばれます。このように、データを移動するために.netアプリケーションで何もする必要はありません。

もう1つの方法は、ODP.NETを使用することです。ODP.NETでは、OracleBulkCopyクラスを使用できます。

しかし、System.Data.OracleClientを使用してOracleテーブルに160kレコードを挿入するのに25分かかるとは思いません。何度もコミットしすぎると思います。また、値をパラメーターを使用して挿入ステートメントにバインドしますか、それとも値を連結しますか。バインドははるかに高速です。

于 2008-12-05T10:06:09.267 に答える
4

リンクされた例がややこしいので、テスト テーブル (jkl_test) に配列を挿入する作業を示すコードを作成しました。ここにテーブルがあります:

create table jkl_test (id number(9));

以下は、ODP.Net を使用して Oracle に接続し、5 つの整数の配列を挿入する単純なコンソール アプリケーションの .Net コードです。

using Oracle.DataAccess.Client;

namespace OracleArrayInsertExample
{
    class Program
    {
        static void Main(string[] args)
        {
            // Open a connection using ODP.Net
            var connection = new OracleConnection("Data Source=YourDatabase; Password=YourPassword; User Id=YourUser");
            connection.Open();

            // Create an insert command
            var command = connection.CreateCommand();
            command.CommandText = "insert into jkl_test values (:ids)";

            // Set up the parameter and provide values
            var param = new OracleParameter("ids", OracleDbType.Int32);
            param.Value = new int[] { 22, 55, 7, 33, 11 };

            // This is critical to the process; in order for the command to 
            // recognize and bind arrays, an array bind count must be specified.
            // Set it to the length of the array.
            command.ArrayBindCount = 5;
            command.Parameters.Add(param);
            command.ExecuteNonQuery();
        }
    }
}
于 2014-06-25T15:39:29.150 に答える
3

Theo の提案を私の調査結果でフォローアップするには (申し訳ありません - 現在、これをコメントとして投稿するのに十分な評判がありません)。

まず、これはいくつかの名前付きパラメーターを使用する方法です。

String commandString = "INSERT INTO Users (Name, Desk, UpdateTime) VALUES (:Name, :Desk, :UpdateTime)";
using (OracleCommand command = new OracleCommand(commandString, _connection, _transaction))
{
    command.Parameters.Add("Name", OracleType.VarChar, 50).Value = strategy;
    command.Parameters.Add("Desk", OracleType.VarChar, 50).Value = deskName ?? OracleString.Null;
    command.Parameters.Add("UpdateTime", OracleType.DateTime).Value = updated;
    command.ExecuteNonQuery();
}

ただし、次の間で速度に変化は見られませんでした。

  • 行ごとに新しい commandString を作成する (String.Format)
  • 行ごとにパラメータ化された commandString を構築する
  • 単一の commandString を使用してパラメーターを変更する

System.Data.OracleClient を使用して、トランザクション内で 2500 行を削除および挿入しています

于 2010-01-21T14:57:33.197 に答える
2

管理されていないOracleクライアント(Oracle.DataAccess)を使用している場合、Tarikが指摘したように、OracleBulkCopyを使用するのが最も速い方法です。

最新のマネージド Oracle クライアント (Oracle.ManagedDataAccess) を使用している場合、最も速い方法は、Damien が指摘したように、配列バインディングを使用することです。アプリケーション・コードを配列バインディングの仕様から切り離したい場合は、配列バインディングを使用して OracleBulkCopy の独自の実装を作成できます。

実際のプロジェクトの使用例を次に示します。

var bulkWriter = new OracleDbBulkWriter();
    bulkWriter.Write(
        connection,
        "BULK_WRITE_TEST",
        Enumerable.Range(1, 10000).Select(v => new TestData { Id = v, StringValue=v.ToString() }).ToList());

500msで10Kレコード挿入!

実装は次のとおりです。

public class OracleDbBulkWriter : IDbBulkWriter
{
    public void Write<T>(IDbConnection connection, string targetTableName, IList<T> data, IList<ColumnToPropertyMapping> mappings = null)
    {
        if (connection == null)
        {
            throw new ArgumentNullException(nameof(connection));
        }
        if (string.IsNullOrEmpty(targetTableName))
        {
            throw new ArgumentNullException(nameof(targetTableName));
        }
        if (data == null)
        {
            throw new ArgumentNullException(nameof(data));
        }
        if (mappings == null)
        {
            mappings = GetGenericMappings<T>();
        }

        mappings = GetUniqueMappings<T>(mappings);
        Dictionary<string, Array> parameterValues = InitializeParameterValues<T>(mappings, data.Count);
        FillParameterValues(parameterValues, data);

        using (var command = CreateCommand(connection, targetTableName, mappings, parameterValues))
        {
            command.ExecuteNonQuery();
        }
    }

    private static IDbCommand CreateCommand(IDbConnection connection, string targetTableName, IList<ColumnToPropertyMapping> mappings, Dictionary<string, Array> parameterValues)
    {
        var command = (OracleCommandWrapper)connection.CreateCommand();
        command.ArrayBindCount = parameterValues.First().Value.Length;

        foreach(var mapping in mappings)
        {
            var parameter = command.CreateParameter();
            parameter.ParameterName = mapping.Column;
            parameter.Value = parameterValues[mapping.Property];

            command.Parameters.Add(parameter);
        }

        command.CommandText = $@"insert into {targetTableName} ({string.Join(",", mappings.Select(m => m.Column))}) values ({string.Join(",", mappings.Select(m => $":{m.Column}")) })";
        return command;
    }

    private IList<ColumnToPropertyMapping> GetGenericMappings<T>()
    {
        var accessor = TypeAccessor.Create(typeof(T));

        var mappings = accessor.GetMembers()
            .Select(m => new ColumnToPropertyMapping(m.Name, m.Name))
            .ToList();

        return mappings;
    }

    private static IList<ColumnToPropertyMapping> GetUniqueMappings<T>(IList<ColumnToPropertyMapping> mappings)
    {
        var accessor = TypeAccessor.Create(typeof(T));
        var members = new HashSet<string>(accessor.GetMembers().Select(m => m.Name));

        mappings = mappings
                        .Where(m => m != null && members.Contains(m.Property))
                        .GroupBy(m => m.Column)
                        .Select(g => g.First())
                        .ToList();
        return mappings;
    }

    private static Dictionary<string, Array> InitializeParameterValues<T>(IList<ColumnToPropertyMapping> mappings, int numberOfRows)
    {
        var values = new Dictionary<string, Array>(mappings.Count);
        var accessor = TypeAccessor.Create(typeof(T));
        var members = accessor.GetMembers().ToDictionary(m => m.Name);

        foreach(var mapping in mappings)
        {
            var member = members[mapping.Property];

            values[mapping.Property] = Array.CreateInstance(member.Type, numberOfRows);
        }

        return values;
    }

    private static void FillParameterValues<T>(Dictionary<string, Array> parameterValues, IList<T> data)
    {
        var accessor = TypeAccessor.Create(typeof(T));
        for (var rowNumber = 0; rowNumber < data.Count; rowNumber++)
        {
            var row = data[rowNumber];
            foreach (var pair in parameterValues)
            {
                Array parameterValue = pair.Value;
                var propertyValue = accessor[row, pair.Key];
                parameterValue.SetValue(propertyValue, rowNumber);
            }
        }
    }
}

注: この実装では、プロパティへの最適化されたアクセスのために Fastmember パッケージを使用します (リフレクションよりもはるかに高速です)。

于 2016-10-25T07:50:36.967 に答える
2

Oracle によると ( http://www.oracle.com/technology/products/database/utilities/htdocs/sql_loader_overview.html )

SQL*Loader は、外部ファイルからのデータを Oracle テーブルにすばやく移入するための主要な方法です。

私の経験では、ローダーがテーブルを他の何よりも速くロードします。

于 2009-04-28T12:32:31.623 に答える
1

OracleBulkCopy は最速の方法の 1 つだと思います。新しいODACバージョンが必要であることを知るのに苦労しました。参照。タイプ [Oracle.DataAccess.Client.OracleBulkCopy] はどこにありますか?

クエリから適切な既存の Oracle テーブルにコピーする完全な PowerShell コードを次に示します。Sql-Server をデータソースとして試しましたが、他の有効な OLE-DB ソースに移動します。

if ($ora_dll -eq $null)
{
    "Load Oracle dll"
    $ora_dll = [System.Reflection.Assembly]::LoadWithPartialName("Oracle.DataAccess") 
    $ora_dll
}

# sql-server or Oracle source example is sql-server
$ConnectionString ="server=localhost;database=myDatabase;trusted_connection=yes;Provider=SQLNCLI10;"

# Oracle destination
$oraClientConnString = "Data Source=myTNS;User ID=myUser;Password=myPassword"

$tableName = "mytable"
$sql = "select * from $tableName"

$OLEDBConn = New-Object System.Data.OleDb.OleDbConnection($ConnectionString)
$OLEDBConn.open()
$readcmd = New-Object system.Data.OleDb.OleDbCommand($sql,$OLEDBConn)
$readcmd.CommandTimeout = '300'
$da = New-Object system.Data.OleDb.OleDbDataAdapter($readcmd)
$dt = New-Object system.Data.datatable
[void]$da.fill($dt)
$OLEDBConn.close()
#Write-Output $dt

if ($dt)
{
    try
    {
        $bulkCopy = new-object ("Oracle.DataAccess.Client.OracleBulkCopy") $oraClientConnString
        $bulkCopy.DestinationTableName = $tableName
        $bulkCopy.BatchSize = 5000
        $bulkCopy.BulkCopyTimeout = 10000
        $bulkCopy.WriteToServer($dt)
        $bulkcopy.close()
        $bulkcopy.Dispose()
    }
    catch
    {
        $ex = $_.Exception
        Write-Error "Write-DataTable$($connectionName):$ex.Message"
        continue
    }
}

ところで:これを使用して、CLOB 列を含むテーブルをコピーします。リンクサーバーcfを使用して動作させることができませんでした。dba に関する質問。新しい ODAC でリンクされたサーブを再試行しませんでした。

于 2011-02-07T07:06:24.087 に答える