使用.NET批量插入Oracle的最快方法是什么?我需要使用.NET将大约160K记录传输到Oracle。目前,我正在使用insert语句并执行160K次。完成大约需要25分钟。源数据存储在DataTable中,作为从另一个数据库(MySQL)查询的结果,
有没有更好的方法呢?
编辑:我目前正在使用System.Data.OracleClient,但愿意接受使用其他提供商(ODP.NET,DevArt等)的解决方案。
答案 0 :(得分:27)
我使用ODP.NET中的数组绑定在15秒左右加载50,000条记录
它通过重复调用您指定的存储过程(并且您可以在其中执行更新/插入/删除)来工作,但它会将多个参数值从.NET传递到数据库。
不是为存储过程的每个参数指定单个值,而是为每个参数指定值的数组。
Oracle一次性将参数数组从.NET传递到数据库,然后使用您指定的参数值重复调用您指定的存储过程。
http://www.oracle.com/technetwork/issue-archive/2009/09-sep/o59odpnet-085168.html
/达米安
答案 1 :(得分:22)
我最近发现了一个专门用于批量插入(ODP.NET)的类。 Oracle.DataAccess.Client.OracleBulkCopy!它需要一个数据表作为参数,然后你调用WriteTOServer方法......它非常快速有效,祝你好运!
答案 2 :(得分:16)
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注入。
答案 3 :(得分:6)
SQL Server的SQLBulkCopy速度非常快。不幸的是,我发现OracleBulkCopy要慢得多。它也有问题:
实际上,如果要填充具有小记录但行数很多的表,则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行,记录大小是max。 102个字节(平均大小43个字符)。数据源是一个25 MB的文本文件,逐行读取为流。
在我的测试中,我将输入数据表构建为固定的表大小,然后使用OracleBulkCopy或OracleDataAdapter将数据块复制到服务器。我在OracleBulkCopy中将BatchSize保留为0(以便将当前表内容复制为一个批处理)并将其设置为OracleDataAdapter中的表大小(同样应在内部创建单个批处理)。 最佳成绩:
进行比较:
相同的客户端计算机,测试服务器是SQL Server 2008 R2。对于SQL Server,批量复制显然是最好的方法。它不仅总体上最快,而且服务器负载也低于使用数据适配器时的服务器负载。遗憾的是,OracleBulkCopy没有提供相同的体验 - BulkCopy API比DataAdapter更容易使用。
答案 4 :(得分:4)
解决此问题的一种非常快捷的方法是建立从Oracle数据库到MySQL数据库的数据库链接。您可以创建指向非Oracle数据库的数据库链接。创建数据库链接后,您可以使用... create table mydata作为select * from ...语句从MySQL数据库中检索数据。这称为异构连接。这样,您无需在.net应用程序中执行任何操作即可移动数据。
另一种方法是使用ODP.NET。在ODP.NET中,您可以使用OracleBulkCopy类。
但我不认为在带有System.Data.OracleClient的Oracle表中插入160k记录应该需要25分钟。我认为你做了太多次了。您是否使用参数将值绑定到insert语句,或者是否连接了值。绑定要快得多。
答案 5 :(得分: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();
}
但是,我发现速度之间没有变化:
我正在使用System.Data.OracleClient,在事务中删除并插入2500行
答案 6 :(得分:2)
Oracle说(http://www.oracle.com/technology/products/database/utilities/htdocs/sql_loader_overview.html)
SQL * Loader是主要的方法 快速填充Oracle表 来自外部文件的数据
我的经验是他们的加载器加载他们的表比其他任何东西都快。
答案 7 :(得分:2)
找到链接的示例有点令人困惑,我制定了一些代码,演示了一个工作数组插入到测试表(jkl_test)中。这是表格:
create table jkl_test (id number(9));
这是一个简单的Console应用程序的.Net代码,它使用ODP.Net连接到Oracle并插入一个包含5个整数的数组:
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();
}
}
}
答案 8 :(得分:1)
我猜OracleBulkCopy是最快的方法之一。我学到了一些麻烦,我需要一个新的ODAC版本。参看Where is type [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
}
}
BTW:我使用它来复制CLOB列的表。我没有使用链接服务器cf. question on dba来实现这一点。我没有使用新的ODAC重试链接服务。
答案 9 :(得分:0)
如果您正在使用非托管oracle客户端(Oracle.DataAccess),那么最快的方法是使用OracleBulkCopy,正如Tarik指出的那样。
如果您使用的是最新的托管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());
在500毫秒内插入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软件包来优化对属性的访问(比反射快得多)