Liberando recursos de conexión dblink
OracleDB DBlink es una funcionalidad que permite establecer una conexión con otra instancia de base de datos.
create public database link remote
connect to MY_USER identified by MY_PASSWORD
using '(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(service_name=ORCLPDB1)))';
select * from dual@remote;
Una característica interesante de esta conexión es el bloqueo transaccional implícito sobre los segmentos undo que comienza con una simple consulta SELECT sobre una tabla dblink. Esta funcionalidad se describe en la guía de administración para la versión 11.2. Desde el punto de vista de la aplicación, esto es relevante para la liberación de recursos, especialmente cuando el acceso dblink puede estar oculto bajo una vista o procedimiento.

Pool de conexiones
Una aplicación típica suele usar algún tipo de pool de conexiones. Según la tecnología y el nivel de abstracción, el uso del pool puede:
- estar oculto y gestionado por el servidor/contenedor (JPA/JTA);
- ocurrir a petición del usuario (acceso directo a DataSource), siendo su responsabilidad liberar los recursos de la sesión.
Cuando la aplicación ya no necesita la conexión (fin declarativo de la transacción/cierre de conexión), esta vuelve al pool. Luego puede reutilizarse sin cerrarse físicamente.
JDBC y dblink
La naturaleza transaccional de dblink debe ponerte en alerta. Necesitarás un commit o rollback para liberar el bloqueo de la transacción. Puede resultar extraño en situaciones como:
- usar dblink en una conexión en modo auto-commit no permite invocar commit/rollback sin cambiar el modo (especialmente problemático con JTA no transaccional);
java.sql.SQLException: Could not rollback with auto-commit set on at oracle.jdbc.driver.PhysicalConnection.rollback(PhysicalConnection.java:2427)
- usar dblink en una conexión en modo solo lectura, es decir,
SET TRANSACTION READ ONLY;
– el bloqueo ocurre igual.
Por ejemplo, al liberar una conexión sin liberar los recursos de dblink, cambiar el nivel de transacción en una "nueva" conexión puede fallar:
java.sql.SQLException: ORA-01453: SET TRANSACTION must be first statement of transaction
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:450)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:399)
at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:1059)
at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:522)
at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:257)
at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:587)
at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:225)
at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:53)
at oracle.jdbc.driver.T4CPreparedStatement.executeForRows(T4CPreparedStatement.java:943)
at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:4798)
at oracle.jdbc.driver.OraclePreparedStatement.execute(OraclePreparedStatement.java:4901)
at oracle.jdbc.driver.OraclePreparedStatementWrapper.execute(OraclePreparedStatementWrapper.java:1385)
En la práctica, se verá así:
-- app conn 1 (physical conn 1)
SET TRANSACTION READ ONLY;
select * from dual@remote;
-- app conn 1 closed (physical conn 1)
-- app conn 2 (physical conn 1)
SET TRANSACTION READ ONLY; -- error
Verificación/depuración
Para comprobar si el problema afecta a tu aplicación, puedes verificar las transacciones abiertas en la base de datos. Necesitarás permisos SELECT sobre las siguientes vistas de sistema:
GRANT SELECT ON V_$TRANSACTION to MY_USER; -- transacciones activas
GRANT SELECT ON V_$SESSION to MY_USER; -- sesiones
GRANT SELECT ON V_$SQL to MY_USER; -- últimas consultas en las sesiones
GRANT SELECT ON V_$PROCESS to MY_USER; -- asociación sesión-proceso
GRANT SELECT ON V_$DBLINK to MY_USER; -- conexiones dblink y estado de transacción, solo para la sesión actual
Con los permisos, ejecuta la consulta:
SELECT session_.SID
, session_.SERIAL#
, session_.USERNAME
, session_.OSUSER
, session_.PROGRAM
, session_.EVENT
, TO_CHAR(session_.LOGON_TIME,
'YYYY-MM-DD HH24:MI:SS') as LOGON_TIME
, TO_CHAR(transaction_.START_DATE,
'YYYY-MM-DD HH24:MI:SS') as START_DATE
, session_.LAST_CALL_ET
, session_.BLOCKING_SESSION
, session_.STATUS
, (SELECT query_.SQL_TEXT
FROM V$SQL query_
WHERE query_.SQL_ID = session_.PREV_SQL_ID
AND ROWNUM <= 1) AS PREV_SQL
, (SELECT query_.SQL_TEXT
FROM V$SQL query_
WHERE query_.SQL_ID = session_.SQL_ID
AND ROWNUM <= 1) AS CURRENT_SQL
FROM V$SESSION session_,
V$TRANSACTION transaction_
WHERE session_.SADDR = transaction_.SES_ADDR;
En la consulta, revisa las siguientes columnas:
LAST_CALL_ET
muestra los segundos desde la última actividad en la sesión;START_DATE
de la transacción asociada a algún evento en la aplicación;- la consulta previa
PREV_SQL
o la actualCURRENT_SQL
en la sesión;
Normalmente, esta información basta para identificar el origen del problema.
Logs
Rastrear las llamadas a nivel JDBC puede ayudarte a asociar una transacción abierta a un proceso concreto.
Revisa la configuración de logging de OJDBC.
Necesitarás un driver compilado para logging, con sufijo _g
, por ejemplo desde un repositorio Maven:
<dependency>
<groupId>com.oracle.database.jdbc.debug</groupId>
<artifactId>ojdbc8_g</artifactId>
<version>21.7.0.0</version>
</dependency>
Tras la configuración básica, verás logs que corresponden al establecimiento de conexión (eventos logon) y consultas ejecutadas:
2022-10-02 10:59:12.150 INFO 524 --- [main] oracle.jdbc: setCollectionUsageThreshold<PS Old Gen>(5136659251)
2022-10-02 10:59:13.018 INFO 524 --- [main] oracle.jdbc: Connection.logon: oracle.jdbc.driver.T4CConnection@66f659e6
2022-10-02 10:59:13.018 INFO 524 --- [main] oracle.jdbc: Operating System Process Identifier (SPID): 2411
2022-10-02 10:59:13.018 INFO 524 --- [main] oracle.jdbc: DRCP Enabled: false
2022-10-02 10:59:13.029 INFO 524 --- [main] com.zaxxer.hikari.HikariDataSource: HikariPool-1 - Start completed.
2022-10-02 10:59:13.795 INFO 524 --- [main] oracle.jdbc: 30839E44 SQL: SET TRANSACTION READ ONLY
2022-10-02 10:59:13.900 INFO 524 --- [main] oracle.jdbc: 47E51549 SQL: SELECT * FROM orders@remote
Además, con el logging más detallado, también verás el SID/SERIAL#/TRACEFILE asociado a las consultas:
Oct 02, 2022 11:32:07 AM oracle.jdbc.driver.T4CConnection getSerialNumber
FINEST: 191A709B Return: 42858
Oct 02, 2022 11:32:07 AM oracle.jdbc.driver.T4CConnection getSessionId
FINEST: 191A709B Return: 285
Oct 02, 2022 11:32:07 AM oracle.jdbc.driver.OracleSql getOriginalSql
FINEST: 360E9C06 Return: SELECT * FROM orders@remote
Puedes obtener la asociación sesión-proceso consultando la base:
SELECT *
FROM V$SESSION s
JOIN V$PROCESS p on s.PADDR = p.ADDR
WHERE p.SPID = 2411;
Desafortunadamente, este logging tan bajo puede generar decenas de MBs de logs por segundo, así que configúralo
solo para los paquetes relevantes. Así puedes identificar una función por logs de hilo compartido o algún ID de correlación.
Alternativamente, puedes añadir información de tracking a la sesión de base de datos con el paquete DBMS_APPLICATION_INFO
.
connection.setClientInfo("OCSID.MODULE", "My application");
connection.setClientInfo("OCSID.ACTION", "Generate a report");