OleDb 内存泄露问题

Suriyel 2014-06-20 01:50:15
近期在定位问题时发现使用OleDb打开很大的Excel文件后,即使什么都不操作Colse掉,内存释放了部分,但是并未回到打开前的水平。在Excel 150M,解压缩后900M的场景下,打开后直接Close,内存比打开前多了近90M。如果再次打开关闭,内存不会再增加。但是如果是依此打开多个不同的大型Excel文件,程序很容易内存溢出崩掉。
在会用Red Gate进行内存分析时,发现确实是Excel非托管代码,在不断累积内存,想了很久,目前想到的有3种解决方案。

首先,先上问题复现的代码,很简单,就是使用OleDb打开一个150M左右的Excel


private static void Test()
{
Console.Read();
using (var con=new OleDbConnection(string.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source='{0}';Extended Properties='Excel 8.0;HDR=Yes;IMEX=1'",@"F:/1.xls")))
{
con.Open();
con.Close();
}
GC.Collect();
Console.Read();
}


复现步骤:
1,准备一个150M的Excel
2,打开任务管理器,打开程序。
3,记录当前程序内存。
4,回车,等到文件读取完毕,并Close释放内存稳定后,再记录当前程序内存。
5,发现多处了70-90M


解决方案
1,经过验证,CSV类型,使用OleDb打开后没有出现类似的内存泄露。所以,可以先把xls、xlsx转为csv,再对csv使用oleDb打开进行操作,最后删掉该csv即可。
如果使用该方法需要注意,csv读取不能根据Sheet读取,只能读整个文件。
xls、xlsx的转化需要使用Microsoft.Office.Interop.Excel.Apllication ,所以需要把当前所需的数据库操作封装一层。

2,直接使用Microsoft.Office.Interop.Excel.Apllication进行Excel操作,并在操作后直接杀掉Excel进程。
但是该方法因为Microsoft.Office.Interop.Excel.Apllication进行Excel读取,每个Range每个Range的读取,都是进程间进行数据交换,性能太渣。唯一可行的是,一大块一大块的读数据,但是没有找到对应的方法。

3,另起一个进程,使用OleDb对Excel进行操作,操作后杀掉该进程。
这个方案可行度也不高,在C#下,要另起进程,带来的稳定性、性能开销都比较大,特别是如果是频繁读取Excel的场景下。


需要解决的问题
1,还有没有更好的办法,在不使用Microsoft.Office.Interop.Excel.Apllication的情况下,解决这一问题。因为使用Microsoft.Office.Interop.Excel.Apllication,就意味着要开个Excel进程,意味着性能开销。
2,OleDb在打开Excel文件时,会进行解压缩,150M 的文件,Open完毕后,程序进程能增加900多M。而使用Microsoft.Office.Interop.Excel.Apllication进行打开,发现只增加了150M。能否在使用OleDb的情况下,减小这部分的内存增加,要知道在程序本身就很耗内存的情况下,打开个Excel就陡增了900M,是很容易崩掉的。


以下是微软提供的解决办法
http://support.microsoft.com/kb/319998
...全文
468 19 打赏 收藏 转发到动态 举报
写回复
用AI写文章
19 条回复
切换为时间正序
请发表友善的回复…
发表回复
於黾 2014-07-23
  • 打赏
  • 举报
回复
还是改用NPOI算了,还方便,不用考虑用户安装的OFFICE版本,也不用考虑用户是否用的WPS
Suriyel 2014-07-23
  • 打赏
  • 举报
回复
引用 13 楼 liucqa 的回复:
这个是老毛病了,换个高版本的,或者把excel文件先改成只读,再读取,然后改回来就行了
改成只读,内存还是有泄露啊 换个高版本指的的是Framework还是office?
  • 打赏
  • 举报
回复
很明显,如果你搞不明白基本概念,那么就算你瞎用什么软件来代替,都脱离不了基本的托管程序系统的内存分配策略的制约。不管用什么PPOO的软件,你都会说它“内存泄露”的。 而且另一个问题是,不管用什么软件,如果你要在内存里将多个 900M 的Excel文件内容同时读取到内存里,都一样会崩溃。
  • 打赏
  • 举报
回复
如果你说“打开、关闭”一次就内存泄露 90M,那么你应该顺序“打开、关闭”10次,于是内存“泄露”就应该是 900M 了。而如果“如果再次打开关闭,内存不会再增加”,这就毫无道理说什么“内存泄露”。 我建议你先搞明白这是不是内存泄露,再来查找对策吧。
泡泡龙 2014-07-23
  • 打赏
  • 举报
回复
http://club.excelhome.net/thread-464455-1-1.html 或者 Provider=Microsoft.ACE.OLEDB.12.0 试试吧
Suriyel 2014-07-14
  • 打赏
  • 举报
回复
引用 13 楼 liucqa 的回复:
这个是老毛病了,换个高版本的,或者把excel文件先改成只读,再读取,然后改回来就行了
我试试
泡泡龙 2014-07-10
  • 打赏
  • 举报
回复
这个是老毛病了,换个高版本的,或者把excel文件先改成只读,再读取,然后改回来就行了
Suriyel 2014-07-09
  • 打赏
  • 举报
回复
引用 9 楼 Shannon_Li 的回复:
理下楼主的主要需求 1,不能直接在当前进程中进行OleDb读取Excel。因为按楼主说的,OleDb存在内存泄露。 那么解决思路应该有以下几个方向: 1,能否用其他的方法进行Excel的读取。 2,能否再其他进程进行Excel的读取,然后将结果传给当前进程。 思路1,要么使用开源的Excel读取程序,要么使用Excel的Application,但按楼主的意思,这两个都不行。 那么只能采用第2种思路了。 对于思路2,最关键的应该是,采用什么的形式将结果传给当前进程。 需要注意以下的约束: 1,是否需要考虑较大的Excel文件。 2,是通过进程间通信传,还是通过转为文本,主程序读取获得。 考虑到以上的约束,我建议楼主另外开个进程,使用OleDb把Excel里所需的数据,转为csv或者txt,主线程可以使用Reader逐行读取解析。既可以规避你说的内存泄露问题,又最大限度的不影响性能和内存开销。
感觉很有理,我先试试
Suriyel 2014-07-09
  • 打赏
  • 举报
回复
引用 10 楼 Z65443344 的回复:
刚做了ADODB读取EXCEL功能,可以将EXCEL当数据库读 数据库连接串就是版本号那一套,跟OLEDB一样 用户名密码为空 唯一缺点就是必须事先知道Sheet名称 或者可以用第三方插件,比如NOPI?
就是ADO,OleDb读取Excel,存在内存泄露啊,你用一个大一点的Excel,只是Open再Close试试,内存是没有回退到原有水平的,调了Gc都没用。 我感觉Shannon的方法比较可行。
於黾 2014-07-09
  • 打赏
  • 举报
回复
刚做了ADODB读取EXCEL功能,可以将EXCEL当数据库读 数据库连接串就是版本号那一套,跟OLEDB一样 用户名密码为空 唯一缺点就是必须事先知道Sheet名称 或者可以用第三方插件,比如NOPI?
Shannon_Li 2014-07-09
  • 打赏
  • 举报
回复
理下楼主的主要需求 1,不能直接在当前进程中进行OleDb读取Excel。因为按楼主说的,OleDb存在内存泄露。 那么解决思路应该有以下几个方向: 1,能否用其他的方法进行Excel的读取。 2,能否再其他进程进行Excel的读取,然后将结果传给当前进程。 思路1,要么使用开源的Excel读取程序,要么使用Excel的Application,但按楼主的意思,这两个都不行。 那么只能采用第2种思路了。 对于思路2,最关键的应该是,采用什么的形式将结果传给当前进程。 需要注意以下的约束: 1,是否需要考虑较大的Excel文件。 2,是通过进程间通信传,还是通过转为文本,主程序读取获得。 考虑到以上的约束,我建议楼主另外开个进程,使用OleDb把Excel里所需的数据,转为csv或者txt,主线程可以使用Reader逐行读取解析。既可以规避你说的内存泄露问题,又最大限度的不影响性能和内存开销。
於黾 2014-06-24
  • 打赏
  • 举报
回复
我回复的就是针对: 2,直接使用Microsoft.Office.Interop.Excel.Apllication进行Excel操作,并在操作后直接杀掉Excel进程。 但是该方法因为Microsoft.Office.Interop.Excel.Apllication进行Excel读取,每个Range每个Range的读取,都是进程间进行数据交换,性能太渣。唯一可行的是,一大块一大块的读数据,但是没有找到对应的方法 可以整个文档读取啊.
於黾 2014-06-24
  • 打赏
  • 举报
回复
        private static void KillExcel()
        {
            Process killer = new Process();
            try
            {
                foreach (Process thisproc in Process.GetProcessesByName("EXCEL"))   //循环查找
                {
                    string str = thisproc.MainWindowTitle; //发现程序中打开跟用户自己打开的区别就在这个属性
                    //用户打开的str 是文件的名称,程序中打开的就是空字符串
                    if (string.IsNullOrEmpty(str))
                    {
                        thisproc.Kill();
                    }
                }
            }
            catch
            {
                string msg = "关闭失败";
                MessageBox.Show(msg);
            }
        }
有没有看进程里是否多了很多EXCEL进程?每次操作完杀死多余的没有界面的EXCEL进程试试.
Suriyel 2014-06-24
  • 打赏
  • 举报
回复
引用 1 楼 rui_china 的回复:
[quote=引用 楼主 Suriyel 的回复:] 近期在定位问题时发现使用OleDb打开很大的Excel文件后,即使什么都不操作Colse掉,内存释放了部分,但是并未回到打开前的水平。在Excel 150M,解压缩后900M的场景下,打开后直接Close,内存比打开前多了近90M。如果再次打开关闭,内存不会再增加。但是如果是依此打开多个不同的大型Excel文件,程序很容易内存溢出崩掉。 在会用Red Gate进行内存分析时,发现确实是Excel非托管代码,在不断累积内存,想了很久,目前想到的有3种解决方案。 首先,先上问题复现的代码,很简单,就是使用OleDb打开一个150M左右的Excel

private static void Test()
        {
 Console.Read();
            using (var con=new OleDbConnection(string.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source='{0}';Extended Properties='Excel 8.0;HDR=Yes;IMEX=1'",@"F:/1.xls")))
            {
                con.Open();
                con.Close();              
            }
  GC.Collect();
 Console.Read();
        }
复现步骤: 1,准备一个150M的Excel 2,打开任务管理器,打开程序。 3,记录当前程序内存。 4,回车,等到文件读取完毕,并Close释放内存稳定后,再记录当前程序内存。 5,发现多处了70-90M 解决方案 1,经过验证,CSV类型,使用OleDb打开后没有出现类似的内存泄露。所以,可以先把xls、xlsx转为csv,再对csv使用oleDb打开进行操作,最后删掉该csv即可。 如果使用该方法需要注意,csv读取不能根据Sheet读取,只能读整个文件。 xls、xlsx的转化需要使用Microsoft.Office.Interop.Excel.Apllication ,所以需要把当前所需的数据库操作封装一层。 2,直接使用Microsoft.Office.Interop.Excel.Apllication进行Excel操作,并在操作后直接杀掉Excel进程。 但是该方法因为Microsoft.Office.Interop.Excel.Apllication进行Excel读取,每个Range每个Range的读取,都是进程间进行数据交换,性能太渣。唯一可行的是,一大块一大块的读数据,但是没有找到对应的方法。 3,另起一个进程,使用OleDb对Excel进行操作,操作后杀掉该进程。 这个方案可行度也不高,在C#下,要另起进程,带来的稳定性、性能开销都比较大,特别是如果是频繁读取Excel的场景下。 需要解决的问题 1,还有没有更好的办法,在不使用Microsoft.Office.Interop.Excel.Apllication的情况下,解决这一问题。因为使用Microsoft.Office.Interop.Excel.Apllication,就意味着要开个Excel进程,意味着性能开销。 2,OleDb在打开Excel文件时,会进行解压缩,150M 的文件,Open完毕后,程序进程能增加900多M。而使用Microsoft.Office.Interop.Excel.Apllication进行打开,发现只增加了150M。能否在使用OleDb的情况下,减小这部分的内存增加,要知道在程序本身就很耗内存的情况下,打开个Excel就陡增了900M,是很容易崩掉的。 以下是微软提供的解决办法 http://support.microsoft.com/kb/319998
NPOI呢?[/quote] 额,我们这边对开源项目审核的比较厉害,所以能不用开源的尽量没有用,哎
Suriyel 2014-06-24
  • 打赏
  • 举报
回复
引用 2 楼 Z65443344 的回复:

郑笑彬 08:35:05
 public DataSet ImportFromExcel(string FilePath)
        {
            System.Data.DataTable dt = new System.Data.DataTable();
            ArrayList TablesList = new ArrayList();

            System.Data.DataSet ds = new DataSet();

            OleDbConnection dbcon = null;
            Microsoft.Office.Interop.Excel.Application excelApp = new Microsoft.Office.Interop.Excel.Application();
            string ss = excelApp.Version;
            switch (ss)
            {
                case "12.0":
                    dbcon = new OleDbConnection("Provider=Microsoft.ACE." + "OLEDB.12.0;Extended Properties=\"Excel 8.0\";Data Source=" + FilePath);
                    break;
                default:
                    dbcon = new OleDbConnection(@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + FilePath + ";Extended Properties=Excel 8.0");
                    break;
            }

            try
            {
                TablesList = GetExcelTables(FilePath);

                try
                {
                    for (int i = 0; i < TablesList.Count; i++)
                    {
                        string str = "select * from [" + TablesList[i] + "$]";

                        System.Data.DataTable table;

                        OleDbCommand cmd = new OleDbCommand(str, dbcon);
                        OleDbDataAdapter adapter = new OleDbDataAdapter(cmd);
                        table = new System.Data.DataTable(TablesList[i].ToString());

                        adapter.Fill(table);
                        ds.Tables.Add(table);
                    }
                }
                catch (Exception exp)
                {
                    MessageBox.Show(exp.Message, "读取EXCEL工作簿出错");
                }
            }
            finally
            {
            }
            return ds;
        }

使用Microsoft.Office.Interop.Excel.Apllication可以直接读取整个文档到dataset啊 也可以指定单个sheet读取到datatable 怎么可能只能按Range读取呢
从代码上来看,好像你只用application判断了下Excel版本,还是使用的oledb进行Excel数据读取,那么还是会有内存泄露问题。
Suriyel 2014-06-24
  • 打赏
  • 举报
回复
引用 7 楼 Z65443344 的回复:
我回复的就是针对: 2,直接使用Microsoft.Office.Interop.Excel.Apllication进行Excel操作,并在操作后直接杀掉Excel进程。 但是该方法因为Microsoft.Office.Interop.Excel.Apllication进行Excel读取,每个Range每个Range的读取,都是进程间进行数据交换,性能太渣。唯一可行的是,一大块一大块的读数据,但是没有找到对应的方法 可以整个文档读取啊.
谢谢你的回复,这贴的关键讨论点,就是使用OleDb会导致内存泄露,这就是我使用application而不使用OleDb(你贴的代码里使用的方式)的原因,您贴的这个第2点,是在这个基础上说,application操作Excel性能消耗太大,不可用。 我希望的是能够解决OleDb的内存泄露,但是您代码里直接使用的是OleDb,是不能解决这个问题的。
於黾 2014-06-20
  • 打赏
  • 举报
回复
@版主看到请帮忙把出现的飞秋用户名删掉,忘记删除了
於黾 2014-06-20
  • 打赏
  • 举报
回复

郑笑彬 08:35:05
 public DataSet ImportFromExcel(string FilePath)
        {
            System.Data.DataTable dt = new System.Data.DataTable();
            ArrayList TablesList = new ArrayList();

            System.Data.DataSet ds = new DataSet();

            OleDbConnection dbcon = null;
            Microsoft.Office.Interop.Excel.Application excelApp = new Microsoft.Office.Interop.Excel.Application();
            string ss = excelApp.Version;
            switch (ss)
            {
                case "12.0":
                    dbcon = new OleDbConnection("Provider=Microsoft.ACE." + "OLEDB.12.0;Extended Properties=\"Excel 8.0\";Data Source=" + FilePath);
                    break;
                default:
                    dbcon = new OleDbConnection(@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + FilePath + ";Extended Properties=Excel 8.0");
                    break;
            }

            try
            {
                TablesList = GetExcelTables(FilePath);

                try
                {
                    for (int i = 0; i < TablesList.Count; i++)
                    {
                        string str = "select * from [" + TablesList[i] + "$]";

                        System.Data.DataTable table;

                        OleDbCommand cmd = new OleDbCommand(str, dbcon);
                        OleDbDataAdapter adapter = new OleDbDataAdapter(cmd);
                        table = new System.Data.DataTable(TablesList[i].ToString());

                        adapter.Fill(table);
                        ds.Tables.Add(table);
                    }
                }
                catch (Exception exp)
                {
                    MessageBox.Show(exp.Message, "读取EXCEL工作簿出错");
                }
            }
            finally
            {
            }
            return ds;
        }

使用Microsoft.Office.Interop.Excel.Apllication可以直接读取整个文档到dataset啊 也可以指定单个sheet读取到datatable 怎么可能只能按Range读取呢
灬浪子灬 2014-06-20
  • 打赏
  • 举报
回复
引用 楼主 Suriyel 的回复:
近期在定位问题时发现使用OleDb打开很大的Excel文件后,即使什么都不操作Colse掉,内存释放了部分,但是并未回到打开前的水平。在Excel 150M,解压缩后900M的场景下,打开后直接Close,内存比打开前多了近90M。如果再次打开关闭,内存不会再增加。但是如果是依此打开多个不同的大型Excel文件,程序很容易内存溢出崩掉。 在会用Red Gate进行内存分析时,发现确实是Excel非托管代码,在不断累积内存,想了很久,目前想到的有3种解决方案。 首先,先上问题复现的代码,很简单,就是使用OleDb打开一个150M左右的Excel

private static void Test()
        {
 Console.Read();
            using (var con=new OleDbConnection(string.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source='{0}';Extended Properties='Excel 8.0;HDR=Yes;IMEX=1'",@"F:/1.xls")))
            {
                con.Open();
                con.Close();              
            }
  GC.Collect();
 Console.Read();
        }
复现步骤: 1,准备一个150M的Excel 2,打开任务管理器,打开程序。 3,记录当前程序内存。 4,回车,等到文件读取完毕,并Close释放内存稳定后,再记录当前程序内存。 5,发现多处了70-90M 解决方案 1,经过验证,CSV类型,使用OleDb打开后没有出现类似的内存泄露。所以,可以先把xls、xlsx转为csv,再对csv使用oleDb打开进行操作,最后删掉该csv即可。 如果使用该方法需要注意,csv读取不能根据Sheet读取,只能读整个文件。 xls、xlsx的转化需要使用Microsoft.Office.Interop.Excel.Apllication ,所以需要把当前所需的数据库操作封装一层。 2,直接使用Microsoft.Office.Interop.Excel.Apllication进行Excel操作,并在操作后直接杀掉Excel进程。 但是该方法因为Microsoft.Office.Interop.Excel.Apllication进行Excel读取,每个Range每个Range的读取,都是进程间进行数据交换,性能太渣。唯一可行的是,一大块一大块的读数据,但是没有找到对应的方法。 3,另起一个进程,使用OleDb对Excel进行操作,操作后杀掉该进程。 这个方案可行度也不高,在C#下,要另起进程,带来的稳定性、性能开销都比较大,特别是如果是频繁读取Excel的场景下。 需要解决的问题 1,还有没有更好的办法,在不使用Microsoft.Office.Interop.Excel.Apllication的情况下,解决这一问题。因为使用Microsoft.Office.Interop.Excel.Apllication,就意味着要开个Excel进程,意味着性能开销。 2,OleDb在打开Excel文件时,会进行解压缩,150M 的文件,Open完毕后,程序进程能增加900多M。而使用Microsoft.Office.Interop.Excel.Apllication进行打开,发现只增加了150M。能否在使用OleDb的情况下,减小这部分的内存增加,要知道在程序本身就很耗内存的情况下,打开个Excel就陡增了900M,是很容易崩掉的。 以下是微软提供的解决办法 http://support.microsoft.com/kb/319998
NPOI呢?

111,087

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • AIGC Browser
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧