困扰了半年之久的Oracle简单查询导致一定概率一直不会返回

hpygzhx520 2018-09-16 10:57:38
因为问题“太简单”,反而有必要啰嗦一下,交代一下事情的原委:
1、一程序中启用了3个线程(线程1、2、3),每个线程连接到一个数据库,需要一直轮询,检测到有需要处理的数据就处理一下。
2、每次启动该程序,有一定概率出现线程1执行到一个固定查询的时候一直不会返回,导致该进程内后续代码不会执行。

写了一个类,提供一个函数用以根据一个SQL返回一个表:
public DataTable GetTable(OracleConnection conn,string SQL,string tableName)
{
try
{
if (conn.State == ConnectionState.Closed)
{
conn.Open();
}
}
catch
{
return null;//数据库打开出错,跳出
}
OracleDataAdapter myAdapter;
DataSet myData = new DataSet();
try
{
myAdapter = new OracleDataAdapter(SQL, conn);
myAdapter.SelectCommand.CommandTimeout = 10;//10秒的超时,假设不加这行,就永远永远也不会往下执行
myAdapter.Fill(myData, tableName);
return myData.Tables[tableName];
}
catch (Exception ex) //出错了
{

}
finally
{

}
}
这个代码的优劣不是本次请教的重点,因为很多地方都使用了这个函数,至少是能得到结果的。
出现这个问题,我一直在思考,问题可能出现在两点:
1、查询语句有问题,写得不好,不合理。
2、查询的表有问题
查询语句如下:
select distinct t1.big_bar_cd from v_largesmallcode t1 where t1.small_bar_code in (select t.barcode from t_commoninoutth t where t.status='0')
这个语句看起来有点别扭,但是放到PLSQL里面去执行是一点问题也没有的,基本不要时间。括号里面的部分,t_commoninoutth是本地数据库A里面的表,括号里面的部分大部分时候是返回0行,查询不会返回的时候也不是因为这个表数据多。
但是v_largesmallcode是一个很大的视图,这个视图是在数据库A中通过oracle dblink连接到数据库B的视图。

背景交代完毕,我困惑有几点:
1、极端情况下即使数据过大、锁表等等一切因素,那也应该会报超时,或者说,锁表因素解除后,总会返回的吧,但事实上是永远也不会返回。myAdapter.SelectCommand.CommandTimeout = 10这句完全是无奈之举。
2、极端情况下,到数据库B的dblink断了,那也总有恢复的一天,也不应该是永远也不会返回。
3、概率,启动程序,发现线程不会继续了,也就是查询不会返回了,关掉,再开,可能就正常了。


这个问题困扰了很久,如鲠在喉,只能靠设置超时来勉强用着。
...全文
1408 22 打赏 收藏 转发到动态 举报
写回复
用AI写文章
22 条回复
切换为时间正序
请发表友善的回复…
发表回复
KHKWB 2019-03-08
  • 打赏
  • 举报
回复
这个是不是应用到数据库之间有防火墙,试一下使用标准写法的url去获取链接。刚遇到同样的问题,已经解决。
  • 打赏
  • 举报
回复
我时长在表名后面加个WITH(NOLOCK),防止锁死
hpygzhx520 2018-09-17
  • 打赏
  • 举报
回复
感谢楼上各位的建议。
我连接字符串没有写Connection Timeout超时部分,我没有查到不写的话默认是多少。根据以往的经验,是30秒。
轮询是不得已的,在我的场景,其他多个系统对某个表的某个字段做标记,我检测到这个标记再去处理。
但我同意10楼的分析,设置超时是有用的,据此推断出就是超时。
接下来说数据库负载,应该不是这个原因。理由如下:
代码大致是这样的:
while (true)
//此处输出日志,加上时间戳
DataTable datatable=GetTable(conn,"tmp");//已经加了myAdapter.SelectCommand.CommandTimeout = 10
//此处再次输出日志,加上时间戳,与上面的日志比较
sleep(5000)
loop

启动程序,正常时两个日志时差在1秒内,不正常时就是10秒(因为设置了超时)。线程是循环执行的,我一次执行遇到数据库B负载过大,超时了,我可以理解,但当不正常的时候,每次都是10秒,1天后还是10秒,这就解释不了数据库负载过大,资源耗尽的情况。

那剩下只有一个怀疑对象了,就是oracle dblink出问题,导致查询v_largesmallcode失败。但是线程内后续部分多处用到这个视图,也没出现这样的问题。

各位的解答给了我一些启发。感谢。
闭包客 2018-09-17
  • 打赏
  • 举报
回复
数据库完全有可能永远不返回的,直至超时。
  • 打赏
  • 举报
回复
不会返回等待返回 ✗
不会返回就记录后重新排队等待下次执行返回了再继续 ✓
xiaoxiangqing 2018-09-17
  • 打赏
  • 举报
回复
超时做一个错误日志
  • 打赏
  • 举报
回复
至于说“永远也不会返回”,#10楼已经说了,一旦一个人遇到超时就去修改数据库系统、客户端DAL驱动的 timeout 时间,甚至甚至为可能的最大值,实际上这是自欺欺人的举动,这不但并不会解决问题,而且会拖延问题的解决。胡乱设置很大的 timeout 值本身就是一个 bug,你可以看看你的数据库连接串中有没有设置过大的值,等等。总之是基本的编程和测试理念还是有本质的差别。
  • 打赏
  • 举报
回复
比如说有点 bug 造成死锁的概率是千分之一,那么假设有3个终端,每一个在收到了通知准确地知道了自己该搜索哪些条件的数据之后,才去操作,这时候死锁的概率就不到千万分之一甚至千万分之一了,因为多用碰在一起的时机很少了,并且恰巧查询和操作数据具有完全相同的条件的概率更少了(万分之一)。但是假设一个人工作了15年,还觉得跟初学者一样粗放一些频繁一些反正更简单,那么它把千分之一概率的死锁,按照条件让多用户同时发生“故意放到一起”执行,他实际上就让这个 bug 出现的概率提高了几十倍。

所以既然你补贴源代码,那么看设计思路看知识结构,就知道倾向于哪种 bug。而且“必然”有这些 bug 长在你的代码中。
  • 打赏
  • 举报
回复
什么叫做死锁呢?比如说5个人争抢厕所的一个坑位,假设这些人都用不厌其烦地独占的思路,那么只要其中但凡有1、2个人除了低级的轮询之外,还有逻辑矛盾操作(例如查询之后在同一个事务中修改),那么他就会加重造成别人跟他一起死锁。所以问题在于设计者的根本意识、设计层面就是倾向于锁死大家的设计思路,设计者平常并不懂按照高标准来设计并发多用户和消息通知系统。

换句话说,死锁是人为的,并且经常发生“巧合碰上死锁”的事情必定是设计上有原因的。不是说你认为这5个人每隔几小时才上一次厕所、于是就认为他们不会死锁。具体的原因自然是跟你的源代码有关,但是看一个人的设计思路大致就能判断出会有哪些类型 的bug 是平常长在你的意识中的。
  • 打赏
  • 举报
回复
死锁有各种可能性,当然需要高并发测试(并且反复几万次测试)才能发现。但是出现死锁的情况,那就应该尽快抛出超时异常,而不是等很长时间。一个查询通常应该确保只用几十、几百毫秒就能返回,如果一个查询用时超过几秒钟,这本身就应该立刻作为一个bug来处理,也应该立刻停下手头的开发工作来解决bug。而许多人则选择无视bug,自欺欺人地去让程序带病工作。实际上先不说技术,先考虑架构设计和软件工程理念问题,先把那种只会轮询并且认为占用数据库系统资源的做法“无所谓”的观念去除,你就会从架构上重新设计(例如信息主动通知,而不是轮询)。否则遇到这类性能问题,我只能祝愿这次走运能“混”过去。
大然然 2018-09-17
  • 打赏
  • 举报
回复
用触发器,数据库有insert update操作的时候意味着数据变化,这个时候trigger生成日志,你扫描这些日志即可,不用时时刻刻扫描数据库
大然然 2018-09-17
  • 打赏
  • 举报
回复
sqlserver的话,可以用 SqlTriggerContext 完成监听数据库变化发送通知的功能,不知道Oracle有没有类似的功能
圣殿骑士18 2018-09-17
  • 打赏
  • 举报
回复
引用 3 楼 daixf_csdn 的回复:
[quote=引用 2 楼 hpygzhx520 的回复:]
一般默认是30秒,当连接失败后会报超时故障。

。。。。不要说什么 一般 默认。既然是特殊情况,说不定谁改了默认配置呢?去查一下不行吗[/quote]

既然你说你设置了超时时间就可以用了,那当然明显就是因为数据库超时导致的问题。所以这可以推导出数据库默认超时时间是被人为设置的非常大的原因。比如因为系统中一个复杂查询需要30分钟以上才能返回,所以设置了1小时等等。
让你查数据库查询超时配置,你又无视。那就没其他建议了。
问题其实很明显,就是数据库频繁查询导致数据库负载异常,导致数据一直没等到返回,但又没有超时,所以你以为怎么了。

圣殿骑士18 2018-09-16
  • 打赏
  • 举报
回复
1、看看数据库配置的超时连接值是多少。
2、看看oracle 的连接驱动dll提供的默认超时时间是多少。
hpygzhx520 2018-09-16
  • 打赏
  • 举报
回复
如果是资源问题,网络问题,线程1内要处理几十个查询,为什么偏偏卡在这一个查询上呢?目前加CommandTimeout = 10后不影响使用了,但从技术的角度看,一直耿耿于怀。
hpygzhx520 2018-09-16
  • 打赏
  • 举报
回复
上述只是演示,实际使用中,线程内部有延时的,不会一刻不停的去读取。在局域网,几秒钟做一次查询不算多大的负载。
  • 打赏
  • 举报
回复
你先考虑别去拼命消耗数据库系统的资源吧。
  • 打赏
  • 举报
回复
系统搞死了,数据库那边的其它用户端进程也会频繁死锁。

摊上这类卡死多用户进程的产品,通常来说,用户很可怜。
hpygzhx520 2018-09-16
  • 打赏
  • 举报
回复
还是怪我没有描述清楚
while (true)
DataTable datatable=GetTable(conn,"tmp");
string a="123";//有一定概率不会执行到这里
loop

不管报什么错,如超时,最坏的结果是返回的datatable是null而已,不应该无法执行到string a="123";
xuzuning 2018-09-16
  • 打赏
  • 举报
回复
如果是因为超时,那么应该调大 SelectCommand.CommandTimeout 的值,默认是 30
你的 catch 分支并无代码,如果真是查询出现了问题,你也并不能知道

既然是 括号里面的部分大部分时候是返回0行
那么没有数据返回是很正常的情况
加载更多回复(2)

110,535

社区成员

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

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

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