Oracle-Java JDBC 连接超时之后的认知纠正

来源:这里教程网 时间:2026-03-03 21:04:34 作者:

背景

  偶然读到熊老师的文章 《老熊的三分地-JDBC中语句超时与事务》了解到: JAVA代码的最后正常断开数据库连接,在默认情况下,正常断开的数据库连接会自动提交没有提交的事务。   通过文章的测试JAVA程序,可以表明,JDBC中的语句超时,只会使当前的SQL中止运行,但如果是在一个事务中,之前运行的DML语句并没有提交。这造成的后果有两种:

  1. 如果是连接池,那么超时之前更新的数据可能会被其他请求重用时得以提交,或者是在连接释放时得以提交,这造成数据的不一致,因为不是一个逻辑上有效的事务。
  2. 由于数据被更新而没有及时回滚,可能会导致应用重新发起相同的事务时被锁住。如果被锁住的会话仍然有超时中止的机制,那么这种情况就会越来越严重。
  3. 为了避免以上的两种问题, 在超时后,应该主动发起一次ROLLBACK操作

  今天正好有国产数据库工程师师给我们培训的时候也提到了这一点,表示: “JDBC连接ORACLE,在断开连接时会把未提交的事务进行提交,在Oracle 23ai版本进行了修复,其它数据库都是回滚事务。”

  这着(zhuo)实有点刷新我对Oracle的基础认知。这是一篇对自己认知纠正的测试文章。复现一下熊老师的测试,实践出真知。

环境准备

  • Oracle 版本
    SQL*Plus: Release 11.2.0.3.0 Production on Tue Nov 19 21:32:31 2024
  • 安装JAVA
    [root@db ~]$ mkdir /usr/local/java
    [root@db ~]$ cd /usr/local/java
    [root@db java]$ mv /root/jdk-11.0.21_linux-x64_bin.tar.gz ./
    [root@db java]$ tar -zxvf jdk-11.0.21_linux-x64_bin.tar.gz 
    [root@db java]$ cd jdk-11.0.21
    [root@db jdk-11.0.21]# pwd
    /usr/local/java/jdk-11.0.21
    [root@db jdk-11.0.21]# vi /etc/profile
    ...
    export JAVA_HOME=/usr/local/java/jdk-11.0.21
    export CLASSPATH=$JAVA_HOME/lib
    export PATH=$PATH:$JAVA_HOME/bin
    ...
    [root@db ~]# source /etc/profile
    [root@db ~]# java -version
    java version "11.0.21" 2023-10-17 LTS
    Java(TM) SE Runtime Environment 18.9 (build 11.0.21+9-LTS-193)
    Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.21+9-LTS-193, mixed mode)
  • Oracle 用户环境变量
    [oracle@db ~]$ cat .bash_profile
    ......
    export NLS_LANG=AMERICAN_AMERICA.AL32UTF8
    export JAVA_HOME=/usr/local/java/jdk-11.0.21
    export CLASSPATH=$JAVA_HOME/lib
    export PATH=$PATH:$JAVA_HOME/bin
    ......
    [oracle@db ~]$ source .bash_profile
    [oracle@db ~]$ java -version
    java version "11.0.21" 2023-10-17 LTS
    Java(TM) SE Runtime Environment 18.9 (build 11.0.21+9-LTS-193)
    Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.21+9-LTS-193, mixed mode)
  • JAVA代码:

    [oracle@db java_oracle]$ cat TestTimeout.java 
    import java.sql.*;
    public class TestTimeout {
        public static Connection getConnection() throws Exception {
            String driver = "oracle.jdbc.driver.OracleDriver";
            String url = "jdbc:oracle:thin:@10.10.5.64:1521/phytest1";
            Class.forName(driver);
            return DriverManager.getConnection(url, "two", "two");
        }
        public static void main(String args[]) {
            test1();
        }
        public static void test1() {
            Connection conn = null;
            Statement pstmt = null;
            try {
                conn = getConnection();
                conn.setAutoCommit(false);
                pstmt = conn.createStatement();
                pstmt.setQueryTimeout(60);
                System.out.println("连接成功!");
                ResultSet rs = pstmt.executeQuery("select userenv('sid') sid from dual");
                while (rs.next()) {
                    System.out.println("SID:" + rs.getString("sid"));
                }
                rs.close();
                pstmt.execute("insert into t1 values (1,userenv('sid'),'xx')");
                System.out.println("Insert t1 succeed!");
                pstmt.execute("update t2 set name='x' where id=1");
                System.out.println("Update t2 succeed!");
                conn.commit();
            } catch (Exception e) {
                e.printStackTrace();
                try {
                    System.out.println("等待......");
                    Thread.sleep(300000);
                } catch (Exception f) {
                }
            } finally {
                try {
                    System.out.println("关闭连接!");
                    pstmt.close();
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
  • 将ojdbc8_jar.zip放在项目目录, 下载:ojdbc8_jar.zip
    [oracle@db java_oracle]$ ls 
    ojdbc8.jar TestTimeout.java
  • 通过javac 编译代码生成:TestTimeout.class
    [oracle@db java_oracle]$ javac TestTimeout.java
    [oracle@db java_oracle]$ ls
    ojdbc8.jar TestTimeout.class TestTimeout.java
  • 测试执行java

    [oracle@db java_oracle]$ java -cp .:ojdbc8.jar TestTimeout
    连接成功!
    SID:387
    Insert t1 succeed!
    Update t2 succeed!
    关闭连接!
  • 查看数据

    TWO@phytest1:1489> select * from t1;
            ID SID                  NAME
    ---------- -------------------- ----------------
             1 387                  xx

  • 配置sqlplus 显示:  用户名@实例名:SID
    [oracle@db ~]$ cat /u01/oracle/11.2.0.3/product/sqlplus/admin/glogin.sql
    SET TERMOUT OFF
    DEFINE sqlprompt=none
    COLUMN sqlprompt NEW_VALUE sqlprompt
    SELECT USER ||'@'|| NVL('&_CONNECT_IDENTIFIER', global_name )||':'|| userenv('sid') sqlprompt FROM global_name;
    SET SQLPROMPT '&sqlprompt> '
    UNDEFINE sqlprompt
    SET TERMOUT ON
    col NAME format a50
    col VALUE format a50
    set lin 250
    set pagesize 500
    [oracle@db ~]$ sqlplus / as sysdba
    SYS@phytest1:387>

    复现:熊老师文章的操作流程

  • SID:1489 表准备:T1、T2:
    TWO@phytest1:1489> create table t1 ( id number primary key,sid varchar2(20),name varchar2(20));
    Table created.
    TWO@phytest1:1489> create table t2 ( id number primary key,sid varchar2(20),name varchar2(20));
    Table created.
    TWO@phytest1:1489> insert into t2 values (1,userenv('sid'),'a');
    1 row created.
    TWO@phytest1:1489> commit;
    Commit complete.
    TWO@phytest1:1489> select * from t2;
           ID SID                  NAME
    ---------- -------------------- --------------
            1 1489                 a
  • SID:1489 将T2表中ID=1的记录进行UPDATE操作,但是不提交。
    TWO@phytest1:1489>  update t2 set name='y' where id=1; 
    1 row updated. 
    TWO@phytest1:1489>
  • 执行java 程序
    [oracle@db java_oracle]$ java -cp .:ojdbc8.jar TestTimeout 
    连接成功! 
    SID:659 
    Insert t1 succeed!
  • sid:74 执行update表T2时会被锁住
    SYS@phytest1:74> set line 800
    SYS@phytest1:74> col EVENT for a50
    SYS@phytest1:74> select sid,event,sql_id from v$session where sid=659;
    SID EVENT SQL_ID
    ---------- ------------------------------------- ----------
    659 enq: TX - row lock contention 3b3b7s22dv13t
  • 查看锁信息
    col blocker for a10
    SELECT (SELECT username
    FROM v$session
    WHERE SID = a.SID) blocker, a.SID, 'is blocking',
    (SELECT username
    FROM v$session
    WHERE SID = b.SID) blockee, b.SID
    FROM v$lock a, v$lock b
    WHERE a.BLOCK = 1 AND b.request > 0 AND a.id1 = b.id1 AND a.id2 = b.id2;
    BLOCKER SID 'ISBLOCKING BLOCKEE SID
    ---------- ---------- ----------- ------------------------------ ----------
    TWO 659 is blocking TWO 277
  • 操作截图 image.png
  • 查看事务的状态
    select start_time,xidusn,xidslot,xidsqn,status from v$transaction where ses_addr=(select saddr from v$session where sid=387);
    select * from v$lock where sid=387;
    -- 取TM锁对象id(v$lock.ID1)
    select owner,object_name,object_type from dba_objects where object_id in (256012);操作截图
  • 操作截图 image.png

  • 如熊老师文章所示,超时关闭连接后insert 插入成功 image.png

    验证下Oracle与Mysql 退出

    Oracle 退出小测试,结果 已提交

       image.png

    Mysql 退出小测试,结果 已回滚

       image.png

    总结

  • 1、通过复现熊老师的测试,确认JDBC在会话超时后会把未提交的数据进行提交处理;
  • 2、通过验证Oracle与Mysql 的退出,Oracle 确实在关闭自动提交后,退出仍然会提交;
  • 3、程序在做异常处理时一定要加rollback;
  • 4、重要的事情说三遍:细节、细节、细节,不能忽略,不能有惯性思维;

    欢迎赞赏支持或留言指正 image.png

  • 相关推荐