2008-12-05 44 views
26

Cách nhanh nhất để chèn hàng loạt vào Oracle bằng .NET là gì? Tôi cần phải chuyển khoảng 160K hồ sơ bằng cách sử dụng .NET to Oracle. Hiện tại, tôi đang sử dụng câu lệnh chèn và thực thi nó 160K lần. Mất khoảng 25 phút để hoàn tất. Dữ liệu nguồn được lưu trữ trong một DataTable, như là kết quả của truy vấn từ cơ sở dữ liệu khác (MySQL),Chèn số lượng lớn vào Oracle bằng cách sử dụng .NET

Có cách nào tốt hơn để làm điều này không?

EDIT: Tôi hiện đang sử dụng System.Data.OracleClient, nhưng sẵn sàng chấp nhận các giải pháp sử dụng một nhà cung cấp (ODP.NET, DevArt, vv ..)

+0

Có gì sai với một tiện ích như SQL * Loader? –

+0

Bạn đã thử DevArt chưa? Tôi đã tự hỏi nếu Devart có OracleBulkCopy. –

Trả lời

2

Bạn không nên cam kết mỗi chèn vì cam kết mất rất nhiều thời gian.

Nhà cung cấp nào bạn sử dụng để kết nối ứng dụng .NET của mình với cơ sở dữ liệu oracle? Bạn có sử dụng ODP.NET hoặc nhà cung cấp Devart (hay còn gọi là nhà cung cấp corelab) hay bạn sử dụng nhà cung cấp Microsoft cho Oracle (System.Data.OracleClient)?

+0

Tôi đã chỉnh sửa câu hỏi để trả lời nhận xét của bạn – Salamander2007

4

Cách thực sự nhanh chóng để giải quyết vấn đề này là tạo liên kết cơ sở dữ liệu từ cơ sở dữ liệu Oracle tới cơ sở dữ liệu MySQL. Bạn có thể tạo liên kết cơ sở dữ liệu tới cơ sở dữ liệu không phải của Oracle. Sau khi bạn đã tạo liên kết cơ sở dữ liệu, bạn có thể truy xuất dữ liệu của mình từ cơ sở dữ liệu MySQL bằng ... tạo bảng mydata dưới dạng select * from ... statement. Điều này được gọi là kết nối không đồng nhất. Bằng cách này bạn không phải làm bất cứ điều gì trong ứng dụng .net của bạn để di chuyển dữ liệu.

Một cách khác là sử dụng ODP.NET. Trong ODP.NET bạn có thể sử dụng lớp OracleBulkCopy.

Nhưng tôi không nghĩ rằng chèn bản ghi 160k trong bảng Oracle với System.Data.OracleClient sẽ mất 25 phút. Tôi nghĩ bạn cam kết quá nhiều lần. Và bạn có ràng buộc các giá trị của bạn vào câu lệnh chèn với các tham số hay không bạn nối các giá trị của bạn. Ràng buộc nhanh hơn nhiều.

+0

Đã xóa Câu trả lời của tôi vì đây là giải pháp tốt hơn nhiều. –

+0

Tôi thực sự đã thiết lập các dblink trong môi trường dev của chúng tôi, nhưng khi nói đến sản xuất, nó chỉ ra rằng oracle và MySQL thậm chí không cư trú trong cùng một mạng, do đó workaround nhanh chóng và bẩn của tôi bằng cách sử dụng. Tôi thực sự cần phải nhìn vào OracleBulkCopy đó. Bất kỳ con trỏ? – Salamander2007

16

Giải pháp của Rob Stevenson-Legget chậm bởi vì anh ta không ràng buộc các giá trị của mình nhưng anh ta sử dụng string.Format().

Khi bạn yêu cầu Oracle thực hiện câu lệnh sql, nó bắt đầu bằng cách tính toán giá trị có của câu lệnh này. Sau đó nó trông trong một bảng băm cho dù nó đã biết tuyên bố này. Nếu nó đã biết nó tuyên bố nó có thể lấy đường dẫn thực hiện của nó từ bảng băm này và thực thi câu lệnh này rất nhanh vì Oracle đã thực thi câu lệnh này trước đây. Điều này được gọi là bộ nhớ cache thư viện và nó không hoạt động đúng nếu bạn không ràng buộc các câu lệnh sql của bạn.

Ví dụ: không làm:

int n;

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

nhưng làm:

 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(); 
     } 

Không sử dụng các thông số cũng có thể gây sql injection.

+0

Đã xóa câu trả lời của tôi vì Theo đúng với liên kết DB. –

+0

Câu trả lời hay được đăng gần 5 năm trước! :) Theo đâu ?! –

26

Tôi tải 50.000 hồ sơ trong vòng 15 giây hoặc lâu hơn sử dụng mảng Binding trong ODP.NET

Nó hoạt động bằng cách liên tục gọi một stored procedure bạn chỉ định (và trong đó bạn có thể làm cập nhật/chèn/xóa), nhưng nó chuyển nhiều giá trị tham số từ .NET đến cơ sở dữ liệu hàng loạt.

Thay vì chỉ định một giá trị duy nhất cho từng tham số cho quy trình được lưu, bạn chỉ định một mảng giá trị cho mỗi thông số.

Oracle chuyển các mảng tham số từ .NET sang cơ sở dữ liệu trong một lần, và sau đó lặp lại nhiều lần thủ tục lưu sẵn mà bạn chỉ định bằng cách sử dụng các giá trị tham số bạn đã chỉ định.

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

/Damian

+0

+1 Đây là câu trả lời đúng khi có giải pháp .NET. – Christian13467

+0

Điều đó hoạt động tốt với mảng nhưng anh ta đang viết một dữ liệu trở lại cơ sở dữ liệu. Làm thế nào để sử dụng một mảng bindings đến một datatable trong ODP.Net? – mcauthorn

+0

Đối với mỗi hàng trong bảng dữ liệu, các giá trị cột riêng lẻ cần phải được thêm vào mảng giá trị (một giá trị mỗi hàng) cho cột đó, và sau đó chèn đơn bằng cách sử dụng liên kết mảng phải được gọi. – Damian

2

Oracle nói (http://www.oracle.com/technology/products/database/utilities/htdocs/sql_loader_overview.html)

SQL * Loader là phương pháp chính để nhanh chóng Populating bảng Oracle với dữ liệu từ các tập tin bên ngoài

Kinh nghiệm của tôi là nỗi lo của họ r tải bảng của họ nhanh hơn bất kỳ thứ gì khác.

+3

Không chắc chắn rằng nó có một API NET mặc dù - Tôi nghĩ rằng câu hỏi ban đầu đã được tái .NET. Ngoài ra nó không đủ cho các mục đích của tôi bởi vì nó chỉ chèn - tôi muốn UPDATE/INSERT tùy thuộc vào việc các bản ghi đã tồn tại chưa. Có thể là đủ cho người hỏi mặc dù. – Damian

+0

SQL * Loader là công cụ tải dữ liệu oracle. Nó có thể xử lý số lượng lớn dữ liệu được lưu trữ trong csv, txt hoặc các tệp khác. Xuất dữ liệu của bạn từ mysql sang định dạng csv và viết một tập lệnh tải để bơm dữ liệu vào oracle. – Christian13467

+0

Liên kết đã chết – billybob

2

Để theo dõi gợi ý Theo với phát hiện của tôi (xin lỗi - Tôi hiện không có đủ uy tín để đăng bài này như một bình luận)

Thứ nhất, đây là làm thế nào để sử dụng một số tên thông số:

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(); 
} 

Tuy nhiên, tôi thấy không có sự thay đổi về tốc độ giữa:

  • xây dựng một commandString mới cho mỗi hàng (String.Format)
  • xây dựng một commandString tại tham số cho mỗi hàng
  • sử dụng một commandString đơn và thay đổi các thông số

Tôi đang sử dụng System.Data.OracleClient, xóa và chèn 2500 hàng bên trong một giao dịch

21

tôi thời gian gần đây đã phát hiện ra một lớp chuyên biệt tuyệt vời cho một số lượng lớn chèn (ODP.NET). Oracle.DataAccess.Client.OracleBulkCopy! Phải mất một datatable như một tham số, sau đó bạn gọi phương thức WriteTOServer ... nó rất nhanh và hiệu quả, chúc may mắn !!

+0

Nó có hoạt động với Compact Framework không? –

1

Tôi đoán rằng OracleBulkCopy là một trong những cách nhanh nhất. Tôi đã có một số khó khăn để tìm hiểu, rằng tôi cần một phiên bản ODAC mới. Cf. Where is type [Oracle.DataAccess.Client.OracleBulkCopy] ?

Đây là mã PowerShell hoàn chỉnh để sao chép từ truy vấn vào bảng Oracle hiện có phù hợp. Tôi đã thử Sql-Server một nguồn dữ liệu, nhưng các nguồn OLE-DB hợp lệ khác sẽ đi đến.

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: Tôi sử dụng tính năng này để sao chép bảng có cột CLOB. Tôi đã không nhận được rằng để làm việc bằng cách sử dụng máy chủ được liên kết cf. question on dba. Tôi đã không thử lại liên kết phục vụ với ODAC mới.

6

SQLBulkCopy của SQL Server nhanh quá nhanh. Thật không may, tôi thấy rằng OracleBulkCopy chậm hơn nhiều.Ngoài ra nó có vấn đề:

  • Bạn phải chắc chắn rằng dữ liệu đầu vào của bạn sạch sẽ nếu bạn định sử dụng sử dụng OracleBulkCopy. Nếu xảy ra vi phạm khóa chính, một ORA-26026 được nêu lên và có vẻ như không thể khôi phục. Cố gắng xây dựng lại chỉ mục không giúp đỡ và bất kỳ chèn tiếp theo nào trên bảng không thành công, cũng chèn bình thường.
  • Ngay cả khi dữ liệu được sạch sẽ, tôi thấy rằng OracleBulkCopy đôi khi bị kẹt bên trong WriteToServer. Vấn đề dường như phụ thuộc vào kích thước lô. Trong dữ liệu thử nghiệm của tôi, vấn đề sẽ là xảy ra tại cùng một điểm chính xác trong thử nghiệm của tôi khi tôi lặp lại. Sử dụng kích thước lô lớn hơn hoặc nhỏ hơn và sự cố không xảy ra. Tôi thấy rằng tốc độ không thường xuyên hơn đối với kích thước lô lớn hơn, điểm này cho các sự cố liên quan đến quản lý bộ nhớ.

Thực tế System.Data.OracleClient.OracleDataAdapter nhanh hơn OracleBulkCopy nếu bạn muốn điền vào một bảng có bản ghi nhỏ nhưng nhiều hàng. Bạn cần phải điều chỉnh kích thước lô mặc dù, BatchSize tối ưu cho OracleDataAdapter là nhỏ hơn cho OracleBulkCopy.

Tôi chạy thử nghiệm của mình trên máy tính Windows 7 có thực thi x86 và máy khách 32 bit ODP.Net 2.112.1.0. . OracleDataAdapter là một phần của System.Data.OracleClient 2.0.0.0. Bộ thử nghiệm của tôi là khoảng 600.000 hàng có kích thước bản ghi tối đa. 102 byte (kích thước trung bình 43 ký tự). Nguồn dữ liệu là tệp văn bản 25 MB, đọc từng dòng dưới dạng luồng.

Trong thử nghiệm của mình, tôi đã xây dựng bảng dữ liệu đầu vào thành kích thước bảng cố định và sau đó sử dụng OracleBulkCopy hoặc OracleDataAdapter để sao chép khối dữ liệu vào máy chủ. Tôi để lại BatchSize là 0 trong OracleBulkCopy (để nội dung của bảng hiện tại được sao chép thành một lô) và đặt nó thành kích thước bảng trong OracleDataAdapter (một lần nữa nên tạo một loạt nội bộ). kết quả tốt nhất:

  • OracleBulkCopy: bảng size = 500, tổng thời gian 4'22"
  • OracleDataAdapter: bảng size = 100, tổng thời gian 3'03"

Để so sánh:

  • SqlBulkCopy: bảng size = 1000, tổng thời gian 0'15"
  • SqlDataAdapter: bảng size = 1000, tổng thời gian 8'05"

Cùng một máy khách, máy chủ thử nghiệm là SQL Server 2008 R2. Đối với SQL Server, sao chép số lượng lớn rõ ràng là cách tốt nhất để đi. Nó không chỉ nhanh nhất, nhưng tải máy chủ cũng thấp hơn khi sử dụng bộ điều hợp dữ liệu. Thật đáng tiếc là OracleBulkCopy không mang lại trải nghiệm tương tự - API BulkCopy dễ sử dụng hơn nhiều so với DataAdapter.

+0

Bạn hoàn toàn đúng về sự lúng túng của việc quản lý bộ nhớ trong trình điều khiển dữ liệu Oracle. Tôi đã trải nghiệm chính xác cùng một vấn đề khi số lượng hàng chèn được thay đổi. Ngoài ra, khi tôi lặp qua các đợt chèn lớn, hiệu suất sẽ giảm dần theo thời gian cho đến khi các lô 10 lần chèn đơn giản có thể mất tới 5 phút. – grenade

2

Tìm các ví dụ được liên kết hơi khó hiểu, tôi đã tìm ra một số mã trình bày một mảng hoạt động chèn vào bảng kiểm tra (jkl_test). Đây là bảng:

create table jkl_test (id number(9)); 

Đây là mã .Net cho ứng dụng Console đơn giản kết nối với Oracle bằng ODP.Net và chèn một mảng của 5 số nguyên:

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(); 
     } 
    } 
} 
0

Nếu bạn đang sử dụng client oracle không được quản lý (Oracle.DataAccess) thì cách nhanh nhất là sử dụng OracleBulkCopy, như đã được trỏ bởi Tarik.

Nếu bạn đang sử dụng ứng dụng oracle quản lý mới nhất (Oracle.ManagedDataAccess) thì cách nhanh nhất là sử dụng liên kết mảng, như được chỉ định bởi Damien. Nếu bạn muốn giữ mã ứng dụng của bạn sạch sẽ khỏi các chi tiết ràng buộc mảng, bạn có thể viết việc triển khai thực hiện OracleBulkCopy của riêng bạn bằng cách sử dụng mảng ràng buộc.

Dưới đây là ví dụ sử dụng từ dự án thực tế:

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

10K hồ sơ được chèn trong 500ms!

Đây là triển khai thực hiện:

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 = [email protected]"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); 
      } 
     } 
    } 
} 

Chú ý: thực hiện này sử dụng Fastmember gói để truy cập tối ưu hóa để tính (nhanh hơn nhiều so với suy nghĩ)

+0

Ví dụ mã của bạn không thể sử dụng được vì ví dụ này bỏ sót một số lớp. * ColumnToPropertyMapping, * OracleCommandWrapper, * IDbBulkWriter – Markus1980Wien

Các vấn đề liên quan