ORACLE的Dead Connection Detection浅析

简介: 在复杂的应用环境下,我们经常会遇到一些非常复杂并且有意思的问题,例如,我们会遇到网络异常(网络掉包、无线网络断线)、客户端程序异常(例如应用程序崩溃Crash)、操作系统蓝屏、客户端电脑掉电、死机重启等异常情况,此时数据库连接可能都没有正常关闭(Colse)、事务都没有提交,连接(connections)就断开了。

    在复杂的应用环境下,我们经常会遇到一些非常复杂并且有意思的问题,例如,我们会遇到网络异常(网络掉包、无线网络断线)、客户端程序异常(例如应用程序崩溃Crash)、操作系统蓝屏、客户端电脑掉电、死机重启等异常情况,此时数据库连接可能都没有正常关闭(Colse)、事务都没有提交,连接(connections)就断开了。如果遇到这些情况,你未提交的一个事务在数据库中是否会回滚? 如果回滚,什么条件才会触发回滚?需要多久才会触发回滚(不是回滚需要多少时间)?如果是一个查询呢,那么情况又是怎么样呢?ORACLE数据库是否提供某些机制来解决这些问题呢?如果这些问题你都能回答,那么可以不用看下文了,在介绍理论知识之前,我们先通过构造测试案例,测试一下,毕竟实践出真知,抽象的理论需要实验来加深理解、全面详细阐述。

我们首先来测试一下数据库会话正常退出的情况吧,我在客户端使用(SQL*Plus)连接到数据库,执行一个UPDATE语句后不提交,然后退出(注意:实验步骤是在服务器端查询一些信息后才退出)。如下所示:

 

SQL> select * from v$mystat where rownum=1;
 
       SID STATISTIC#      VALUE
---------- ---------- ----------
       196          0          0
 
SQL> select sid,serial# from v$session where sid=196;
 
       SID    SERIAL#
---------- ----------
       196          9
 
SQL> update scott.dept set loc='CHICAGO' where deptno=40;
 
1 row updated.
 
SQL> exit    --在服务器查询一些信息后才执行该命令

 

服务器端我们查看会话(196,9)的一些相关信息,如下所示:

SQL> set linesize 1200
SQL> select sid, seconds_in_wait, event from v$session_wait where sid=196;
 
       SID SECONDS_IN_WAIT EVENT
---------- ----------- -------------------------------------------------
       196              33 SQL*Net message from client
 
SQL> SELECT B.USERNAME
  2         ,B.SID
  3         ,B.SERIAL#
  4         ,LOGON_TIME
  5         ,A.OBJECT_ID 
  6         ,A.LOCKED_MODE
  7  FROM   V$LOCKED_OBJECT A, 
  8         V$SESSION B 
  9  WHERE  A.SESSION_ID = B.SID 
 10  ORDER  BY B.LOGON_TIME;
 
USERNAME              SID    SERIAL# LOGON_TIM  OBJECT_ID   LOCKED_MODE 
----------------- ---------- ---------- --------- ----------   -----------
TEST                   196          9 01-DEC-16      73199       3

 

 

从上面可以看到196会话对表SCOTT.DEPT持有锁(Row-X 行独占(RX)),对象ID为73199,然后我们在客户端不提交UPDATE语句就执行exit命令退出会话后,然后在服务器端检查会话是否回滚。如下所示,测试结果我们可以看到,正常exit后,会话会立即回滚。(pmon进程立即回收相关进程,回收资源)

SQL> select sid, seconds_in_wait, event from v$session_wait where sid=196;
 
no rows selected
 
SQL> SELECT B.USERNAME
  2         ,B.SID
  3         ,B.SERIAL#
  4         ,LOGON_TIME
  5         ,A.OBJECT_ID 
  6  FROM   V$LOCKED_OBJECT A, 
  7         V$SESSION B 
  8  WHERE  A.SESSION_ID = B.SID 
  9  ORDER  BY B.LOGON_TIME;
 
no rows selected
 
SQL> 

 

接下来,我们来构造网络异常的案例(需要多台机器或虚拟机),如下所示,我们首先在虚拟机上使用SQL*Plus连接到服务器端(账号为test,另外服务器上sqlnet.ora 不要设置SQLNET.EXPIRE_TIME参数,不启用DCD,后面介绍至这个),然后执行一个UPATE语句不提交

SQL> show user;
USER is "TEST"
SQL> select * from v$mystat where rownum =1;
 
       SID STATISTIC#      VALUE
---------- ---------- ----------
       914          0          1
 
SQL> select sid,serial# from v$session where sid=914;
 
       SID    SERIAL#
---------- ----------
       914       3944
 
SQL> update scott.emp set sal=8000 where empno=7369;
 
1 row updated.
 
SQL> 

 

然后我们断开虚拟机的网络,构造网络异常案例(在客户端机器上执行service network stop命令断开网络,我们在服务器端使用SQL*Plus查看会话(914,3944)的情况,如下所示

SQL> select sid, seconds_in_wait, event from v$session_wait where sid=914;
 
       SID SECONDS_IN_WAIT EVENT
---------- --------------- ----------------------------------------------------------------
       914              93 SQL*Net message from client
 
SQLSELECT B.USERNAME
  2         ,B.SID
  3         ,B.SERIAL#
  4         ,LOGON_TIME
  5         ,A.OBJECT_ID 
  6  FROM   V$LOCKED_OBJECT A, 
  7         V$SESSION B 
  8  WHERE  A.SESSION_ID = B.SID 
  9  ORDER  BY B.LOGON_TIME;
 
USERNAME                              SID    SERIAL# LOGON_TIM  OBJECT_ID
------------------------------ ---------- ---------- --------- ----------
TEST                                  914       3944 01-DEC-16     782460
 
SQL> 

 

我们继续执行上面语句,你会看到看到会话914一直是INACTIVE,对表一直持有Row-X 行独占(RX),而且seconds_in_wait也一直在增长

 

SQL> select sid, seconds_in_wait, event from v$session_wait where sid=914;
 
       SID SECONDS_IN_WAIT EVENT
---------- --------------- -----------------------------------------------------
       914            4928 SQL*Net message from client
 
SQLSELECT B.USERNAME
  2         ,B.SID
  3         ,B.SERIAL#
  4         ,LOGON_TIME
  5         ,A.OBJECT_ID 
  6  FROM   V$LOCKED_OBJECT A, 
  7         V$SESSION B 
  8  WHERE  A.SESSION_ID = B.SID 
  9  ORDER  BY B.LOGON_TIME;
 
USERNAME                              SID    SERIAL# LOGON_TIM  OBJECT_ID
------------------------------ ---------- ---------- --------- ----------
TEST                                  914       3944 01-DEC-16     782460
 
SQLselect sid, seconds_in_wait, event from v$session_wait where sid=914;
 
       SID SECONDS_IN_WAIT EVENT
---------- --------------- ------------------------------------------------
       914            5853 SQL*Net message from client
 
SQL> SELECT B.USERNAME
  2         ,B.SID
  3         ,B.SERIAL#
  4         ,LOGON_TIME
  5         ,A.OBJECT_ID 
  6  FROM   V$LOCKED_OBJECT A, 
  7         V$SESSION B 
  8  WHERE  A.SESSION_ID = B.SID 
  9  ORDER  BY B.LOGON_TIME;
 
USERNAME                              SID    SERIAL# LOGON_TIM  OBJECT_ID
------------------------------ ---------- ---------- --------- ----------
TEST                                  914       3944 01-DEC-16     782460
 
SQL> 

 

最后一直等待pmon进程回收资源,经过多次测试,发现这个时间都是在7860多秒后才会被PMON进程回收资源。

 

 

那么这个是否有一个固定的值?这个值是否有规律呢?我构造了这样一个脚本在服务器端运行(根据实际情况修改sid, serial#的值),测试数据库需要耗费多久时间,PMON进程才会回收进程,释放资源,回滚事务。

CREATE TABLE TEST.SESSION_WAIT_RECORD
AS
SELECT sid, 
       seconds_in_wait, 
       event,
   sysdate as curr_datetime
FROM   v$session_wait 
       where 1=0;
        
 
CREATE TABLE TEST.LOCK_OBJECT_RECORD AS 
        SELECT B.username, 
               B.sid, 
               B.serial#, 
               logon_time, 
               A.object_id ,
               sysdate as curr_datetime
        FROM   v$locked_object A, 
               v$session B 
        WHERE  A.session_id = B.sid 
       AND 1=0;
 
 
 
 
DECLARE 
    v_index NUMBER := 1; 
BEGIN 
    WHILE v_index != 0 LOOP 
    
          INSERT INTO SESSION_WAIT_RECORD
        SELECT sid, 
               seconds_in_wait, 
               event ,
         sysdate
        FROM   v$session_wait 
        WHERE  sid = 916; 
 
                INSERT INTO LOCK_OBJECT_RECORD
        SELECT B.username, 
                   B.sid, 
               B.serial#, 
               logon_time, 
               A.object_id ,
               sysdate
        FROM   v$locked_object A, 
               v$session B 
        WHERE  A.session_id = B.sid 
                AND A.session_id=916 AND B.serial#=415
        ORDER  BY B.logon_time; 
        
        commit;
 
        dbms_lock.Sleep(10); 
 
        SELECT Count(*) 
        INTO   v_index 
        FROM   v$session_wait 
        WHERE  sid = 916; 
    END LOOP; 
END; 

 

1:多次的测试结果是否一直一致?这个值是否有什么规律?

从我的几次测试结果来看(当然没有大量测试和考虑各种场景),几次测试的结果如下(查询SESSION_WAIT_RECORD表),基本上都是在7872~7876. 由于上面SQL会休眠10秒,所以可以推断数据库会在一个固定的时间后清理断开的会话。

测试实验1

测试实验2:

 

测试实验3:

将上面脚本休眠的时间改为2秒,避免修改时间过长引起的误差,测试结果是7876

 

看似结果有点不一致,其实是因为误差,因为脚本里面休眠的时间(实验1、2的休眠时间为10秒,实验3改为2秒),以及其他方面的一些误差导致,规律就是这个跟Linux系统的TCP keepalive有关系,我们先来看看TCP keepalive概念,如下

 

The keepalive concept is very simple: when you set up a TCP connection, you associate a set of timers. Some of these timers deal with the keepalive procedure. When the keepalive timer reaches zero, you send your peer a keepalive probe packet with no data in it and the ACK flag turned on. You can do this because of the TCP/IP specifications, as a sort of duplicate ACK, and the remote endpoint will have no arguments, as TCP is a stream-oriented protocol. On the other hand, you will receive a reply from the remote host (which doesn't need to support keepalive at all, just TCP/IP), with no data and the ACK set.

 

顾名思义,TCP keepalive它是用来保存TCP连接的,注意它只适用于TCP连接。系统会替你维护一个timer,时间到了,就会向remote peer发送一个probe package,当然里面是没有数据的,对方就会返回一个应答,这时你就知道这个通道保持正常。与TCP keepalive有关的三个参数tcp_keepalive_time、tcp_keepalive_intvl、tcp_keepalive_probes

[root@myln01uat ~]# cat /proc/sys/net/ipv4/tcp_keepalive_time

7200

[root@mylnx01uat ~]# cat /proc/sys/net/ipv4/tcp_keepalive_intvl

75

[root@mylnx01uat ~]# cat /proc/sys/net/ipv4/tcp_keepalive_probes

9

[root@getlnx01uat ~]#

 

/proc/sys/net/ipv4/tcp_keepalive_time    当keepalive起用的时候,TCP发送keepalive消息的频度。默认是2小时。

/proc/sys/net/ipv4/tcp_keepalive_intvl   当探测没有确认时,keepalive探测包的发送间隔。缺省是75秒。

/proc/sys/net/ipv4/tcp_keepalive_probes  如果对方不予应答,keepalive探测包的发送次数。缺省值是9。

 

那么在Oracle没有启用DCD时,系统和数据库如何判断一个连接是否异常,需要关闭呢?这个时间是这样计算的,首先它等待了7200,然后每隔75秒发送探测包,一共发送了9次后(7200+ 75*9 = 7875 ),都没有收到客户端应答,那么它就判断这个连接死掉了,可以关闭了。所以这个值是一个固定值, 具体为7875, 当然不同的操作系统可能有所不同,取决于上面三个tcp_keepalive参数,过了7875秒后, 这个时候PMON进程就会回收与它相关的所有资源(例如回滚事务,释放lock、latch、memory)。这个值与我测试的时间非常接近了(考虑我们是采集的等待时间,以及测试脚本里面有休眠时间,这样采集的数据有些许偏差)。

 

2: 如果是一个查询操作呢?结果又是什么情况。

  如果是查询操作,结果依然是如此,有兴趣的可以自行测试。

 

3:这个是否跟专用连接服务器模式与共享连接服务器模式有关?

测试结果发现,专用连接服务器模式与共享连接服务器模式都一样。只是跟Linux的系统内核参数tcp_keepalive_time、tcp_keepalive_intvl、tcp_keepalive_probes有关系。

那么问题来了,如果会话持续持有Row-X 行独占(RX)长达7875秒,那么很有可能导致系统出现一些性能问题,重要的系统里面这个是不可接受的,好了,现在回到我们讨论的正题,ORACLE是怎么处理这些问题的?它应该有一套机制来解决这个问题,否则它也太弱了。 其实ORACLE提供了DCD(Dead Connection Detection 即死连接检测)机制来解决这个问题,下面来介绍这个:

 

Dead Connection Detection概念

DCD是Dead Connection Detection的缩写,用于检查那些死掉但没有断开的session。它的具体原理如下所示:

当一个新的数据库连接建立后,SQL*Net读取参数文件的SQLNET.EXPIRE_TIME设置(如果设置了的话),在服务端初始化DCD,DCD会为这个连接创建一个定时器,当该定时器超过SQLNET.EXPIRE_TIME指定时间间隔后,就会向客户端发送一个probe package(侦测包),该包实质上是一个空的SQL*NET包,不包括任何有用数据,它仅在底层协议上创建了数据流。如果此时客户端连接还是正常的话,那么这个probe package就会被客户端直接丢弃,然后Oracle服务器就会把该连接对应的定时器重新复位。如果客户异常退出的话,侦测包由客户端的IP层交到TCP层时,就会发现原先的连接已经不存在了,然后TCP层就会返回错误信息,该信息被ORACLE服务端接收到后,ORACLE就会知道该连接已经不可用了,于是SQL*NET就会向操作系统发送消息,释放该连接的相关资源。

官方文档关于Dead Connection Detection的介绍请参考文档“Dead Connection Detection (DCD) Explained (文档 ID 151972.1)”,摘抄部分如下所示

 

如何开启/启用DCD

 

开启DCD(Dead Connection Detection)非常简单,只需要在服务器端的sqlnet.ora里面设置SQLNET.EXPIRE_TIME即可,当然客户端也需要支持SQL*Net V2以及后面版本。如何检查、确认是否开启了DCD,官方文档有详细介绍:Note.395505.1 How to Check if Dead Connection Detection (DCD) is Enabled in 9i and 10g。 此处不做展开。

 

DCD的问题与异常

 

DCD在一些版本和平台还是有蛮多Bug的,你在Oracle Metalink上搜索一下,都能查到很多,另外我在测试过程中,设置SQLNET.EXPIRE_TIME=5,测试发现,清理这些Dead Connection的时间不是5分钟,而是20多分钟,

搜索了大量资料,也没有完全彻底弄清楚这个问题,只是知道这个跟TCP/IP有超时重传机制有关系,网络知识是我的薄弱项啊(尝试了多次无果后,只能放弃),当然,数据库回收Dead Connection也不会完全跟SQLNET.EXPIRE_TIME指定的时间一致的(例如,下面官方文档就明确指出not at the exact time of the DCD value)。 另外这个值还有可能被防火墙影响,可以参考防火墙、DCD与TCP Keep alive这篇文章。

 

 

DCD的好处与弊端

 

其实,DCD的好处上面已经基本阐述清楚了,其实DCD还是有一些弊端的。例如,在Windows平台性能很差(bug#303578);在SCO Unix下它会触发Bug,消耗大量CPU资源; DCD 在协议层是很消耗资源的, 所以如果要用DCD来清除死进程, 会加重系统的负担, 任何时候, 干净的退出系统,这是首要的. 如下英文所述:

DCD is much more resource-intensive than similar mechanisms at the protocol level, so if you depend on DCD to clean up all dead processes, that will put

an undue load on the server. Clearly it is advantageous to exit applications cleanly in the first place.

 

参考资料:

http://www.laoxiong.net/firewall-dcd-and-tcp-keep-alive.html

Note.601605.1 A discussion of Dead Connection Detection, Resource Limits, V$SESSION, V$PROCESS and OS processes:
Note.395505.1 How to Check if Dead Connection Detection (DCD) is Enabled in 9i and 10g:
Connections on Windows Platform Timout after 2 Hours, Why ? (文档 ID 1073461.1)
Concurrent Manager Functionality Not Working And PCP Failover Takes Long Inspite of Enabling DCD With Database Server (文档 ID 438921.1)
Common Questions About Dead Connection Detection (DCD) (文档 ID 1018160.6)
Dead Connection Detection (DCD) Explained (文档 ID 151972.1)

相关文章
|
3月前
|
Oracle 关系型数据库
Navicat 连接Oracle ORA-28547: connection to server failed, probable Oracle Net admin error
Navicat 连接Oracle ORA-28547: connection to server failed, probable Oracle Net admin error
100 0
|
SQL Oracle 关系型数据库
解决Oracle的状态: 失败 -测试失败: IO 错误: The Network Adapter could not establish the connection
解决Oracle的状态: 失败 -测试失败: IO 错误: The Network Adapter could not establish the connection
2049 0
解决Oracle的状态: 失败 -测试失败: IO 错误: The Network Adapter could not establish the connection
|
SQL Oracle 网络协议
SQL Developer 连接 oracle数据库 报错 Io 异常 The Network Adapter could not establish the connection的三种解决方法
SQL Developer 连接 oracle数据库 报错 Io 异常 The Network Adapter could not establish the connection的三种解决方法
1309 0
SQL Developer 连接 oracle数据库 报错 Io 异常 The Network Adapter could not establish the connection的三种解决方法
|
SQL Oracle 关系型数据库
Oracle中Error while performing database login with the XXXdriver; Listener refused the connection wit...
Oracle中Error while performing database login with the XXXdriver; Listener refused the connection wit...
253 0
|
Oracle 关系型数据库 数据库
Oracle中Error while performing database login with the XXXdriver; Listener refused the connection with the following error; ORA-12505,TNS:listener does
一次连接数据库怎么也连接不上,查了多方面资料,终于找到答案,总结 首先应该保证数据库的服务启动 在myeclipse的数据库视图中点 右键->new 弹出database driver的窗口, Driver template选择oracle(thin driver), Driver name 输入...
1407 0
|
人工智能 Oracle 关系型数据库
Oracle中Error while performing database login with the XXXdriver; Listener refused the connection with the following error; ORA-12505,TNS:listener does
欢迎关注大数据和人工智能技术文章发布的微信公众号:清研学堂,在这里你可以学到夜白(作者笔名)精心整理的笔记,让我们每天进步一点点,让优秀成为一种习惯! 一次连接数据库怎么也连接不上,查了多方面资料,终于找到答案,总结 首先应该保证数据库的服务启动 在myeclipse的数据库视图中点 右键->ne...
1767 0
|
Oracle 关系型数据库 数据库连接