Oracle 11g DB-Link 即使在重新创建数据库后连接仍保持打开状态 link

Oracle 11g DB-Link connection stays open even after re-creating database link

我遇到 Oracle DB-Link 连接问题 (Oracle 11g)。让我们考虑以下情况:

  1. 我们与用户 USER1DATABASE_A 连接到数据库,
  2. 我们创建了新的私有数据库 link 到 DATABASE_B,连接用户名:USER2
CREATE DATABASE LINK "CHECK_CONNECTION"
CONNECT TO USER2
IDENTIFIED BY "password1"
USING 'DATABASE_B';
  1. 测试连接失败 - 密码或用户名不正确
SELECT * FROM DUAL@CHECK_CONNECTION

Error at line 1
ORA-01017: invalid username/password; logon denied
  1. 我们正在更改密码:
DROP DATABASE LINK CHECK_CONNECTION

CREATE DATABASE LINK "CHECK_CONNECTION"
CONNECT TO USER2
IDENTIFIED BY "password2"
USING 'DATABASE_B';
  1. 连接测试成功
SELECT * FROM DUAL@CHECK_CONNECTION

DUMMY
-----
X   
1 row selected.
  1. 我们再次更改密码,改成旧密码:
DROP DATABASE LINK CHECK_CONNECTION

CREATE DATABASE LINK "CHECK_CONNECTION"
CONNECT TO USER2
IDENTIFIED BY "password1"
USING 'DATABASE_B';
  1. 尽管密码错误,连接仍然正确:
SELECT * FROM DUAL@CHECK_CONNECTION

DUMMY
-----
X   
1 row selected.
  1. 仅使用更改后的名称创建新的 DB-Link 检测到不正确的连接。
CREATE DATABASE LINK "CHECK_CONNECTION_2"
CONNECT TO USER2
IDENTIFIED BY "password1"
USING 'DATABASE_B';

SELECT * FROM DUAL@CHECK_CONNECTION_2

Error at line 1
ORA-01017: invalid username/password; logon denied

知道为什么密码错误但连接正确吗?

来自命令手册 alter session close database link:

When you issue a statement that uses a database link, Oracle Database creates a session for you on the remote database using that link. The connection remains open until you end your local session...

Oracle 不会在每次使用数据库 link 时都重新连接,这很有用。但是,即使数据库 link 已更改,保持连接仍然存在似乎是一个小错误。我已经证实这在 12c 中仍然会发生。

这应该没什么大不了的,因为数据库 links 应该保持相当静态。就像应用程序不应为每个查询重新连接到数据库的方式一样,数据库会话不应频繁更改 links。

数据库 link 发生了很多奇怪的事情。使您的远程过程尽可能简单。

您需要检查通话计划。有可能第一次是从数据库中取值,第二次调用时是从缓存中读取。举个小例子:

SQL> set autotrace traceonly;
SQL> select * from dual;


Execution Plan
----------------------------------------------------------
Plan hash value: 272002086

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |     2 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS FULL| DUAL |     1 |     2 |     2   (0)| 00:00:01 |
--------------------------------------------------------------------------


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
          3  consistent gets
          2  physical reads
          0  redo size
        522  bytes sent via SQL*Net to client
        523  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL> select * from dual;


Execution Plan
----------------------------------------------------------
Plan hash value: 272002086

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |     2 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS FULL| DUAL |     1 |     2 |     2   (0)| 00:00:01 |
--------------------------------------------------------------------------


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          3  consistent gets
          0  physical reads
          0  redo size
        522  bytes sent via SQL*Net to client
        523  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL>

从跟踪中可以明显看出,在第一次调用期间有 2 次物理读取。但是当再次调用同样的语句时,物理读取为0,这意味着结果是从缓存中读取的。

在你的例子中,虽然 db link 的定义发生了变化,但基本的 sql 保持不变,因此发生了缓存命中。

@乔恩海勒

您的回答很有帮助 - 改变会话有效,如果将在步骤 6 中使用,在删除和创建适当的数据库之间 link。如果在删除不正确的数据库后使用它将不起作用 link - 仅仅是因为未创建连接。

不管你怎么暗示我,也许我解决问题的想法是不正确的。

It shouldn't be a big deal because database links should remain fairly static. Just like the way an application should not re-connect to the database for each query, database sessions should not be changing links frequently.

我想从级别 USER1 验证与其他数据库的连接,使用 PL/SQL 程序。

CREATE OR REPLACE PROCEDURE USER1.CHECK_CONNECTION_PROC (
   P_ID         IN     NUMBER,
   P_USERNAME   IN     VARCHAR2,
   P_PASSWORD   IN     VARCHAR2,
   P_DATABASE   IN     VARCHAR2,
   P_RESULT        OUT VARCHAR2)
IS
   M_DBLINK_NAME   VARCHAR2 (100) DEFAULT 'CHECK_CONNECTION';
   M_IS_EXISTS     NUMBER;
BEGIN
   EXECUTE IMMEDIATE
         'CREATE DATABASE LINK "'
      || M_DBLINK_NAME
      || '" CONNECT TO '
      || P_USERNAME
      || ' IDENTIFIED BY "'
      || P_PASSWORD
      || '" USING '''
      || P_DATABASE
      || '''';

   EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM DUAL@' || M_DBLINK_NAME;

   P_RESULT := 'PASSED';

   EXECUTE IMMEDIATE 'DROP DATABASE LINK ' || M_DBLINK_NAME;
EXCEPTION
   WHEN LOGIN_DENIED
   THEN
      P_RESULT := 'LOGIN_DENIED';

      EXECUTE IMMEDIATE 'DROP DATABASE LINK ' || M_DBLINK_NAME;
   WHEN OTHERS
   THEN
      P_RESULT := SUBSTR (SQLERRM, 1, 200);

      SELECT SIGN (COUNT (*))
        INTO M_IS_EXISTS
        FROM ALL_DB_LINKS
       WHERE DB_LINK = M_DBLINK_NAME;

      IF M_IS_EXISTS = 1
      THEN
         EXECUTE IMMEDIATE 'DROP DATABASE LINK ' || M_DBLINK_NAME;
      END IF;
END CHECK_CONNECTION_PROC;

DECLARE
   M_RESULT   VARCHAR2 (4000);
BEGIN
   EXECUTE IMMEDIATE 'TRUNCATE TABLE TEMPORARY_TABLE';

   INSERT ALL
     INTO TEMPORARY_TABLE (NUMERIC_VALUE1,
                           VARCHAR_VALUE1,
                           VARCHAR_VALUE2,
                           VARCHAR_VALUE3)
   VALUES (1,
           'USER2',
           'password2',
           'DATABASE_B')
     INTO TEMPORARY_TABLE (NUMERIC_VALUE1,
                           VARCHAR_VALUE1,
                           VARCHAR_VALUE2,
                           VARCHAR_VALUE3)
   VALUES (2,
           'USER3',
           'password3',
           'DATABASE_C')
     INTO TEMPORARY_TABLE (NUMERIC_VALUE1,
                           VARCHAR_VALUE1,
                           VARCHAR_VALUE2,
                           VARCHAR_VALUE3)
   VALUES (3,
           'USER4',
           'password4',
           'DATABASE_D')
      SELECT * FROM DUAL;

   FOR CNT IN (  SELECT TMP.NUMERIC_VALUE1 ID,
                        TMP.VARCHAR_VALUE1 USERNAME,
                        TMP.VARCHAR_VALUE2 PASSWORD,
                        TMP.VARCHAR_VALUE3 DATABASE
                   FROM TEMPORARY_TABLE TMP
               ORDER BY 1)
   LOOP
      CHECK_CONNECTION_PROC (CNT.ID,
                             CNT.USERNAME,
                             CNT.PASSWORD,
                             CNT.DATABASE,
                             M_RESULT);

      UPDATE TEMPORARY_TABLE
         SET VARCHAR_VALUE4 = M_RESULT
       WHERE NUMERIC_VALUE1 = CNT.ID;
   END LOOP;
END;

SELECT TMP.NUMERIC_VALUE1 ID,
       TMP.VARCHAR_VALUE1 USERNAME,
       TMP.VARCHAR_VALUE2 PASSWORD,
       TMP.VARCHAR_VALUE3 DATABASE,
       TMP.VARCHAR_VALUE4 RESULT
  FROM TEMPORARY_TABLE TMP;

ID  USERNAME    PASSWORD     DATABASE     RESULT    
---------------------------------------------------
1   USER2       password2    DATABASE_B   PASSED
2   USER3       password3    DATABASE_C   PASSED
3   USER4       password4    DATABASE_D   PASSED

3 rows selected.

至于现在我没有找到其他方法来验证用户名和密码的正确性到其他数据库,除了创建临时 DB-Link.