最近应需求写了一个oracle到mysql的数据导入工具,本来想随便写写,直接把数据一次读到内存然后再写。后来发现性能略差,就试图做点性能优化,使用IDataReader,一边读一边写,然而mysql写速度根本跟不上oracle读速度,而且测试机只有双核4线程,开4个写线程CPU75%,6个写线程就95%了。
不过没有用到sql优化,mysql插入优化主要是拼接sql,比如说在values后面拼1000个()。oracle就比较方便,参数绑定可以支持数组,这样就可以批量执行。类似于这样。
public int OracleExecuteBatch(string sql, List<DbType> types, List<List<object>> paramList) { if (string.IsNullOrWhiteSpace(sql)) { return -1; } string commandText = ""; int num = 0; if (!this._ParseSQL(sql, ref commandText, ref num)) { return -1; } OracleCommand oracleCommand = new OracleCommand(); oracleCommand.Connection = (OracleConnection)this.mCon; oracleCommand.CommandText = commandText; for (int i = 0; i < types.Count; i++) { DbParameter dbParameter = oracleCommand.CreateParameter(); dbParameter.DbType = types[i]; dbParameter.Value = paramList[i].ToArray(); dbParameter.ParameterName = this.mParamNamePre + i; oracleCommand.Parameters.Add(dbParameter); } int count = paramList[0].Count; oracleCommand.ArrayBindCount = count; return oracleCommand.ExecuteNonQuery(); }
当然我想解决的问题不是这个问题,我的工具在数据同步完成后内存没有下降,这让我怀疑我是不是哪里把内存写漏?拿VS远程调试把进程停下来也没发现有几个线程,于是只能祭出Windbg。
按前文和参考文档,先载入dump文件(其实可以直接调试进程),然后输入 !DumpHeap -stat 分析托管对象。过了10几分钟后才得到结果,原来里面还有几十亿个临时对象没有释放。ProcessExplorer 显示二代堆占用了大部分内存。
然后点击第一列 windbg 会自动输入 !DumpHeap /d -mt 000007fe8a61e3a0 这样就会打印这个类型所有对象,于是就打印了一个多小时(
点击对象前地址会自动输入 !DumpObj /d 00000003c359f750,这就打印了对象的所有信息
用 !gcroot 检查这个对象的引用情况,发现没有任何引用(
然后拿 vs 远程调试暂停下进程,输入 System.GC.Collect() 内存瞬间释放(
结论:垃圾回收好尼玛偷懒,线程长时间运行时,对象会移动到二代堆,然而等到线程退出时虚拟机也不好好回收一下,毕竟系统内存还有富余,最后只能定时主动调用 System.GC.Collect() 以达到回收内存的目的。
参考:
WinDBG找出你内存溢出的地方