我正在开发一些每天处理大量数据的企业应用程序,为此它使用 C# .NET 4 编写的 WINDOWS SERVICE 应用程序。它还连接到 SQL SERVER 2008 R2,但由于某种原因,它(随机)抛出了我存储 JSON 序列化数据的同步表中的此错误:
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 数据的相当通用的表:
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 记录在列中有 36.231.800 个字符的数据job_data
,即(如果我们说 1 个字符是 2 个字节,UTF-8)大约 70MB 的数据,这并不多。
请考虑更改作业数据的存储(例如磁盘)或类似的东西不是我的选择。我想修复这个错误,所以如果有人知道任何事情,请帮忙!
这个错误也随机发生在相同的数据上,运行的系统是 vmWare-vCloud,我认为是一些大型刀片系统。我们有大约 6GB 的 RAM 专用于我们的 vm(服务最多使用大约 1-2GB),服务编译为 x64,系统是 x64 Windows 2008R2 Standard。我已经确保没有单个对象的内存超过 2GB,所以不是这样,SqlClient 内部也有错误,在我 15 年的开发经验中,我从未见过它,谷歌一无所获。此外,由于 DB 具有超过 32GB 的 RAM 并且仅使用 20GB 峰值,因此错误不在 DB 端。对于我在这个系统中使用的不常见的细节是多线程和每个作业步骤之后的 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);
}
}