[Oracle] Bulk Insert Data

来源:http://www.cnblogs.com/memento/archive/2016/03/21/5303342.html
-Advertisement-
Play Games

ArrayBind和OracleBulkCopy兩種方式批量導入大數據


命名空間:Oracle.DataAccess.Client

組件:Oracle.DataAccess.dll(2.112.1.0)

ODP.NET 版本:ODP.NET for .NET Framework 2.0 或 ODP.NET for .NET Framework 4

工具:Microsoft Visual Studio Ultimate 2013 + Oracle SQL Developer 1.5.5 + Oracle Database 11g Enterprise Edition 11.2.0.1.0(32位) + TNS for 32-bit Windows 11.2.0.1.0

 

方式一:ArrayBind

當插入一條數據時,SQL語句如下:

INSERT INTO table_name VALUES (:col1, :col2, :col3, :col4, :col5)
  1 public void InsertDataRow(Dictionary<string, object> dataRow)
  2 {
  3 	StringBuilder sbCmdText = new StringBuilder();
  4 	sbCmdText.AppendFormat("INSERT INTO {0}(", m_TableName);
  5 	sbCmdText.Append(string.Join(",", dataRow.Keys.ToArray()));
  6 	sbCmdText.Append(") VALUES (");
  7 	sbCmdText.Append(":" + string.Join(",:", dataRow.Keys.ToArray()));
  8 	sbCmdText.Append(")");
  9 
 10 	using (OracleConnection conn = new OracleConnection())
 11 	{
 12 		using (OracleCommand cmd = conn.CreateCommand())
 13 		{
 14 			cmd.CommandType = CommandType.Text;
 15 			cmd.CommandText = sbCmdText.ToString();
 16 			OracleParameter parameter = null;
 17 			OracleDbType dbType = OracleDbType.Object;
 18 			foreach (string colName in dataRow.Keys)
 19 			{
 20 				dbType = GetOracleDbType(dataRow[colName]);
 21 				parameter = new OracleParameter(colName, dbType);
 22 				parameter.Direction = ParameterDirection.Input;
 23 				parameter.OracleDbTypeEx = dbType;
 24 				parameter.Value = dataRow[colName];
 25 				cmd.Parameters.Add(parameter);
 26 			}
 27 			conn.Open();
 28 			int result = cmd.ExecuteNonQuery();
 29 		}
 30 	}
 31 }

此時,每一個 OracleParameter 的 Value 值都賦予單個欄位的 一個具體值,這種也是最為傳統的插入數據的方法。

Oracle V6 中 OCI 編程介面加入了數組介面特性。

當採用 ArrayBind 時,OraleParameter 的 Value 值則是賦予單個欄位的 一個數組,即多條數據的該欄位組合成的一個數組。此時 Oracle 僅需要執行一次 SQL 語句,即可在記憶體中批量解析並導入數據,減少程式與資料庫之間來回的操作,其優點就是數據導入的總體時間明顯減少,尤其是進程占用CPU的時間。

如果數據源是 DataTable 類型,首先把 DataTable 數據源,轉換成 object[][] 類型,然後綁定 OracleParameter 的 Value 值為對應欄位的一個 Object[] 數組即可;參考代碼如下:

  1 /// <summary>
  2 /// 批量插入大數據量
  3 /// </summary>
  4 /// <param name="columnData">列名-列數據字典</param>
  5 /// <param name="dataCount">數據量</param>
  6 /// <returns>插入數據量</returns>
  7 public int InsertBigData(Dictionary<string, object> columnData, int dataCount)
  8 {
  9 	int result = 0;
 10 	if (columnData == null || columnData.Count < 1)
 11 	{
 12 		return result;
 13 	}
 14 	string[] colHeaders = columnData.Keys.ToArray();
 15 	StringBuilder sbCmdText = new StringBuilder();
 16 	if (columnData.Count > 0)
 17 	{
 18 		// 拼接INSERT的SQL語句
 19 		sbCmdText.AppendFormat("INSERT INTO {0}(", m_TableName);
 20 		sbCmdText.Append(string.Join(",", colHeaders));
 21 		sbCmdText.Append(") VALUES (");
 22 		sbCmdText.Append(m_ParameterPrefix + string.Join("," + m_ParameterPrefix, colHeaders));
 23 		sbCmdText.Append(")");
 24 		OracleConnection connection = null;
 25 		try
 26 		{
 27 			connection = new OracleConnection(GetConnectionString());
 28 			using (OracleCommand command = connection.CreateCommand())
 29 			{
 30 				command.ArrayBindCount = dataCount;
 31 				command.BindByName = true;
 32 				command.CommandType = CommandType.Text;
 33 				command.CommandText = sbCmdText.ToString();
 34 				command.CommandTimeout = 1800;
 35 				OracleParameter parameter;
 36 				OracleDbType dbType = OracleDbType.Object;
 37 				foreach (string colName in colHeaders)
 38 				{
 39 					dbType = GetOracleDbType(columnData[colName]);
 40 					parameter = new OracleParameter(colName, dbType);
 41 					parameter.Direction = ParameterDirection.Input;
 42 					parameter.OracleDbTypeEx = dbType;
 43 					parameter.Value = columnData[colName];
 44 					command.Parameters.Add(parameter);
 45 				}
 46 				connection.Open();
 47 				OracleTransaction trans = connection.BeginTransaction();
 48 				try
 49 				{
 50 					command.Transaction = trans;
 51 					result = command.ExecuteNonQuery();
 52 					trans.Commit();
 53 				}
 54 				catch (Exception ex)
 55 				{
 56 					trans.Rollback();
 57 					throw ex;
 58 				}
 59 			}
 60 		}
 61 		finally
 62 		{
 63 			if (connection != null)
 64 			{
 65 				connection.Close();
 66 				connection.Dispose();
 67 			}
 68 			GC.Collect();
 69 			GC.WaitForFullGCComplete();
 70 		}
 71 	}
 72 	return result;
 73 }
  1 /// <summary>
  2 /// 根據數據類型獲取OracleDbType
  3 /// </summary>
  4 /// <param name="value">數據</param>
  5 /// <returns>數據的Oracle類型</returns>
  6 private static OracleDbType GetOracleDbType(object value)
  7 {
  8 	OracleDbType dataType = OracleDbType.Object;
  9 	if (value is string[])
 10 	{
 11 		dataType = OracleDbType.Varchar2;
 12 	}
 13 	else if (value is DateTime[])
 14 	{
 15 		dataType = OracleDbType.TimeStamp;
 16 	}
 17 	else if (value is int[] || value is short[])
 18 	{
 19 		dataType = OracleDbType.Int32;
 20 	}
 21 	else if (value is long[])
 22 	{
 23 		dataType = OracleDbType.Int64;
 24 	}
 25 	else if (value is decimal[] || value is double[] || value is float[])
 26 	{
 27 		dataType = OracleDbType.Decimal;
 28 	}
 29 	else if (value is Guid[])
 30 	{
 31 		dataType = OracleDbType.Varchar2;
 32 	}
 33 	else if (value is bool[] || value is Boolean[])
 34 	{
 35 		dataType = OracleDbType.Byte;
 36 	}
 37 	else if (value is byte[])
 38 	{
 39 		dataType = OracleDbType.Blob;
 40 	}
 41 	else if (value is char[])
 42 	{
 43 		dataType = OracleDbType.Char;
 44 	}
 45 	return dataType;
 46 }
GetOracleDbType說明:如果採用分次(每次1萬數據)執行 InsertBigData 方法,速度反而比一次性執行 InsertBigData 方法慢,詳見下麵測試結果;

測試結果:

無索引,數據類型:4列NVARCHAR2,2列NUMBER

30+萬(7.36M):一次性導入用時 15:623,每次10000導入用時 

60+萬(14.6M):一次性導入用時 28:207,每次10000導入用時 1:2:300

100+萬(24.9M):一次性導入報如下異常

image

此時實際上從資源監視器上可以得知仍有可用記憶體,但是仍舊報 OutOfMemoryException,所以猜測應該是一個 bug;

如果每次10000導入用時 2:9:252

如果每次50000導入用時 58:101

附加 InsertBigData 方法使用示例:

  1 // 每10000數據導入一次
  2 Dictionary<string, object> columnsData = new Dictionary<string, object>();
  3 int dataCount = m_SourceDataTable.Rows.Count;
  4 int times = dataCount / 10000 + (dataCount % 10000 == 0 ? 0 : 1);
  5 for (int i = 0; i < times; i++)
  6 {
  7 	int startIndex = i * 10000;
  8 	int endIndex = (i + 1) * 10000;
  9 	endIndex = endIndex > dataCount ? dataCount : endIndex;
 10 	int currDataCount = endIndex - startIndex;
 11 	columnsData.Add("COL1", new string[currDataCount]);
 12 	columnsData.Add("COL2", new string[currDataCount]);
 13 	columnsData.Add("COL3", new decimal[currDataCount]);
 14 	columnsData.Add("COL4", new string[currDataCount]);
 15 	columnsData.Add("COL5", new decimal[currDataCount]);
 16 	columnsData.Add("COL6", new string[currDataCount]);
 17 	for (int rowIndex = startIndex; rowIndex < endIndex; rowIndex++)
 18 	{
 19 		int dicRowIndex = rowIndex - startIndex;// 列數據行索引
 20 		foreach (string colName in columnsData.Keys)
 21 		{
 22 			object cell = m_SourceDataTable.Rows[rowIndex][colName];
 23 			string cellStr = (cell + "").TrimEnd(new char[] { '\0', ' ' });
 24 			if (colName == "COL3" || colName == "COL5")
 25 			{
 26 				decimal value = 0;
 27 				decimal.TryParse(cellStr, out value);
 28 				((decimal[])columnsData[colName])[dicRowIndex] = value;
 29 			}
 30 			else
 31 			{
 32 				((string[])columnsData[colName])[dicRowIndex] = cellStr;
 33 			}
 34 		}
 35 	}
 36 	m_DAL.InsertBigData(columnsData, currDataCount);
 37 
 38 	columnsData.Clear();
 39 	GC.Collect();
 40 	GC.WaitForFullGCComplete();
 41 }
View Code

方式二:OracleBulkCopy

說明:

1. OracleBulkCopy 採用 direct path 方式導入;

2. 不支持 transaction,無法 Rollback;

3. 如果該表存在觸發器時,無法使用 OracleBulkCopy(報異常信息 Oracle Error: ORA-26086),除非先禁用該表的所有觸發器;

4. 過程中會自動啟用 NOT NULL、UNIQUE 和 PRIMARY KEY 三種約束,其中 NOT NULL 約束在列數組綁定時驗證,任何違反 NOT NULL 約束條件的行數據都會捨棄;UNIQUE 約束是在導入完成後重建索引時驗證,但是在 bulk copy 時,允許違反索引約束,併在完成後將索引設置成禁用(UNUSABLE)狀態;而且,如果索引一開始狀態就是禁用(UNUSABLE)狀態時,OracleBulkCopy 是會報錯的。

參考代碼如下:

  1 /// <summary>
  2 /// 批量插入數據
  3 /// 該方法需要禁用該表所有觸發器,並且插入的數據如果為空,是不會採用預設值
  4 /// </summary>
  5 /// <param name="table">數據表</param>
  6 /// <param name="targetTableName">資料庫目標表名</param>
  7 /// <returns></returns>
  8 public bool InsertBulkData(DataTable table, string targetTableName)
  9 {
 10 	bool result = false;
 11 	string connStr = GetConnectionString();
 12 	using (OracleConnection connection = new OracleConnection(connStr))
 13 	{
 14 		using (OracleBulkCopy bulkCopy = new OracleBulkCopy(connStr, OracleBulkCopyOptions.Default))
 15 		{
 16 			if (table != null && table.Rows.Count > 0)
 17 			{
 18 				bulkCopy.DestinationTableName = targetTableName;
 19 				for (int i = 0; i < table.Columns.Count; i++)
 20 				{
 21 					string col = table.Columns[i].ColumnName;
 22 					bulkCopy.ColumnMappings.Add(col, col);
 23 				}
 24 				connection.Open();
 25 				bulkCopy.WriteToServer(table);
 26 				result = true;
 27 			}
 28 			bulkCopy.Close();
 29 			bulkCopy.Dispose();
 30 		}
 31 	}
 32 
 33 	return result;
 34 }

測試結果:

數據類型:4列NVARCHAR2,2列NUMBER

30+萬(7.36M):用時 14:590

60+萬(14.6M):用時 28:28

1048576(24.9M):用時 52:971

附加,禁用表的所有外鍵SQL:

ALTER TABLE table_name DISABLE ALL TRIGGERS

總結

1、在30+萬和60+萬數據時,ArrayBind一次性導入和OracleBulkCopy時間相差不是很大,但是ArrayBind方式一般都需要轉換數據形式,占用了一些時間,而 OracleBulkCopy 則只需要簡單處理一下 DataTable 數據源即可導入;

2、當數據量達到100+萬時,ArrayBind很容易出現記憶體不足異常,此時只能採用分批次執行導入,根據測試結果可知,次數越少,速度越快;而採用 OracleBulkCopy 方式則很少出現記憶體不足現象,由此可見 OracleBulkCopy 占用記憶體比 ArrayBind 方式少;

參考資料:

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

2、ArrayBind http://www.soaspx.com/dotnet/csharp/csharp_20130911_10501.html

3、Oracle數據導入方法 http://dbanotes.net/Oracle/All_About_Oracle_Data_Loading.htm

4、介紹OracleBulkCopy類 https://docs.oracle.com/cd/E11882_01/win.112/e23174/OracleBulkCopyClass.htm#ODPNT7446

5、http://dba.stackexchange.com/questions/7287/what-specifically-does-oraclebulkcopy-do-and-how-can-i-optimize-its-performance


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 如果想對自己自定義的類進行解檔和歸檔的話 必須遵循一個協議:NSCoding Student.h 文件 Student.m 文件 客戶端代碼 運行結果:
  • 此文章來自:聽雲博客 很多時候需要網路抓包分析,在iPhone上抓包稍有不同,下麵介紹三種常用的方式。分析工具以wireshark為例。 一、最簡單的方式:用PC作為熱點,在PC上抓包 優點:簡單 缺點:不能抓真機2g/3g/4g網路數據 步驟如下: 1、PC接上有線 2、PC用wifi方式共用網路
  • 載入使用AnimationUtils.load方法載入 一般都用代碼控制 如果需要 用XML控制可以在res\下建立 animator,即可, 在這個目錄建立XML文件如果 下方法導入
  • 先說一下為什麼要講框架的設計。 第一、IM應用一般是基於長連接的,也就是後臺一直在收發數據,那這裡就有一個後臺的概念; 第二、如果用戶是一個人群裡面的中心人物的話,那麼他的的數據量就會很大。頁面的顯示及資料庫的處理就需要關註了; 第三、分解app有利於我們降低耦合,在後期維護和升級時,稍微容易一點。
  • iOS證書突然失效 今早上班打包直接報錯,錯誤如圖 根據錯誤信息到“鑰匙串”裡面看了一下證書,證書都莫名其妙的失效了,昨天還是好好的。 重新去鑰匙串從證頒發中心獲取證書,然後登陸開發者賬號重新申請證書,然後添加到鑰匙串中,再次打包發現剛申請成功的證書還是無效的。折騰了半天才找到解決方案。 解決方法:
  • UICollectionView是一種新的數據展示方式,簡單來說可以把他理解成多列的UITableView(請一定註意這是UICollectionView的最最簡單的形式)。如果你用過iBooks的話,可能你還對書架佈局有一定印象:一個虛擬書架上放著你下載和購買的各類圖書,整齊排列。其實這就是一個U
  • 屏幕左邊緣右滑返回,TabBar 滑動切換,你是否喜歡並十分依賴這兩個操作,甚至覺得 App 不支持這類操作的話簡直反人類?這兩個操作在大屏時代極大提升了操作效率,其背後的技術便是今天的主題:視圖控制器轉換(View Controller Transition)。 視圖控制器中的視圖顯示在屏幕上有兩
  • 很多開發者一聽說Android終端的屏幕尺寸五花八門,屏幕解析度千奇百怪,就覺得Android開發在屏幕適配方面是必定是一件頭疼的事情。因為在Android問世之前,廣大開發者知道的UI解決方案大致分為兩類: 在Web開發中的CSS,一層一層的去層疊樣式。 在iOS開發中去計算每一個UIView的尺...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...