ORA-01000, el error máximo de cursores abiertos, es un error extremadamente común en el desarrollo de bases de datos Oracle. En el contexto de Java, ocurre cuando la aplicación intenta abrir más ResultSets que cursores configurados en una instancia de base de datos.
Las causas comunes son:
Error de configuración
- Tiene más subprocesos en su aplicación que consultan la base de datos que cursores en la base de datos. Un caso es cuando tiene una conexión y un grupo de subprocesos mayor que el número de cursores en la base de datos.
- Tiene muchos desarrolladores o aplicaciones conectados a la misma instancia de base de datos (que probablemente incluirá muchos esquemas) y juntos están usando demasiadas conexiones.
Solución:
Fuga del cursor
- Las aplicaciones no cierran ResultSets (en JDBC) o cursores (en procedimientos almacenados en la base de datos)
- Solución : las fugas del cursor son errores; aumentar el número de cursores en la base de datos simplemente retrasa la falla inevitable. Las fugas se pueden encontrar mediante el análisis de código estático , JDBC o el registro a nivel de aplicación y la supervisión de la base de datos .
Antecedentes
Esta sección describe parte de la teoría detrás de los cursores y cómo se debe usar JDBC. Si no necesita conocer los antecedentes, puede omitir esto e ir directamente a 'Eliminación de fugas'.
¿Qué es un cursor?
Un cursor es un recurso en la base de datos que contiene el estado de una consulta, específicamente la posición en la que se encuentra un lector en un ResultSet. Cada instrucción SELECT tiene un cursor, y los procedimientos almacenados PL / SQL pueden abrir y usar tantos cursores como necesiten. Puede encontrar más información sobre los cursores en Orafaq .
Una instancia de base de datos normalmente sirve a varios esquemas diferentes , muchos usuarios diferentes , cada uno con varias sesiones . Para ello, dispone de un número fijo de cursores disponibles para todos los esquemas, usuarios y sesiones. Cuando todos los cursores están abiertos (en uso) y entra una solicitud que requiere un nuevo cursor, la solicitud falla con un error ORA-010000.
Encontrar y configurar el número de cursores
El DBA normalmente configura el número durante la instalación. Se puede acceder al número de cursores actualmente en uso, el número máximo y la configuración en las funciones de Administrador en Oracle SQL Developer . Desde SQL se puede configurar con:
ALTER SYSTEM SET OPEN_CURSORS=1337 SID='*' SCOPE=BOTH;
Relacionar JDBC en la JVM con cursores en la base de datos
Los siguientes objetos JDBC están estrechamente relacionados con los siguientes conceptos de base de datos:
- JDBC Connection es la representación del cliente de una sesión de base de datos y proporciona transacciones de base de datos . Una conexión solo puede tener una única transacción abierta a la vez (pero las transacciones se pueden anidar)
- Un ResultSet de JDBC es compatible con un solo cursor en la base de datos. Cuando se llama a close () en ResultSet, se suelta el cursor.
- Un JDBC CallableStatement invoca un procedimiento almacenado en la base de datos, a menudo escrito en PL / SQL. El procedimiento almacenado puede crear cero o más cursores y puede devolver un cursor como un conjunto de resultados JDBC.
JDBC es seguro para subprocesos: está bastante bien pasar varios objetos JDBC entre subprocesos.
Por ejemplo, puede crear la conexión en un hilo; otro hilo puede usar esta conexión para crear un PreparedStatement y un tercer hilo puede procesar el conjunto de resultados. La única restricción principal es que no puede tener más de un ResultSet abierto en un solo PreparedStatement en cualquier momento. Consulte ¿Oracle DB admite varias operaciones (paralelas) por conexión?
Tenga en cuenta que se produce una confirmación de base de datos en una conexión, por lo que todos los DML (INSERT, UPDATE y DELETE) en esa conexión se confirmarán juntos. Por lo tanto, si desea admitir varias transacciones al mismo tiempo, debe tener al menos una conexión para cada transacción simultánea.
Cerrar objetos JDBC
Un ejemplo típico de ejecución de un ResultSet es:
Statement stmt = conn.createStatement();
try {
ResultSet rs = stmt.executeQuery( "SELECT FULL_NAME FROM EMP" );
try {
while ( rs.next() ) {
System.out.println( "Name: " + rs.getString("FULL_NAME") );
}
} finally {
try { rs.close(); } catch (Exception ignore) { }
}
} finally {
try { stmt.close(); } catch (Exception ignore) { }
}
Tenga en cuenta cómo la cláusula finalmente ignora cualquier excepción generada por close ():
- Si simplemente cierra ResultSet sin el try {} catch {}, puede fallar y evitar que se cierre la declaración.
- Queremos permitir que cualquier excepción que surja en el cuerpo del intento se propague al llamador. Si tiene un bucle, por ejemplo, para crear y ejecutar declaraciones, recuerde cerrar cada declaración dentro del bucle.
En Java 7, Oracle ha introducido la interfaz AutoCloseable que reemplaza la mayor parte del texto estándar de Java 6 con un agradable azúcar sintáctico.
Sosteniendo objetos JDBC
Los objetos JDBC pueden guardarse de forma segura en variables locales, instancias de objetos y miembros de clases. En general, es una mejor práctica:
- Utilice la instancia de objeto o los miembros de la clase para contener los objetos JDBC que se reutilizan varias veces durante un período más largo, como Connections y PreparedStatements
- Utilice variables locales para ResultSets, ya que se obtienen, se repiten y luego se cierran normalmente dentro del alcance de una única función.
Sin embargo, hay una excepción: si está utilizando EJB, o un contenedor Servlet / JSP, debe seguir un modelo de subprocesamiento estricto:
- Solo el servidor de aplicaciones crea subprocesos (con los que maneja las solicitudes entrantes)
- Solo el servidor de aplicaciones crea conexiones (que obtiene del grupo de conexiones)
- Al guardar valores (estado) entre llamadas, debe tener mucho cuidado. Nunca almacene valores en sus propias cachés o miembros estáticos; esto no es seguro en clústeres y otras condiciones extrañas, y el servidor de aplicaciones puede hacer cosas terribles con sus datos. En su lugar, utilice beans con estado o una base de datos.
- En particular, nunca guarde objetos JDBC (Connections, ResultSets, PreparedStatements, etc.) sobre diferentes invocaciones remotas; deje que el servidor de aplicaciones lo administre. El servidor de aplicaciones no solo proporciona un grupo de conexiones, sino que también almacena en caché sus PreparedStatements.
Eliminando fugas
Hay varios procesos y herramientas disponibles para ayudar a detectar y eliminar fugas de JDBC:
Durante el desarrollo, la detección temprana de errores es, con mucho, el mejor enfoque:
Prácticas de desarrollo: las buenas prácticas de desarrollo deberían reducir la cantidad de errores en su software antes de que salga del escritorio del desarrollador. Las prácticas específicas incluyen:
- Programación en pareja , para educar a quienes no tienen suficiente experiencia
- Revisiones de código porque muchos ojos son mejores que uno
- Prueba unitaria, lo que significa que puede ejercitar toda su base de código desde una herramienta de prueba que hace que la reproducción de fugas sea trivial
- Utilice bibliotecas existentes para la agrupación de conexiones en lugar de crear las suyas propias
Análisis de código estático: utilice una herramienta como el excelente Findbugs para realizar un análisis de código estático. Esto detecta muchos lugares donde close () no se ha manejado correctamente. Findbugs tiene un complemento para Eclipse, pero también se ejecuta de forma independiente para casos únicos, tiene integraciones en Jenkins CI y otras herramientas de compilación
En tiempo de ejecución:
Sostenibilidad y compromiso
- Si la capacidad de retención del ResultSet es ResultSet.CLOSE_CURSORS_OVER_COMMIT, el ResultSet se cierra cuando se llama al método Connection.commit (). Esto se puede configurar mediante Connection.setHoldability () o mediante el método Connection.createStatement () sobrecargado.
Registro en tiempo de ejecución.
- Pon buenas declaraciones de registro en tu código. Estos deben ser claros y comprensibles para que el cliente, el personal de soporte y los compañeros de equipo puedan comprenderlos sin capacitación. Deben ser concisos e incluir la impresión del estado / valores internos de las variables y atributos clave para que pueda rastrear la lógica de procesamiento. Un buen registro es fundamental para depurar aplicaciones, especialmente aquellas que se han implementado.
Puede agregar un controlador JDBC de depuración a su proyecto (para la depuración, no lo implemente). Un ejemplo (no lo he usado) es log4jdbc . Luego, debe hacer un análisis simple en este archivo para ver qué ejecuciones no tienen un cierre correspondiente. El conteo de los abiertos y los cerrados debe resaltar si hay un problema potencial
- Seguimiento de la base de datos. Supervise su aplicación en ejecución utilizando herramientas como la función 'Supervisar SQL' de SQL Developer o TOAD de Quest . El monitoreo se describe en este artículo . Durante la supervisión, consulta los cursores abiertos (por ejemplo, de la tabla v $ sesstat) y revisa su SQL. Si el número de cursores aumenta y (lo más importante) se vuelve dominado por una declaración SQL idéntica, sabe que tiene una fuga con ese SQL. Busque su código y revíselo.
Otros pensamientos
¿Puedes usar WeakReferences para manejar conexiones de cierre?
Las referencias débiles y suaves son formas de permitirle hacer referencia a un objeto de una manera que le permita a la JVM recolectar basura al referente en cualquier momento que lo considere adecuado (asumiendo que no hay cadenas de referencia fuertes para ese objeto).
Si pasa una ReferenceQueue en el constructor a la ReferenceQueue suave o débil, el objeto se coloca en ReferenceQueue cuando el objeto es GC'ed cuando ocurre (si ocurre). Con este enfoque, puede interactuar con la finalización del objeto y podría cerrar o finalizar el objeto en ese momento.
Las referencias fantasmas son un poco más extrañas; su propósito es solo controlar la finalización, pero nunca puede obtener una referencia al objeto original, por lo que será difícil llamar al método close () en él.
Sin embargo, rara vez es una buena idea intentar controlar cuándo se ejecuta el GC (las referencias débiles, suaves y fantasma le informan después del hecho de que el objeto está en cola para GC). De hecho, si la cantidad de memoria en la JVM es grande (por ejemplo, -Xmx2000m), es posible que nunca GC el objeto y, de todos modos, experimente el ORA-01000. Si la memoria de la JVM es pequeña en relación con los requisitos de su programa, es posible que los objetos ResultSet y PreparedStatement sean GCed inmediatamente después de la creación (antes de que pueda leer de ellos), lo que probablemente fallará en su programa.
TL; DR: El mecanismo de referencia débil no es una buena manera de administrar y cerrar objetos Statement y ResultSet.
for (String language : additionalLangs) {