OracleDataReader.Read()耗时过高问题的解决办法

一、背景

C#连接Oracle数据库,使用的是以下的方式。项目运行了一段时间,突然就出现下面的while循环需要消耗大概100秒左右的时间,但是SQL在Oracle中执行只需要1秒以内。

OracleConnection conn = new OracleConnection(connectString);
OracleCommand comm = new OracleCommand(sb.ToString(), conn);
OracleDataReader dr = comm1.ExecuteReader(); var j = 0; while (dr.Read()) { LocationResult LocationResult = new LocationResult(); //LocationResult.uid = dr["LOCATION_RESULT_UID"].ToString(); Location location = Location.LoadFromLocationID(dr["LOCATION_ID"].ToString()); LocationResult.uniquepartId = dr["UNIQUEPART_ID"].ToString(); LocationResult.lineNo = location.LineNo; LocationResult.location = location.LineNo + "." + location.StatNo + "." + location.StatIdx + "." + location.FuNo + "." + location.WorkPos + "." + location.ToolPos; LocationResult.locationId = location.LocationID; LocationResult.typeNo = dr["TYPE_NUMBER"].ToString(); LocationResult.typeVar = dr["TYPE_VERSION"].ToString(); LocationResult.processNo = dr["PROCESS_NUMBER"].ToString(); LocationResult.resultState = dr["RESULT_STATE"].ToString(); LocationResult.timeStamp = dr["RESULT_DATE"].ToString(); System.Diagnostics.Debug.Write(" -----" + (j++) + " "); if (j == 24)//调试使用 { // break; } if ("51".Equals(location.LineNo.Substring(0, 2)) && !"110".Equals(location.StatNo)) { } else { GetParameters(LocationResult); } info.locationResults.Add(LocationResult); }

二、解决方案(本解决方案是初步解决方案,最终解决方案请看最后)

  增加一个变量j,每次循环的时候自增1,然后再循环的时候打印出来。

观察输出发现,每次都是打印到24的时候会卡个100秒,然后突然打印25-100以后的数值。

然后调试的时候,发现是在while(dr.Read()) 第25次的时候会消耗大量的时间。由于无法进入dr.Read()观察到底是哪里在消耗时间,所以无法继续调试。东搞西搞,一直百度,发现没有人遇到类似的问题。搞了两天左右都搞不定,用户都嗷嗷叫了,使用系统的时候,每次查询都需要等1-2分钟,体验非常糟糕。

  经过各种观察,变换参数,变换查询条件,都是第25次的时候卡住。咨询了现在专门做C#的大学同学,他认为是硬件问题,不像是软件问题。所以我给服务器加了2G的内存,发现问题仍然存在。

  最后在昨天晚上想起来,自己刚刚做过的分页。因为查询的数据不是很多,最多200条以内,所以索性就在数据库分好页,每次只查询20条,不达到25条这个bug。代码完成后,查询简直飞快,感动得我和用户都哭了。。。

  下面上代码:

OracleConnection conn1 = new OracleConnection(connectString);
            try
            {
                string count = "";
                conn1.Open();
                string countsql = "SELECT COUNT(1) TOTAL FROM (" + sb.ToString() + ")A";
                OracleCommand comm2 = new OracleCommand(countsql, conn1);

                OracleDataReader dr2 = comm2.ExecuteReader();//直接查询 dr.Read()到25次的时候会消耗100秒左右的时间,所以此处分页处理,每次只向数据库查询20记录,跳过25这个bug
                if (dr2.Read())
                {
                    count = dr2["TOTAL"].ToString();
                }
                var countInt = Convert.ToInt32(count);
                var page = 20;
                for(int i = 0; i < countInt / page + 1; i++)
                {
                    OracleCommand comm1 = new OracleCommand("SELECT * FROM ( SELECT TMP_PAGE.*, ROWNUM ROW_ID FROM ("+sb.ToString()+ ")TMP_PAGE  WHERE ROWNUM <= "+((i+1)* page) +") WHERE ROW_ID >"+(i* page), conn1);
                    OracleDataReader dr = comm1.ExecuteReader();
                    //System.Diagnostics.Debug.Write("SELECT * FROM ( SELECT TMP_PAGE.*, ROWNUM ROW_ID FROM (" + sb.ToString() + ")TMP_PAGE  WHERE ROWNUM <= " + ((i + 1) * page) + ") WHERE ROW_ID >" + (i * page));
                    var j = 0;
                    while (dr.Read())
                    {
                        LocationResult LocationResult = new LocationResult();
                        //LocationResult.uid = dr["LOCATION_RESULT_UID"].ToString();
                        Location location = Location.LoadFromLocationID(dr["LOCATION_ID"].ToString());
                        LocationResult.uniquepartId = dr["UNIQUEPART_ID"].ToString();

                        LocationResult.lineNo = location.LineNo;
                        LocationResult.location = location.LineNo + "." + location.StatNo + "." + location.StatIdx + "." + location.FuNo + "." + location.WorkPos + "." + location.ToolPos;
                        LocationResult.locationId = location.LocationID;
                        LocationResult.typeNo = dr["TYPE_NUMBER"].ToString();
                        LocationResult.typeVar = dr["TYPE_VERSION"].ToString();
                        LocationResult.processNo = dr["PROCESS_NUMBER"].ToString();
                        LocationResult.resultState = dr["RESULT_STATE"].ToString();
                        LocationResult.timeStamp = dr["RESULT_DATE"].ToString();

                        //System.Diagnostics.Debug.Write(" -----" + (j++) + "
");
                        if (j == 24)//调试使用
                        {
                            // break;
                        }

                        if ("51".Equals(location.LineNo.Substring(0, 2)) && !"110".Equals(location.StatNo)
                        else
                            GetParameters(LocationResult);
                        info.locationResults.Add(LocationResult);
                    }
                }   
            }
            catch (Exception error)
            {
                Console.Write(error);
                conn1.Close();
            }
            finally
            {
                conn1.Close();
            }

  

三、总结

1、使用如下代码查出总数据量,sb.ToString()里面是原来的查询sql,拼接而成的比较长,这里就不贴了。

string countsql = "SELECT COUNT(1) TOTAL FROM (" + sb.ToString() + ")A";
                OracleCommand comm2 = new OracleCommand(countsql, conn1);

                OracleDataReader dr2 = comm2.ExecuteReader();//直接查询 dr.Read()到25次的时候会消耗100秒左右的时间,所以此处分页处理,每次只向数据库查询20记录,跳过25这个bug
                if (dr2.Read())
                {
                    count = dr2["TOTAL"].ToString();
                }
                var countInt = Convert.ToInt32(count);

2、使用下面的方式进行分页查询,每次只查20条数据,并用dr.Read()读取出来。成功避免了第25次循环消耗时间过长的问题。

var page = 20;
                for(int i = 0; i < countInt / page + 1; i++)
                {
                    OracleCommand comm1 = new OracleCommand("SELECT * FROM ( SELECT TMP_PAGE.*, ROWNUM ROW_ID FROM ("+sb.ToString()+ ")TMP_PAGE  WHERE ROWNUM <= "+((i+1)* page) +") WHERE ROW_ID >"+(i* page), conn1);
                    OracleDataReader dr = comm1.ExecuteReader();
                    //System.Diagnostics.Debug.Write("SELECT * FROM ( SELECT TMP_PAGE.*, ROWNUM ROW_ID FROM (" + sb.ToString() + ")TMP_PAGE  WHERE ROWNUM <= " + ((i + 1) * page) + ") WHERE ROW_ID >" + (i * page));
                    var j = 0;
                    while (dr.Read())
                    {
                        //TODO
                    info.locationResults.Add(LocationResult);//每次查询到的东西添加到List里面
                    }        

        }        

3、本次问题其实并没有根本解决,只是通过取巧的方式暂时避免了问题。假如这个问题是每次查询第一个结果就会消耗大量的时间,那么这个解决方案就失效了。最终的解决方案应该是,阅读OracleDataReader.Read()的源代码,找出根本问题,并重写OracleDataReader.Read(),从而根本解决问题。后面再看看吧。

---------------------------------------------------------------------------------------------------------------------------

2020年10月29日更新

程序运行了两天,问题又出现了。仔细阅读了微软官方的文档,发现之前离职的同事写的代码有问题,没有写下面标注的部分。将项目中相关的OracleDataReader 代码使用完成后都Close()掉,去除本篇文章前面的分页,问题消失。再观察观察。

---------------------------------------------------------------------------------------------------------------------------

2020年10月30日更新

问题又出现了,我日。都不知道是什么原因。今天再找找原因,实在不行的话我就用java写个接口,通过java来查了.

OracleDataReader 一直出问题,我今天换成了DataTable的形式承载数据,具体实现如下,需要观察几天看看。

               DataTable dt = new DataTable();
                int all = new OracleDataAdapter(sb.ToString(), conn).Fill(dt);
                foreach (DataRow row in dt.Rows)
                {
                       //TODO
                }        

---------------------------------------------------------------------------------------------------------------------------

2020年11月9日更新

问题依旧。今天试了下查询的SQL 使用Oracle的强制索引。更新程序之后发现确实变快了一点。

猜测原因是,自己在PLSQL中,或者使用java接口查询数据库的时候,可能凑巧所有的schema都走索引,所以就比较快,程序自己运行的时候,有些时候查询的50个schema中有几个是不走索引的,所以程序偶尔会出现卡很久,基本上要卡60s左右,但是使用强制索引之后,就统一都走索引了。只能说再观察观察吧。下图是强制索引之后,Explain Plan显示的过程。

 

本文链接:https://www.cnblogs.com/CryOnMyShoulder/p/13889350.html

原文地址:https://www.cnblogs.com/CryOnMyShoulder/p/13889350.html