Parámetros de las transacciones usando Visual FoxPro

12 comentarios

Como ya seguramente sabes, todas las operaciones (INSERT, UPDATE, DELETE, SELECT, FETCH, EXECUTE PROCEDURE, etc.) que puedes realizar con las bases de datos de Firebird requieren ser realizadas dentro de transacciones, sí o sí, en el 100% de los casos.

Esas transacciones deben tener un comienzo y un fin.

Antes de comenzar una transacción puedes (si quieres) establecer cuales serán los parámetros que se usarán en esa transacción. Si tú no los estableces entonces el Cliente del Firebird lo hará por tí.

Parámetros por defecto de las transacciones

Si tú no indicas cuales serán los parámetros que utilizará la siguiente transacción entonces el Cliente del Firebird determina los siguientes:

  • READ WRITE
  • WAIT
  • SNAPSHOT

READ WRITE significa que la transacción podrá cambiar el contenido de las tablas. La alternativa es READ ONLY que significa que se puede leer el contenido de las tablas pero no cambiarlos.

WAIT significa que si encuentra un conflicto con otra transacción entonces esperará hasta que la otra transacción termine. La alternativa es NO WAIT lo cual significa que si encuentra un conflicto con otra transacción inmediatamente se elevará una excepción con un mensaje de error.

SNAPSHOT significa que si otra transacción realizó cambios a las tablas después que esta transacción haya empezado, esta transacción no se enterará de esos cambios. Las alternativas son READ COMMITTED que le permite a esta transacción enterarse de todos los cambios que otras transacciones hayan realizado y confirmado (se confirma con un COMMIT) y SNAPSHOT TABLE STABILITY que abre a todas las tablas que usa esta transacción en forma exclusiva, impidiendo por lo tanto a las otras transacciones realizar cambios a esas tablas.

Especificando los parámetros de la siguiente transacción

Una vez que una transacción empezó ya no puedes cambiar los parámetros de la misma. Eso implica que todos los parámetros debes establecerlos antes de que la transacción comience. Y recuerda que si tú no estableces los parámetros de una transacción entonces el Cliente del Firebird los establece por tí usando los valores por defecto (READ WRITE, WAIT, SNAPSHOT), como vimos más arriba.

Esto es importante recordar, porque puede prestarse a confusión: una transacción nunca hereda los parámetros de la transacción anterior. Los parámetros de una transacción los estableces tú específicamente con los valores que deseas o los establece el Cliente del Firebird, pero en ambos casos solamente se aplican a la siguiente transacción, a ninguna otra.

La clase SQL_CLASES

Si programas con el lenguaje Visual FoxPro entonces puedes descargar SQL_CLASES.PRG, allí encontrarás propiedades y métodos que te facilitarán grandemente el uso de Firebird.

http://www.mediafire.com/view/l36nar3o6ap19b9/SQL_CLASES.PRG

A continuación, la explicación sobre las propiedades y métodos relacionados con las transacciones.

Propiedades

cTransaccionAcceso           = "READ WRITE"     && la alternativa es   : READ ONLY
cTransaccionAislamiento      = "READ COMMITTED" && las alternativas son: SNAPSHOT, SNAPSHOT TABLE STABILITY
cTransaccionModoBloqueo      = "WAIT"           && la alternativa es   : NO WAIT
cTransaccionRecordVersion    = "RECORD_VERSION" && la alternativa es   : NO RECORD_VERSION
cTransaccionReservacion      = "SHARED"         && las alternativas son: PROTECTED, READ, WRITE
cTransaccionTablasReservadas = ""               && la alternativa es   : RESERVING MisTablas FOR PROTECTED WRITE
cTransaccionTipo             = ""               && las alternativas son: ABM, INFORME
nTipoTransaccion             = 2                && 1 = Transacción automática, 2 = Transacción manual.

Estas propiedades serán luego usadas por la función .Abrir_Transaccion(), la cual como su nombre lo indica se encarga de abrir las transacciones.

Métodos

.Abrir_Transaccion() abre una transacción utilizando los valores que tienen las propiedades. Es en esas propiedades donde determinamos como queremos que sea la transacción que se abrirá.

.Abrir_Transaccion_ABM() abre una transacción que será utilizada para realizar cambios a las tablas (INSERT, UPDATE, DELETE). Primero, les asigna valores a las propiedades y luego llama a la función .Abrir_Transacción(), que vimos en el párrafo anterior. Desde luego que podrías tener funciones .Abrir_Transaccion_ABM_2(), .Abrir_Transaccion_ABM_3(), etc. con valores distintos.

.Abrir_Transaccion_Informe() abre una transacción que será utilizada solamente con el comando SELECT. Primero, le asigna valores a las propiedades y luego llama a la función .Abrir_Transaccion(). Si tus necesidades aumentan podrías tener funciones .Abrir_Transaccion_Informe_2(), .Abrir_Transaccion_Informe_3(), etc.

En realidad, podríamos tener solamente la función .Abrir_Transaccion(), sin necesidad de las otras dos funciones. Pero están allí para facilitarnos la vida, así cuando queremos una transacción que cambiará el contenido de alguna tabla llamamos a la función  .Abrir_Transaccion_ABM() y cuando queremos una transacción que solamente realizará consultas llamamos a la función .Abrir_Transaccion_Informe(). Si no tuviéramos a esas dos funciones entonces antes de llamar a .Abrir_Transaccion() tendríamos que establecer los valores de las propiedades, lo cual nos haría escribir más y con el riesgo de equivocarnos alguna vez. Gracias a estas dos funciones escribimos menos y nos aseguramos que las propiedades siempre tengan los valores correctos.

.Ejecutar() se encarga de ejecutar el comando que le enviamos como parámetro. Pero antes de ello verifica que tengamos una transacción abierta con la función .Abrir_Transaccion_ABM() o con la función .Abrir_Transaccion_Informe(). ¿Por qué? Porque así nos aseguramos que la transacción cumpla con nuestros requisitos. Si queremos ejecutar un comando y no tenemos una transacción abierta por nosotros entonces el comando no será ejecutado. En otras palabras, no aceptamos transacciones abiertas automáticamente por el Cliente del Firebird, todas las transacciones debemos abrirlas nosotros usando o la función .Abrir_Transaccion_ABM() o la función .Abrir_Transaccion_Informe(). De esta manera nos aseguramos que la transacción siempre tenga los valores que deseamos que tenga. Si el comando que queremos ejecutar es un COMMIT o un ROLLBACK entonces sabemos que allí la transacción finaliza y se coloca en la propiedad .cTransaccionTipo el valor “”, de esa manera podemos saber que no hay una transacción abierta.

Funciones de Visual FoxPro

Para escribir menos y facilitarnos la vida, en lugar de llamar a las funciones de SQL_CLASES llamamos a otras funciones que realizan la misma tarea. A esta técnica se le llama “enmascarar” y podemos leerla en este artículo:

Enmascarando los stored procedures

Las funciones enmascaradoras que usamos son:

  • ABRIR_TRANSACCION_ABM()
  • ABRIR_TRANSACCION_INFORME()

Como ves, tienen los mismos nombres que los usados en SQL_CLASES pero en este caso se trata de funciones independientes, no son funciones dentro de una clase.

FUNCTION ABRIR_TRANSACCION_ABM
Local lcAlias, llResultadoOK

   lcAlias = Alias()

   llResultadoOK = _SCreen.goSQL.ABRIR_TRANSACCION_ABM()

   if !Empty(lcAlias)
      select (lcAlias)
   endif

Return(llResultadoOK)
*
*
FUNCTION ABRIR_TRANSACCION_INFORME
Local lcAlias, llResultadoOK

   lcAlias = Alias()

   llResultadoOK = _SCreen.goSQL.ABRIR_TRANSACCION_INFORME()

   if !Empty(lcAlias)
      select (lcAlias)
   endif

Return(llResultadoOK)
*
*
FUNCTION CERRAR_TRANSACCION
LParameters tcModo
Local lcAlias, llResultadoOK

   if !Empty(_Screen.goSQL.cTransaccionTipo) && Solamente cierra las transacciones abiertas
      lcAlias = Alias()
      tcModo = iif(VarType(tcModo) <> "C", "COMMIT", tcModo)
      llResultadoOK = iif((tcModo == "COMMIT" .or. tcModo == "ROLLBACK"), .T., .F.)
      if llResultadoOK
         llResultadoOK = SQL_Ejecutar(tcModo, "CUR_CERRAR")
      endif
      if !llResultadoOK .and. tcModo == "COMMIT" && Si no se pudo conseguir que la transacción finalice con un COMMIT, hay que finalizarla con un ROLLBACK
         =SQL_Ejecutar("ROLLBACK", "CUR_CERRAR")
         do MENSAJE_ERROR with "Falló el COMMIT, eso es un error grave.*Se finalizó la transacción con un ROLLBACK"
      endif
      _Screen.goSQL.cTransaccionTipo = ""
      if !Empty(lcAlias) .and. Used(lcAlias)
         select (lcAlias)
      endif
   else
      llResultadoOK = .T.
   endif

Return(llResultadoOK)
*
*

Lo que hacen las dos primeras funciones es, primero guardar el nombre de la tabla .DBF que tenemos abierta (si hay alguna tabla .DBF abierta, por supuesto), segundo llamar a la función de SQL_CLASES que nos interesa y tercero si había una tabla .DBF abierta volver a seleccionarla. Esas funciones nos devuelven un valor indicando si la transacción se pudo abrir exitosamente o no.

La función Cerrar_Transaccion() como su nombre lo indica se encarga de cerrar la transacción que tenemos abierta. Si no le indicamos si queremos cerrarla con un COMMIT o con un ROLLBACK entonces asume que queremos cerrarla con un COMMIT, ya que eso es lo más común. Para que sepamos que ya no hay una transacción abierta le asigna “” a la propiedad .cTransaccionTipo.

Ejemplos de uso de las transacciones

Ahora que ya hemos visto la clase y las funciones relacionadas con las transacciones veamos algunos ejemplos de como usarlas.

=Abrir_Transaccion_Informe()
llResultado = SQL_Ejecutar("SELECT * FROM PERSONALIZAR WHERE PER_USUARI = " + lcUsuario)
=Cerrar_Transaccion()

En este caso queremos realizar una consulta entonces se llamó a la función enmascaradora Abrir_Transaccion_Informe(), se realizó la consulta y se cerró la transacción.

TEXT TO lcComando NOSHOW
   EXECUTE PROCEDURE GRABAR_PERSONALIZACION (?lcUsuario, ?lcImagenFondo, ?lnColorTextoMensajesAyuda, ?lcArchivoSonido)
ENDTEXT

=Abrir_Transaccion_ABM()
llGrabacionOK = SQL_Ejecutar(lcComando)
=Cerrar_Transaccion()

En este caso llamamos a un stored procedure que cambiará el contenido de una tabla, por lo tanto llamamos a la función enmascaradora Abrir_Transaccion_ABM(), ejecutamos el comando y cerramos la transacción.

IMPORTANTE:

En Firebird es extremadamente importante tener a las transacciones abiertas durante el menor tiempo posible. Cuanto menos tiempo dure una transacción mucho mejor, porque eso ayudará a evitar conflictos con otras transacciones que quieran acceder a las mismas filas de las mismas tablas.

Como puedes ver en los ejemplos anteriores el procedimiento es abrir la transacción, ejecutar un comando, e inmediatamente cerrar la transacción. Eso es lo correcto.

Conclusión:

Los parámetros de las transacciones los puede establecer el Cliente del Firebird o los podemos establecer nosotros. Si los establecemos nosotros entonces tenemos un mucho mayor control sobre el actuar de esa transacción.

El lenguaje Visual FoxPro nos permite crear clases, esa es una muy buena característica del lenguaje y debemos aprovecharla. Aquí vemos un ejemplo del uso de las clases, en este caso para usarlas con Firebird.

Enmascarar las llamadas a rutinas o funciones nos facilita la vida porque escribimos menos y nos aseguramos de que todo esté siempre correcto. Las transacciones deben durar el menor tiempo posible, por eso lo correcto es abrirlas, ejecutar operaciones en ellas, y cerrarlas inmediatamente, tal como se ha visto en los ejemplos.

Artículos relacionados:

Entendiendo las transacciones

Enmascarando los stored procedures

El índice del blog Firebird21

El foro del blog Firebird21

Velocidad de acceso a constantes, variables y columnas

Deja un comentario

A veces tenemos la alternativa de usar en un SELECT una constante, una variable, o el valor de una columna. Desde el punto de vista de la velocidad ¿hay diferencias entre cuál de ellas usar?

Alternativa 1. Usando una constante.

SELECT
   MiColumna
FROM
   MiTabla
WHERE
   MiColumna = 4567

Alternativa 2. Usando una variable.

SELECT
   MiColumna
FROM
   MiTabla
WHERE
   MiColumna = RDB$GET_CONTEXT('USER_SESSION', 'MIVALOR')

Alternativa 3. Usando el valor de una columna.

SELECT
   MiColumna
FROM
   MiTabla
WHERE
   MiColumna = (SELECT MiColumnaInteresante FROM MiOtraTabla)

Y en este caso MiOtraTabla tiene una sola fila.

¿Cuál de estas tres alternativas es la más rápida?

La más rápida es la Alternativa 1., luego le sigue la Alternativa 2. y finalmente la Alternativa 3, la más lenta de todas.

Claro que las diferencias de velocidad se miden en milisegundos así que en la práctica muchas veces será lo mismo si utilizas cualquiera de esas alternativas.

Artículos relacionados:

Usando variables en Firebird

El índice del blog Firebird21

 

Usando variables en Firebird

Deja un comentario

Cuando programamos con Firebird, al igual que cuando programamos con cualquier lenguaje de programación podemos usar variables. En el caso de Firebird esas variables pueden ser usadas en los stored procedures y en los triggers.

Tenemos cuatro tipos de variables que podemos usar:

  1. Las variables locales
  2. Las variables del Sistema
  3. Las variables de la Conexión
  4. Las variables de la Transacción

1. Las variables locales

Solamente existen dentro del stored procedure o del trigger que las declaró. Para declarar una variable local se utiliza el comando DECLARE VARIABLE. Veamos algunos ejemplos:

DECLARE VARIABLE lcNombre     VARCHAR(40);
DECLARE VARIALBE lnCantidad   SMALLINT;
DECLARE VARIABLE ldFechaVenta DATE;

 Todas las anteriores son variables locales, pueden ser usadas en cualquier lugar dentro del stored procedure o del trigger que las declaró pero cuando éste termina todas esas variables desaparecen, dejan de existir.

2. Las variables del sistema

Estas son puestas por el propio Firebird y los programadores pueden usarlas pero no cambiarles los valores que tienen puestos. Pueden ser de dos tipos:

a) Variables de contexto en el namespace SYSTEM

b) Variables globales

Las variables de contexto en el namespace SYSTEM se recuperan usando la función RDB$GET_CONTEXT() y enviando como primer parámetro de esa función a la palabra ‘SYSTEM’ y como segundo parámetro el nombre de la variable que nos interesa.

SELECT
   RDB$GET_CONTEXT('SYSTEM', 'DB_NAME')
FROM
   RDB$DATABASE

En este caso se recupera la ruta y el nombre completo de la Base de Datos usando para ello la variable DB_NAME la cual existe en el namespace SYSTEM. Puedes ver cuales son las otras variables de ese namespace en este artículo:

https://firebird21.wordpress.com/2013/04/13/rdbget_context/

SELECT
   CURRENT_USER
FROM
   RDB$DATABASE

En el ejemplo anterior obtuvimos el nombre del usuario actual mediante la variable global CURRENT_USER.

3. Las variables de la Conexión

Estas son variables del programador. O sea que es el programador quien les asigna valores y los recupera después. Sus valores pueden ser escritos y leídos en cualquier stored procedure o en cualquier trigger y en cualquier momento, pero cuando el usuario se desconecta de la Base de Datos todas estas variables desaparecen, dejan de existir.

SELECT
   RDB$SET_CONTEXT('USER_SESSION', 'CANTIDAD_INSERTS', lnCantidadInserts + 1)
FROM
   RDB$DATABASE

Arriba se asignó un valor a una variable de la conexión llamada CANTIDAD_INSERTS, la cual puede ser utilizada en cualquier stored procedure o en cualquier trigger y mantendrá su valor en cada uno de ellos. Podríamos utilizarla para saber cuantos INSERT realizó el usuario y así poder decirle algo como: “Juan, estuviste muy haragán hoy, solamente insertaste 6 filas en las tablas”.

SELECT
   RDB$GET_CONTEXT('USER_SESSION', 'CANTIDAD_INSERTS')
FROM
   RDB$DATABASE

El código de arriba nos permite conocer cual es el valor actual de la variable de la conexión llamada CANTIDAD_INSERTS.

Recuerda que las variables de la conexión pueden ser usadas desde el momento en que el usuario se conectó a la Base de Datos hasta el momento en que se desconectó.

4. Las variables de la Transacción

Estas también son variables del programador. El programador puede escribir en ellas cualquier valor que desee y leer ese valor después. Sus valores pueden ser escritos y leídos en cualquier stored procedure o en cualquier trigger y en cualquier momento, pero cuando termina la transacción todas estas variables desaparecen, dejan de existir.

SELECT
   RDB$SET_CONTEXT('USER_TRANSACTION', 'CANTIDAD_INSERTS', lnCantidadInserts + 1)
FROM
   RDB$DATABASE

En este caso la variable CANTIDAD_INSERTS solamente existe en la transacción actual. Cuando la transacción termina la variable CANTIDAD_INSERTS es eliminada, deja de existir.

O sea que las usaríamos para guardar valores que solamente son relevantes en la transacción actual.

Recuerda que las variables de la transacción pueden ser usadas desde el momento en que se inició la transacción hasta el momento en que se terminó la transacción.

Conclusión:

Cuando programamos siempre necesitamos usar variables, es imposible programar cualquier aplicación no trivial sin usar variables. Con Firebird los alcances de las variables pueden ser:

  • locales
  • del sistema
  • de la conexión
  • de la transacción.

Las variables locales solamente pueden ser usadas dentro del stored procedure o del trigger que las declaró. El programador puede escribirlas y leerlas.

Las variables del sistema pueden ser leídas en cualquier stored procedure o trigger pero sus valores no pueden ser cambiados, ya que son puestos por el propio Firebird, y por lo tanto nunca los programadores pueden cambiarlos.

Las variables de la conexión pueden ser usadas desde el momento que empezó la conexión a la Base de Datos hasta el momento en que esa conexión finalizó, en cualquier stored procedure o trigger. El programador puede escribirlas y leerlas.

Las variables de la transacción pueden ser usadas desde el momento en que empezó la transacción hasta el momento en que finalizó la transacción, en cualquier stored procedure o trigger. El programador puede escribirlas y leerlas.

Artículos relacionados:

RDB$GET_CONTEXT()

RDB$SET_CONTEXT()

El índice del blog Firebird21

 

Usando un SELECT en una columna computada

Deja un comentario

Las columnas computadas son columnas donde no se introducen datos, sino que su contenido es obtenido después de realizar algunas operaciones en otras columnas.

https://firebird21.wordpress.com/2013/06/23/columnas-computadas/

Por ejemplo, tenemos una columna para guardar los APELLIDOS de una persona, otra columna para guardar los NOMBRES de esa persona y creamos una columna computada para tener en ella los APELLIDOS y los NOMBRES. Algo como esto:

ALU_APENOM COMPUTED BY (TRIM(ALU_APELLD) || ', ' || ALU_NOMBRE)

Los usuarios introducen datos en las columnas ALU_APELLD (apellidos del alumno) y ALU_NOMBRE (nombres del alumno) pero no en la columna ALU_APENOM (apellidos y nombres del alumno). ¿Por qué no en esa columna? porque esa es una columna computada como podemos ver en la definición, ya que el nombre de la columna está seguido por COMPUTED BY. En el ejemplo anterior las columnas ALU_APELLD y ALU_NOMBRE pertenecen a la misma tabla que la columna ALU_APENOM. ¿Pero y si queremos columnas de otra tabla, es posible conseguir eso?

Sí, es posible.

Para ello debemos:

  1. Crear un stored procedure seleccionable
  2. Usar doble paréntesis, rodeando al SELECT que nos traerá los datos que queremos.

Ejemplo:

Supongamos que tenemos una tabla llamada EMPLEADOS y otra tabla llamada SALARIOS y deseamos que la tabla EMPLEADOS tenga una columna computada que contendrá el total de los salarios pagados a cada empleado.

CREATE PROCEDURE TOTAL_PAGADO(
   tnIdeEmp INTEGER)
RETURNS(
   ftnPagado INTEGER)
AS
BEGIN

   ftnPagado = (SELECT SUM(SAL_MONTOX) FROM SALARIOS WHERE SAL_IDEEMP = :tnIdeEmp);

   SUSPEND;

END;

Este stored procedure seleccionable (sabemos que es seleccionable porque tiene el comando SUSPEND en él) halla y devuelve la suma de todos los salarios pagados a cada empleado. Recibe como parámetro de entrada el identificador del empleado que nos interesa y devuelve la suma de los salarios que se le pagaron. Recuerda que el stored procedure debe ser seleccionable para que esta técnica funcione.

CREATE TABLE EMPLEADOS (
   EMP_IDENTI BIGINT NOT NULL,
   EMP_NOMBRE VARCHAR(20),
   EMP_APELLD VARCHAR(20),
   EMP_PAGADO COMPUTED BY ((SELECT FTNPAGADO FROM TOTAL_PAGADO(EMP_IDENTI))));

En la columna computada EMP_PAGADO el SELECT debe estar rodeado por dos paréntesis. Si pones un solo paréntesis el Firebird se enojará contigo y te dirá que cometiste un error. Debes usar dobles paréntesis.

 Luego, escribiendo un comando tal como:

SELECT
   *
FROM
   EMPLEADOS

Esto será lo que obtendremos:

COMPUTED1

Captura 1. Si haces clic en la imagen la verás más grande

Que es exactamente lo que queríamos obtener. Los datos de cada empleado y el total de los salarios que le hemos pagado hasta este momento.

Desde luego que podríamos haber obtenido estos mismos datos sin necesidad de usar columnas computadas, el hecho de usar columnas computadas tiene la ventaja de que nuestros SELECTs son más sencillos de escribir. Fíjate en el SELECT que nos mostró la Captura 1., si no las hubiéramos usado ese SELECT hubiera sido más complicado de escribir. Aquí simplemente escribimos: SELECT * FROM EMPLEADOS y ya obtuvimos lo que queríamos.

NOTA IMPORTANTE:

Esta técnica debe solamente ser empleada con versiones de Firebird 2.1 ó posteriores, ya que las versiones anteriores tenían un problema que causaba que los backups no pudieran ser restaurados. Así que si estás usando Firebird 1.0 ó Firebird 1.5 ó Firebird 2.0, ni se te ocurra utilizar esta técnica.

Y aunque estés usando Firebird 2.1 ó posterior siempre es recomendable que hagas un backup y lo restaures inmediatamente, para verificar de que tu backup puede ser restaurado exitosamente.

Conclusión:

Usar el comando SELECT dentro de una columna computada es una técnica de programación que nos facilitará escribir futuros SELECTs. Para que esta técnica funcione debemos hacer dos cosas:

  1. Escribir un stored procedure seleccionable
  2. Rodear con doble paréntesis a la columna COMPUTED BY

Artículos relacionados:

Columnas computadas

El índice del blog Firebird21

El foro del blog Firebird21

Vaciando el caché lo mínimo posible

Deja un comentario

Supongo que sabes lo que es la memoria caché.

La memoria caché usada por el Firebird puede ser grabada en el disco duro por instrucciones del propio Firebird o cuando quiere el Sistema Operativo.

Esa grabación en el disco duro toma su tiempo, no es instantánea y a veces tu intención es que las operaciones con la Base de Datos sean lo más rápidas posibles. Aquí te mostraré lo que debes hacer para que la grabación en el disco duro ocurra muy raras veces, lograrás así mayor velocidad en todas las operaciones.

CUIDADO:

Esta es una técnica muy riesgosa, antes de emplearla debes verificar que tienes una UPS (unidad de energía no interrumpida) de buena capacidad y funcionando correctamente. De no ser así un corte de la energía eléctrica hará que se pierdan todos los datos que estaban en la memoria caché, que pueden ser muchísimos.

Como evitar que Firebird vacíe el caché en el disco duro

Forced writes = OFF

Puedes establecer Forced writes en OFF con el programa GFIX, tal como se muestra a continuación:

GFIX -user SYSDBA -password masterkey MiBaseDatos -write async

MaxUnflushedWrites = -1
MaxUnflushedWriteTime = -1

Estos valores los debes establecer en el archivo FIREBIRD.CONF, el cual se encuentra en la misma carpeta donde instalaste el Firebird (por ejemplo en: C:\Archivos de Programa\Firebird\Firebird_2_5\)

Con estos ajustes el Firebird jamás vaciará el caché en el disco duro, eso hará que las operaciones con la Base de Datos sean más rápidas pero también incrementará muchísimo el riesgo de problemas si llega a ocurrir un corte de la energía eléctrica.

Por lo tanto, no es recomendable que lo hagas salvo en casos muy puntuales y por muy poco tiempo, no te acostumbres a dejar de vaciar el caché porque la Ley de Murphy está siempre vigente (“si algo malo puede pasar, pasará, y en el peor momento posible”).

Un caso en que dejar de vaciar el caché te podría ayudar mucho es cuando tienes que introducir muchísimas filas en tus tablas “a bulto”, por ejemplo, tienes los datos en miles de filas de una planilla Excel y los quieres enviar al Firebird. Pero apenas termines de migrar esos datos ya deberías poner Forced writes en ON y MaxUnflushedWrites y MaxUnflushedWriteTime con los valores que tenían anteriormente.

Artículo relacionado:

El índice del blog Firebird21

 

Conociendo el programa que mantiene una transacción abierta

Deja un comentario

Cuando la diferencia entre la OAT (Oldest Active Transaction= transacción activa más antigua) y la NT (Next Transaction = siguiente transacción) es mayor que 1 y nadie está usando la Base de Datos podemos deducir que algún programa finalizó sin hacer un COMMIT o un ROLLBACK a su transacción.

Como sabemos, todas las transacciones deben finalizar con un COMMIT o con un ROLLBACK, inclusive las que solamente hacen un SELECT.

Si un programa no finaliza sus transacciones con un COMMIT o con un ROLLBACK eso está mal y debemos detectar cual es ese programa y corregir el problema.

El siguiente SELECT nos dirá el nombre del programa problemático:

SELECT
   *
FROM
   MON$ATTACHMENTS
WHERE
   MON$ATTACHMENT_ID = 
      (SELECT 
          MON$ATTACHMENT_ID 
       FROM 
          MON$TRANSACTIONS 
       WHERE 
          MON$TRANSACTION_ID = 
             (SELECT 
                 MIN(MON$TRANSACTION_ID) 
              FROM 
                 MON$TRANSACTIONS))

En la columna MON$REMOTE_PROCESS veremos el nombre del programa ejecutable (o sea, con extensión .EXE) que inició la transacción. Ese programa tiene un problema y debemos pedirle al programador que corrija el problema. El problema es que abrió una transacción y jamás la cerró. En otras palabras, en algún lugar le está faltando un COMMIT o un ROLLBACK.

Pero también puede haber otro motivo, mucho menos frecuente pero que a veces puede ocurrir: ese otro motivo es que el programa .EXE sí finaliza correctamente todas sus transacciones (con un COMMIT o con un ROLLBACK) pero ocurrió un corte de la energía eléctrica o un usuario presionó las teclas CTRL-ALT-DEL o presionó el botón de “Reset”, y entonces aunque el .EXE finalizó no se cerraron las transacciones que tenía abiertas.

Artículos relacionados:

Entendiendo los identificadores de las transacciones

Verificando periódicamente los identificadores de las transacciones

El índice del blog Firebird21

El foro del blog Firebird21

 

 

 

Impidiendo la conexión a una Base de Datos

4 comentarios

En ocasiones queremos evitar que un usuario, un proceso o una computadora se conecten a nuestra Base de Datos. O varios usuarios, varios procesos o varias computadoras.

Ya habíamos visto algunos artículos sobre este tema:

Como evitar que se conecten a una Base de Datos

Evitando que un usuario se conecte más de una vez a la Base de Datos

Evitando que un usuario se conecte más de una vez (método mejorado)

En el presente artículo nos explayaremos más sobre como evitar las conexiones indeseadas.

A partir del Firebird 2.1 podemos usar los llamados triggers de las bases de datos (database triggers, en inglés) y nos servirán perfectamente para lo que pretendemos lograr.

El primer paso es crear una excepción, la cual mostrará un mensaje al usuario. En realidad esto no es obligatorio, podríamos dejar que el Firebird muestre su mensaje por defecto pero ese mensaje estará en inglés. Si queremos personalizar el mensaje entonces deberemos crear una excepción, como la siguiente:

CREATE EXCEPTION
   E_ACCESO_NO_PERMITIDO 'No tienes permiso para conectarte a esta Base de Datos' ;

A continuación crearemos los triggers de bases de datos que necesitaremos:

Un trigger para evitar que los usuarios se conecten a la Base de Datos:

Listado 1.

CREATE TRIGGER NEW_DBTRIGGER_C
   ACTIVE ON CONNECT
   POSITION 2
AS
BEGIN

   IF (CURRENT_USER IN ('JUAN', 'MARIA', 'ESTELA','MONICA')) THEN
      EXCEPTION E_ACCESO_NO_PERMITIDO;

END;

Ningún usuario cuyo nombre esté dentro del IN (en este caso: JUAN, MARIA, ESTELA y MONICA) podrá conectarse a la Base de Datos.

Un trigger para evitar que los programas se conecten a la Base de Datos:

Listado 2.

CREATE TRIGGER NEW_DBTRIGGER_C1
   ACTIVE ON CONNECT
   POSITION 3
AS
BEGIN

   IF (EXISTS(SELECT
                 *
              FROM
                 MON$ATTACHMENTS M
              WHERE
                 M.MON$ATTACHMENT_ID = CURRENT_CONNECTION AND
                (NOT M.MON$REMOTE_PROCESS IN ('MI_PROG1.EXE', 'MI_PROG2.EXE')))) THEN
      EXCEPTION E_ACCESO_NO_PERMITIDO;

END;

En este caso solamente permitiremos el acceso a los programas cuyos nombres se encuentren en el IN, o sea: MI_PROG1.EXE y MI_PROG2.EXE. Evidentemente que tú escribirías allí los nombres de tus propios programas.

¿Cuál es el defecto que tiene esta protección? Que si el intruso sabe que MI_PROG1.EXE puede conectarse entonces podría renombrar a su propio programa como MI_PROG1.EXE y así conseguirá la conexión. Por eso siempre es importante que los mensajes de las excepciones le den al intruso muy poca información. Sería muy mala idea que el mensaje sea algo como: “Solamente se pueden conectar MI_PROG1.EXE y MI_PROG2.EXE” porque en ese caso lo primero que se le ocurrirá al intruso será renombrar a su programa y conseguirá conectarse. Además, hay que tomar en cuenta otro aspecto: no se debe impedir el acceso a los programas legítimos que podamos necesitar, como: GBAK.EXE

Un trigger para evitar que desde una computadora se conecten a la Base de Datos:

Listado 3.

CREATE TRIGGER NEW_DBTRIGGER_C2
   ACTIVE ON CONNECT
   POSITION 4
AS
BEGIN

   IF (EXISTS(SELECT
                 1
              FROM
                 MON$ATTACHMENTS M
              WHERE
                 M.MON$ATTACHMENT_ID = CURRENT_CONNECTION AND
                 M.MON$REMOTE_ADDRESS IN ('192.168.0.100', '192.168.0.104'))) THEN
      EXCEPTION E_ACCESO_NO_PERMITIDO;

END;

En este caso, las computadoras cuyos IP son 192.168.0.100 ó 192.168.0.104 jamás podrán conectarse a nuestra Base de Datos, siempre se les rechazarán todos los intentos de conexión.

Un trigger para evitar que desde una subred se conecten a la Base de Datos:

Listado 4.

A veces queremos que se rechace la conexión desde toda una subred y escribir todos los IP después del IN como se mostró en el ejemplo anterior sería muy largo y muy tedioso. Para esos casos tenemos una mucha mejor alternativa:


CREATE TRIGGER NEW_DBTRIGGER_C3
   ACTIVE ON CONNECT
   POSITION 4
AS
BEGIN

   IF (EXISTS(SELECT
                 1
              FROM
                 MON$ATTACHMENTS M
              WHERE
                 M.MON$ATTACHMENT_ID = CURRENT_CONNECTION AND
                 M.MON$REMOTE_ADDRESS STARTING WITH '192.168.14')) THEN
      EXCEPTION E_ACCESO_NO_PERMITIDO;

END;

Aquí estamos rechazando las conexiones de todas las computadoras de la subred 192.168.14, o en otras palabras cualquier computadora cuyo IP empiece con esos números será rechazada. Esto puede ser muy útil cuando por ejemplo tenemos una Base de Datos para los sueldos y jornales de los empleados y no queremos que desde otras computadoras de la Empresa puedan curiosear en ella. Entonces, de un plumazo las rechazamos a todas las que no se encuentran en nuestra misma subred.

Un trigger para evitar que se conecten fuera del horario autorizado:

Si el horario laboral de la empresa va de 8:00 a 18:00 entonces sería muy sospechoso que alguien tratara de conectarse a las 23:45 ¿verdad? ¿Por qué alguien querría conectarse a esa hora? Para estos casos podríamos tener un trigger que restrigiera el acceso fuera del horario establecido.

Listado 5.

CREATE TRIGGER NEW_DBTRIGGER_C4
   ACTIVE ON CONNECT
   POSITION 5
AS
BEGIN

 IF (CURRENT_USER <> 'SYSDBA' AND
     CURRENT_TIME < '07:45:00' OR CURRENT_TIME > '18:15:00') THEN
    EXCEPTION E_ACCESO_NO_PERMITIDO;

END;

Aquí, solamente al usuario SYSDBA le permitimos conectarse a cualquier hora que desee, todos los demás usuarios deberán iniciar la conexión entre las 07:45:00 y las 18:15:00

Desde luego que también podríamos restringir la conexión por los días de la semana: de Lunes a Viernes tendrían un horario de conexión, los Sábados tendrían otro horario de conexión y los Domingos tendrían otro horario.

O podríamos realizar combinaciones: MARCELA tiene un horario, SILVIA tiene otro horario, SUSANA tiene otro horario, etc.

Conclusión:

Para mantener la confidencialidad y la seguridad de los datos y de la información muchas veces es muy importante impedir que personas no autorizadas puedan conectarse a la Base de Datos. Esas personas podrían tener derechos de conexión a otras bases de datos de la Empresa, pero no a esta Base de Datos.

Usando los triggers de las bases de datos podemos impedir (o al menos dificultar) que se conecten quienes no deberían conectarse. Pero recuerda que ningún método es infalible. El usuario SYSDBA y el creador de la Base de Datos siempre podrán conectarse. Y podrían irse a almorzar dejando la conexión abierta y el intruso aprovechar la ocasión.

Hay muchísimas combinaciones más que puedes realizar pero con los ejemplos anteriores ya tendrás una buena idea de lo que se puede lograr.

Artículos relacionados:

Como evitar que se conecten a una Base de Datos

Evitando que un usuario se conecte más de una vez a la Base de Datos

Evitando que un usuario se conecte más de una vez (método mejorado)

El índice del blog Firebird21

El foro del blog Firebird21

Older Entries