2013-08-27 24 views
2

我正在研究一些企業應用程序,每天處理大量數據,並且要使用C#.NET 4編寫WINDOWS SERVICE應用程序。它也與SQL SERVER 2008 R2有連接,但由於某些原因它(隨機)拋出我在同步表這個錯誤存儲JSON序列數據:SqlClient返回奇怪的OOM異常? C#.NET 4

Exception of type 'System.OutOfMemoryException' was thrown. 
at System.Data.SqlClient.TdsParser.ReadPlpUnicodeChars(Char[]& buff, Int32 offst, Int32 len, TdsParserStateObject stateObj) 
at System.Data.SqlClient.TdsParser.ReadSqlStringValue(SqlBuffer value, Byte type, Int32 length, Encoding encoding, Boolean isPlp, TdsParserStateObject stateObj) 
at System.Data.SqlClient.TdsParser.ReadSqlValue(SqlBuffer value, SqlMetaDataPriv md, Int32 length, TdsParserStateObject stateObj) 
at System.Data.SqlClient.SqlDataReader.ReadColumnData() 
at System.Data.SqlClient.SqlDataReader.ReadColumn(Int32 i, Boolean setTimeout) 
at System.Data.SqlClient.SqlDataReader.GetValueInternal(Int32 i) 
at System.Data.SqlClient.SqlDataReader.GetValues(Object[] values) 

此表是相當普遍的表,以保持LOB數據:即失敗了36.231

CREATE TABLE [dbo].[SyncJobItem](
[id_job_item] [int] IDENTITY(1,1) NOT NULL, 
[id_job] [int] NOT NULL, 
[id_job_item_type] [int] NOT NULL, 
[id_job_status] [int] NOT NULL, 
[id_c] [int] NULL, 
[id_s] [int] NULL, 
[job_data] [nvarchar](max) NOT NULL, 
[last_update] [datetime] NOT NULL, 
CONSTRAINT [PK_SyncJobItem] PRIMARY KEY CLUSTERED) 

LOB記錄。在job_data列中有800個字符的數據,這是(如果我們說1個字符是2個字節,UTF-8)約70MB的數據不多。

請考慮改變作業數據存儲(如磁盤)或類似的東西不是我的選擇。我想解決這個錯誤,所以如果有人知道任何事情,請幫助!

此外,這個錯誤在相同的數據上隨機發生,系統運行的是vmWare-vCloud,也就是我認爲的一些大刀片系統。我們有大約6GB的RAM專用於我們的vm(服務最多使用大約1-2GB),服務編譯爲x64,系統爲x64 Windows 2008R2 Standard。我已經確定沒有單個對象的內存超過2GB,所以不是這樣,SqlClient內部也有錯誤,而我從未見過它的15年開發經驗,Google什麼都沒發現。此外,錯誤不在DB端,因爲DB擁有超過32GB的RAM,並且只使用20GB的峯值。對於我在這個系統中使用的不常用的細節,在每個作業步驟之後(數據上有多個步驟)是多線程和GC.Collect()。

編輯:

這裏是做這個問題全碼:

internal static void ExecuteReader(IConnectionProvider conn, IList destination, IObjectFiller objectBuilder, string cmdText, DbParameterCollection parameters, CommandType cmdType, int cmdTimeout) 
    { 
     IDbCommand cmd = CreateCommand(conn.DBMS, cmdText, parameters, cmdType, cmdTimeout); 
     cmd.Connection = conn.Connection; 

     bool connIsOpennedLocally = EnsureOpenConnection(conn); 
     try 
     { 
      AssignExistingPendingTransactionToCommand(conn, cmd); 
      using (IDataReader reader = cmd.ExecuteReader(CommandBehavior.SingleResult)) 
      { 
       objectBuilder.FillCollection(reader, destination); 
       PopulateOutputParameterValues(parameters, cmd); 
      } 
     } 
     finally 
     { 
      CloseConnectionIfLocal(conn, connIsOpennedLocally); 
      cmd.Dispose(); 
     } 
    } 

... 

    private void FillFromAlignedReader(ICollection<TEntity> collection, IDataReader openedDataReader, IDbTable table) 
    { 
     // Fastest scenario: data reader fields match entity field completely. 
     // It's safe to reuse same array because GetValues() always overwrites all members. Memory is allocated only once. 
     object[] values = new object[openedDataReader.FieldCount]; 
     while (openedDataReader.Read()) 
     { 
      openedDataReader.GetValues(values); 
      TEntity entity = CreateEntity(table, EntityState.Synchronized, values); 
      collection.Add(entity); 
     } 
    } 
+0

你能澄清你「crunches large amount of data」是什麼意思嗎>你是否從你建議的表格中讀取數據並對數據進行處理?或者你正在閱讀其他表並寫入這張表嗎?數據處理完畢後,你對數據做了什麼?在最終刷新之前它是否保留在內存中,或者是否在逐行的基礎上從內存寫入和刷新? – GarethD

+0

我正在將數據從SQL Server加載到DataTable。這是什麼時候呢。這是簡單的選擇語句。我正在對數據進行一些計算之前和之後。基本上,它是這樣的: 1)裝載從NoSQL的分貝(Couchbase)數據 2)聚集數據與地圖,減少 3)序列化聚合結果對象到JSON對象 4)保存到DB該表 5 )轉到下一步,從SQL加載JSON(這裏它與OOM分開) –

+0

當您說*我已確保單個對象在內存中沒有超過2GB *時,是否在步驟5中包含DataTable?你能用一個只向前移動的SqlDataReader來實現同樣的事情嗎,這樣你在內存中一次只能有一行?我認爲查看拋出錯誤的代碼塊以及堆棧跟蹤會很有用。 – GarethD

回答

2

對於那些誰大量的測試和MSDN(link)我得出結論後遇到此問題的最大單場大小能夠在正常讀取模式下SqlDataReader讀取大約是64臺機器上70MB,在此之後它需要將其SqlCommand切換爲CommandBehavior.SequentialAccess並傳輸字段內容。

示例代碼,將工作這樣的:當你在你需要以獲取列,當你到達BLOB列,你應該叫這樣的一個循環中讀取數據

... 
    behaviour = CommandBehavior.SequentialAccess; 
    using (IDataReader reader = cmd.ExecuteReader(behaviour)) 
    { 
     filler.FillData(reader, destination); 
    } 

(取決於數據類型):

... 
    private string GetBlobDataString(IDataReader openedDataReader, int columnIndex) 
    { 
     StringBuilder data = new StringBuilder(20000); 
     char[] buffer = new char[1000]; 
     long startIndex = 0; 

     long dataReceivedCount = openedDataReader.GetChars(columnIndex, startIndex, buffer, 0, 1000); 
     data.Append(buffer, 0, (int)dataReceivedCount); 
     while (dataReceivedCount == 1000) 
     { 
      startIndex += 1000; 
      dataReceivedCount = openedDataReader.GetChars(columnIndex, startIndex, buffer, 0, 1000); 
      data.Append(buffer, 0, (int)dataReceivedCount); 
     } 

     return data.ToString(); 
    } 

    private byte[] GetBlobDataBinary(IDataReader openedDataReader, int columnIndex) 
    { 
     MemoryStream data = new MemoryStream(20000); 
     BinaryWriter dataWriter = new BinaryWriter(data); 

     byte[] buffer = new byte[1000]; 
     long startIndex = 0; 

     long dataReceivedCount = openedDataReader.GetBytes(columnIndex, startIndex, buffer, 0, 1000); 
     dataWriter.Write(buffer, 0, (int)dataReceivedCount); 
     while (dataReceivedCount == 1000) 
     { 
      startIndex += 1000; 
      dataReceivedCount = openedDataReader.GetBytes(columnIndex, startIndex, buffer, 0, 1000); 
      dataWriter.Write(buffer, 0, (int)dataReceivedCount); 
     } 

     data.Position = 0; 
     return data.ToArray(); 
    } 

這應用於數據工作達約1GB-1.5GB,之後它會打破單個對象上不能夠所以直接從緩衝器保留足夠大小的連續存儲器塊,然後刷新到磁盤或將數據拆分爲多個較小的對象。

-1

我認爲你應該使用DB-類型的文本數據的這些大的量。只有在需要執行搜索時才使用nvarchar。請注意,啓用全文搜索時,這可能會出現奇怪的行爲。

+1

[微軟建議](http: //technet.microsoft.com/en-us/library/ms187993.aspx)文本,NText和圖像數據類型將從sql-server的未來版本中移除,並且'VARCHAR(MAX)','NVARCHAR(MAX)'和'VARBINARY'應該用於他們的位置。我懷疑這是解決方案! – GarethD