使用.NET大容量插入到Oracle

使用.NET大容量插入到Oracle,.net,sql,oracle,bulkinsert,.net,Sql,Oracle,Bulkinsert,使用.NET对Oracle进行批量插入的最快方法是什么?我需要使用.NET将大约160K条记录传输到Oracle。目前,我正在使用insert语句并执行160K次,大约需要25分钟才能完成。源数据存储在DataTable中,这是来自另一个数据库(MySQL)的查询结果 有没有更好的办法 编辑:我目前正在使用System.Data.OracleClient,但愿意接受使用其他提供商(ODP.NET、DevArt等)的解决方案解决此问题的一个非常快速的方法是建立从Oracle数据库到MySQL数据库

使用.NET对Oracle进行批量插入的最快方法是什么?我需要使用.NET将大约160K条记录传输到Oracle。目前,我正在使用insert语句并执行160K次,大约需要25分钟才能完成。源数据存储在DataTable中,这是来自另一个数据库(MySQL)的查询结果

有没有更好的办法


编辑:我目前正在使用System.Data.OracleClient,但愿意接受使用其他提供商(ODP.NET、DevArt等)的解决方案

解决此问题的一个非常快速的方法是建立从Oracle数据库到MySQL数据库的数据库链接。您可以创建指向非Oracle数据库的数据库链接。创建数据库链接后,您可以使用。。。创建表mydata作为选择*从。。。陈述这称为异构连接。这样,您就不必在.net应用程序中执行任何操作来移动数据

另一种方法是使用ODP.NET。在ODP.NET中,您可以使用OracleBulkCopy类


但我不认为在带有System.Data.OracleClient的Oracle表中插入160k条记录需要25分钟。我认为你犯了太多的错误。您是使用参数将值绑定到insert语句,还是将值连接在一起。绑定要快得多

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();
    }
(n=0;n<100000;n++)的

{
mycommand.CommandText=String.Format(“插入[MyTable]([MyId])值({0})”,n+1;
mycommand.ExecuteOnQuery();
}
但要做到:

      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();
      }
OracleParameter myparam=新的OracleParameter();
int n;
mycommand.CommandText=“插入[MyTable]([MyId])值(?”;
mycommand.Parameters.Add(myparam);
对于(n=0;n<100000;n++)
{
myparam.Value=n+1;
mycommand.ExecuteOnQuery();
}

不使用参数也会导致sql注入

我使用ODP.NET中的数组绑定在15秒内加载了50000条记录

它通过重复调用您指定的存储过程(并且可以在其中执行更新/插入/删除)来工作,但它会将多个参数值从.NET批量传递到数据库

不是为存储过程的每个参数指定单个值,而是为每个参数指定一个值数组

Oracle一次性将参数数组从.NET传递到数据库,然后使用指定的参数值重复调用指定的存储过程

/达米安

甲骨文说()

SQL*加载器是 使用 来自外部文件的数据


我的经验是,他们的加载器加载表格的速度比其他任何东西都快。

以我的发现跟进西奥的建议(抱歉,我目前没有足够的声誉将此作为评论发布)

首先,这是如何使用几个命名参数:

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行

我最近发现了一个专门的类,它非常适合批量插入(ODP.NET)。Oracle.DataAccess.Client.OracleBulkCopy!它以datatable作为参数,然后调用WriteTOServer方法…它非常快速有效,祝你好运

我想OracleBulkCopy是最快的方式之一。我有一些困难要学习,我需要一个新的ODAC版本。比照

下面是完整的PowerShell代码,可以从查询复制到合适的现有Oracle表中。我尝试使用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列的表。使用链接服务器时,我无法实现这一点。我没有使用新的ODAC重试链接服务。

SQL Server的SQLBulkCopy速度惊人。不幸的是,我发现OracleBulkCopy要慢得多。它还存在以下问题:

  • 如果您打算这样做,您必须非常确定您的输入数据是干净的 使用OracleBulkCopy。如果发生主键冲突,则ORA-26026 已引发,并且似乎无法恢复。试图重建 索引没有帮助,表上的任何后续插入都失败, 也包括普通插入件
  • 即使数据是干净的,我发现 OracleBulkCopy有时会卡在WriteToServer中。问题 似乎取决于批量大小。在我的测试数据中,问题是 在我的测试中,当我重复时,发生在完全相同的点上。使用 批量大小越大或越小,问题就不会发生。我懂了 这表明,在批量较大的情况下,速度更不规则 与内存管理相关的问题
实际上,如果您想用小记录但多行填充表,System.Data.OracleClient.OracleDataAdapter比OracleBulkCopy更快。不过,您需要调整批大小,OracleDataAdapter的最佳批大小小于OracleBulkCopy的最佳批大小

我使用x86可执行文件和32位ODP.Net客户端2.112.1.0在Windows 7机器上运行了测试。神谕
create table jkl_test (id number(9));
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();
        }
    }
}
var bulkWriter = new OracleDbBulkWriter();
    bulkWriter.Write(
        connection,
        "BULK_WRITE_TEST",
        Enumerable.Range(1, 10000).Select(v => new TestData { Id = v, StringValue=v.ToString() }).ToList());
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);
            }
        }
    }
}