Programación en SQL
- ¿Por qué no es SQL un lenguaje computacionalmente completo?
Le faltan ciertas características como recursividad o estructuras repetitivas (bucles).
¿Qué soluciones conoces para aumentar las capacidades de cómputo de SQL? En SQL 2003 se introduce recursividad de forma limitada. Los bucles se consiguen combinando SQL con otros lenguajes procedimentales.
- SQL embebido: describe su funcionamiento.
Se denomina así al código SQL que se mezcla con el código de un lenguaje anfitrión. Existe un proceso de precompilación que transforma el SQL en llamadas a rutinas del gestor.
Limitaciones del SQL embebido estático.
El SQL dinámico soluciona las limitaciones del SQL estático. Con él podemos generar aplicaciones en las que el usuario puede crear de forma interactiva sus propias consultas, permite el uso de variables en nombres de tablas y nombres de atributos de tablas y soluciona el uso de FETCH si no se conocen los nombres de los atributos en tiempo de compilación.
- Librerías de acceso a datos: ¿qué tipo de funcionalidad proporcionan sus funciones?
Permiten establecer conexiones, ejecutar las consultas, obtener y procesar resultados, gestionar errores y otras funciones que SQL no permite hacer.
¿Qué problema existe con el uso de las librerías de PHP?
Al cambiar el SGBD (MySQL, PostgreSQL…), es necesario cambiar las funciones de conexión a la BD. Además, distintos SGBD no tienen por qué soportar las mismas sentencias SQL. Esto no sucede con ODBC y JDBC.
Soluciones propuestas con ODBC y JDBC.
Tanto ODBC como JDBC tienen una capa de software intermedia entre la base de datos y la aplicación final que se conecta a ella. Si cambiamos la base de datos o el SGBD no es necesario cambiar el código de la aplicación, ya que las funciones utilizadas disponibles en la librería de acceso a datos no variarán.
Gestión de transacciones
- ¿Qué es una transacción? Por un ejemplo.
Una transacción es una interacción con una estructura de datos compleja, compuesta por varios procesos que se han de aplicar uno después del otro. La transacción debe realizarse de una sola vez y sin que la estructura a medio manipular pueda ser alcanzada por el resto del sistema hasta que se hayan finalizado todos sus procesos. Está delimitada por instrucciones de la forma “begin transaction” y “end transaction”.
Describe las propiedades ACID de las transacciones detallando qué o quién debe ser el responsable de su cumplimiento.
- Atomicidad: o todas las operaciones de la transacción se ejecutan adecuadamente o ninguna. Es responsabilidad del SGBD, en concreto, del componente de gestión de transacciones.
- Consistencia: la ejecución aislada (sin concurrencia) de la transacción conserva la consistencia de la BD. Es responsabilidad del programador.
- Aislamiento: aunque se ejecuten varias transacciones concurrentemente, el sistema garantiza que para cada par de transacciones Ti y Tj se cumple que para los efectos de Ti, o bien Tj ha terminado su ejecución antes de que comience Ti, o bien que Tj ha comenzado su ejecución después de que Ti termine. Es responsabilidad del SGBD, en concreto, del componente de control de concurrencias.
- Durabilidad: tras la finalización correcta de la transacción, los cambios realizados en la BD permanecen, incluso si hay fallos en el sistema. Es responsabilidad del SGBD, en concreto del componente de gestión de recuperaciones.
- Describe los estados por los que puede pasar la ejecución de una transacción.
Pon un ejemplo.
- Activa (estado inicial): la transacción permanece en este estado durante su ejecución.
- Parcialmente Comprometida: la transacción pasa a este estado cuando acaba de realizar la última instrucción.
- Fallida: la transacción pasa a este estado tras descubrir que no puede continuar la ejecución normal.
- Abortada: la transacción pasa a este estado después de haber restablecido la base de datos a su estado anterior.
- Comprometida: la transacción pasa a este estado tras completarse con éxito.
Comenta el problema de las escrituras externas observables.
Hay que tener cuidado cuando se efectúan escrituras externas observables. Estas escrituras no pueden borrarse ya que puede haber sido vista fuera del sistema de BD. Muchos sistemas realizan este tipo de escrituras exclusivamente cuando la transacción correspondiente está comprometida. Muchas veces se desea que una transacción activa muestre al usuario ciertos datos, sobre todo cuando son transacciones de duración extensa. Esto arriesga la atomicidad del sistema.
- Implementación de la atomicidad y la durabilidad: describe la solución de copia en la sombra destacando sus ventajas y desventajas.
El componente de gestión de recuperaciones de un sistema de BD implementa el soporte para la atomicidad y la durabilidad.
En la copia en la sombra, todos los cambios se realizan en una copia de la BD dejando la original intacta. De esta forma, si se produjese un error la BD original no se vería afectada. Este esquema asume que sólo una transacción está activa en cada momento. Si la operación es satisfactoria se consulta al SO para asegurarse de que todas las páginas de la copia se han escrito en disco. Acto seguido se actualiza el puntero para que apuntes a la nueva copia de la base de datos. Esta última operación debe ser atómica para asegurar que la implementación de la copia en la sombra cumple las propiedades de atomicidad y durabilidad.
La solución de la copia en la sombra es poco eficiente y exige mucha memoria ya que la ejecución de una simple transacción requiere copiar la base de datos completa. En BD grandes supone un gran gasto. Además no se permiten con este sistema las transacciones concurrentes.
¿Qué otras soluciones conoces?
Existen otras formas más prácticas de implementar la atomicidad y durabilidad que son mucho menos costosas y más potentes. Algunas son el uso de esquemas basados en registro histórico, esquemas de modificación diferida o esquemas de modificación inmediata.
7. Secuencialidad: ¿cuándo es una planificación secuenciable?
Una planificación representa el orden cronológico en el que se ejecutan las instrucciones en el sistema. Una planificación secuenciable consiste en una sucesión de instrucciones de varias transacciones en la cual las instrucciones pertenecientes a una única transacción están contiguas en dicha planificación.
¿Cuándo son dos planificaciones equivalentes en cuanto a conflictos? Si una planificación P se puede transformar en otra P’ por medio de una serie de intercambios de instrucciones no conflictivas, se dice que P y P’ son equivalentes en cuanto a conflictos.
¿Cuándo es una planificación secuenciable en cuanto a conflictos?
Una planificación es secuenciable en cuanto a conflictos si es equivalente en cuanto a conflictos a una planificación secuencial. Se pueden encontrar dos planificaciones que nos lleven al mismo resultado y en cambio no sean equivalentes en cuanto a conflictos.
- ¿Cuándo no es recuperable una planificación?
Una planificación no es recuperable cuando, para todo par de transacciones Ti y Tj, donde Tj lee elementos que se han escrito previamente por Ti, Ti no se compromete antes que Tj.
¿Cuándo presenta una planificación retrocesos en cascada?
Incluso si una planificación es recuperable hay que hacer retroceder a varias transacciones para recuperar correctamente el estado previo a un fallo en Ti. Este fenómeno en el cual un fallo en una transacción provoca una serie de retrocesos de transacciones se denomina retrocesos en cascada.
- Describe alguna técnica que conozcas para comprobar si una planificación es secuenciable en cuanto a conflictos.
Tenemos la planificación P y un grafo de precedencia para P, G = (V, A).
V es el conjunto de todas las transacciones que participan en la planificación. A es el conjunto de arcos Ti > Tj para los que se cumple una de estas condiciones:
- Ti ejecuta escribir(Q) antes de que Tj ejecute leer(Q)
- Ti ejecuta leer(Q) antes de que Tj ejecute escribir(Q)
- Ti ejecuta escribir(Q) antes de que Tj ejecute escribir(Q)
Si existe el arco Ti > Tj en G, entonces en toda planificación P’ equivalente a P, Ti aparece antes que Tj.
Control de concurrencia
10. Protocolos basados en bloqueos: describe lo que son los bloqueos y la compatibilidad con ellos.
Una forma de asegurar la secuencialidad es exigir que el acceso a los elementos de datos se haga en exclusión mutua, es decir, mientras una transacción accede a un dato ninguna otra transacción puede modificar dicho elemento. Para esto se usan los bloqueos. El método más habitual es permitir que una transacción acceda a un elemento sólo si posee actualmente un bloqueo sobre dicho elemento.
Los bloqueos pueden tener dos modos: compartidos (Ti puede sólo leer) o exclusivos (Ti puede leer y escribir).
Dados dos modos arbitrarios A y B, Ti solicita un bloqueo en modo A sobre Q, bajo el que Tj tiene un bloqueo de modo B. Si a Ti se le puede conceder un bloqueo sobre Q a pesar de la presencia del bloqueo de modo B, entonces se dice que el modo A es compatible con el modo B.
¿A qué componente del SGBD se le piden?
Toda transacción debe solicitar el bloqueo apropiado al gestor de control de concurrencia.
¿Es posible usar bloqueos y tener planificaciones no secuenciables? ¿Por qué? Sí. En el caso de que una transacción bloquee un elemento y cuando acabe de usarlo lo desbloquee. Es decir, cuando no se desbloqueen los elementos justo antes de comprometerse la transacción.
¿Qué es un interbloqueo?
Cuando dos transacciones se encuentran a la espera de que la otra libere un bloqueo para poder continuar se entra en una situación de interbloqueo. En esta situación una de las dos transacciones deberá retrasar su estado.
¿Qué es un protocolo?
Se exige que toda transacción del sistema siga un conjunto de reglas llamado protocolo de bloqueos, que indica el momento en que una transacción puede bloquear y desbloquear cada uno de los elementos de datos.
¿Qué es una planificación legal para un protocolo?
Se dice que una planificación es legal bajo un protocolo de bloqueo dado si es una planificación posible para un conjunto de transacciones que sigan la regla de protocolo de bloqueo.
¿Cuándo podemos decir que un protocolo asegura la secuencialidad en cuanto a conflictos (grafo de precedencia)?
Dado un conjunto de transacciones que participan en una planificación S, se dice que Ti precede a Tj en S (Ti > Tj), si existe un elemento de datos Q tal que Ti ha obtenido un bloqueo en modo A sobre Q, y Tj ha obtenido un bloqueo en modo B sobre Q más tarde y A y B no son compatibles. Si Ti > Tj entonces esta precedencia implica que en cualquier planificación secuencial equivalente, Ti debe aparecer antes que Tj. Se puede asegurar la secuencialidad en cuanto a conflictos si y sólo si las planificaciones legales son secuenciables en cuanto a conflictos.
¿Cuándo se produce inanición?
Si existe una secuencia de transacciones que soliciten un bloqueo en modo compartido sobre Q, y cada una de ellas libera el bloqueo un poco después de que sea concedido, de forma que Ti (que suponemos ha pedido un bloqueo exclusivo sobre Q y debe esperar hasta que las otras transacciones en modo compartido liberen Q) nunca obtenga el bloqueo en modo exclusivo sobre Q. Ti nunca progresa y se dice que tiene inanición.
¿Cómo se puede evitar?
Se puede evitar concediendo los bloqueos del siguiente modo. Cuando una transacción Ti solicita un bloqueo sobre un elemento de dato en modo particular M, el gestor de control de concurrencia concede el bloqueo siempre que:
No exista otra transacción que posea un bloqueo sobre Q en un modo que esté en conflicto con M.
No exista otra transacción que esté esperando un bloqueo sobre Q y que lo haya solicitado antes que Ti.
De este modo una petición de bloqueo nunca se quedará bloqueada por otra petición solicitada más tarde.
- Protocolo de bloqueo en dos fases. Variantes: estricto y riguroso.
El protocolo de bloqueo en dos fases asegura la secuencialidad en cuanto a conflictos pero no la recuperabilidad ni la ausencia de interbloqueos. Exige que cada transacción realice las peticiones de bloqueo y desbloqueo en dos fases:
- Fase de crecimiento: una transacción puede obtener bloqueos pero no liberarlos.
- Fase de decrecimiento: una transacción puede liberar bloqueos pero no obtenerlos.
Inicialmente una transacción está en fase de crecimiento y adquiere los bloqueos que necesite. Una vez que libera un bloqueo, entra en fase de decrecimiento y no puede solicitar más bloqueos.
- Protocolo de bloqueo estricto: permite evitar los retrocesos en cascada. Exige que una transacción debe poseer todos los bloqueos en modo exclusivo que tome hasta que se complete. Esto asegura que todo dato que escribe una transacción no comprometida estará bloqueado en modo exclusivo hasta que se complete la transacción.
- Protocolo de bloqueo riguroso: exige que se posean todos los bloqueos hasta que la transacción se comprometa. Las transacciones se secuencian en el orden en que se comprometen.
¿Cómo se puede aumentar la concurrencia?
Para aumentar la concurrencia podemos permitir que se modifique el tipo de bloqueo durante la ejecución de la transacción.
¿Puede el sistema generar los bloqueos de forma automática o debe intervenir el usuario?
Existe un mecanismo llamado gestor de bloqueos que se implementa como un proceso que recibe mensajes de transacciones y envía mensajes como respuesta. De esta forma evitamos la intervención del usuario.
- Prevención de los interbloqueos. Describe las dos soluciones que conoces.
Existen dos enfoques.
Uno asegura que no puede haber esperas cíclicas ordenando las peticiones de bloqueo o exigiendo que todos los bloqueos se adquieran juntos. Se bloquean todos los elementos antes de que comience la ejecución de la transacción, bloqueando todos en un paso o ninguno. Otra posibilidad es imponer un orden parcial a todos los elementos y exigir que una transacción bloquee un elemento de datos sólo en el orden que especifica dicho orden parcial.
El segundo realiza retrocesos de las transacciones en lugar de esperar un bloqueo, siempre que el bloqueo pueda llevar potencialmente a un interbloqueo. Se utiliza la expropiación y retrocesos de transacciones. En la expropiación, cuando la transacción T2 solicita un bloqueo que la transacción T1 posee, el bloqueo debe expropiarse a T1 y concedérselo a continuación a T2. Para controlar la expropiación se asigna una marca temporal única a cada transacción que el sistema usa para decidir si debe esperar o retroceder.
Si una transacción retrocede mantiene su marca temporal antigua. Cuando vuelve a comenzar puede seguir dos esquemas:
- Esperarmorir: sin expropiación. Cuando Ti solicita un elemento de datos que posee actualmente Tj, sólo espera si tiene una marca temporal más pequeña que Tj. En otro caso retrocede (muere).
- Heriresperar: con expropiación. Cuando Ti solicita un elemento que tiene Tj, sólo puede esperar si tiene una marca mayor que Tj. En otro caso Tj retrocede. Siempre que retroceden las transacciones es importante asegurar que no haya inanición, es decir que ninguna transacción retroceda repetidamente y nunca se le permita progresar. El principal problema de ambos esquemas es que pueden producir retrocesos innecesarios.
- Detección y recuperación de interbloqueos.
Si el sistema no usa ningún protocolo que asegure la ausencia de interbloqueos, entonces se debe usar un esquema de detección y recuperación de interbloqueos. Periódicamente se invoca a un algoritmo que examina el estado del sistema para determinar si hay un interbloqueo, en cuyo caso el sistema debe:
Mantener información sobre la asignación de los elementos de datos de las transacciones, así como de toda petición de elementos pendiente.
Proporcionar un algoritmo que use esta información para determinar si el sistema ha entrado en interbloqueo.
Recuperarse del interbloqueo cuando el algoritmo determine que se ha entrado en ese estado.
Detección. Los interbloqueos se pueden describir mediante un grafo de espera G=(V, a) donde V son todas las transacciones del sistema. Cada arco Ti > Tj representa que Ti está esperando a que Tj libere un elemento de datos. Existe interbloqueo si y sólo si el grafo de espera contiene un ciclo. Toda transacción involucrada en el ciclo está interbloqueada.
Recuperación. Se deben realizar tres acciones:
1. Selección de una víctima. Se deben determinar las transacciones que van a retroceder, que deben ser aquellas que incurran en un coste mínimo. Para calcular el coste:
○ Lo que la transacción ha calculado y lo que queda
○ Número de datos que ha usado la transacción
○ La cantidad de elementos de datos que necesita la transacción para que se complete
○ Número de transacciones que se verían involucradas en el retroceso
- Retroceso. Debe determinarse hasta donde retrocede la transacción escogida. La solución más simple es el retroceso total, se aborta la transacción y vuelve a comenzar. Sin embargo es más efectivo el retroceso parcial; éste requiere que el sistema mantenga información adicional sobre el estado de todas las transacciones que están en ejecución.
- Inanición. Cuando, al estar basado en un sistema de costes, siempre es la misma transacción la elegida como víctima. Se suele incluir el número de retrocesos en el cálculo de costes para evitarlo.
- Operaciones de inserción y borrado. Borrado. Borra de la BD el elemento Q.
Casos en los que la operación de borrado entra en conflicto con otra instrucción: Sean Ii e Ij instrucciones de Ti y Tj que están consecutivas en la planificación S.
Sea Ii = borrar (Q):
- Ij = leer(Q). Están en conflicto. Si Ii está antes de Ij habrá un error lógico. En caso contrario la operación será exitosa.
- Ij = escribir(Q). Análogo al caso anterior.
- Ij = borrar(Q). Habrá siempre error lógico.
- Ij = insertar(Q). Están en conflicto. Si no existe Q previamente Ii primero producirá un error. Si existe Q e Ij primero habrá un error lógico.
Podemos concluir que:
- En el protocolo de dos fases se necesita un bloqueo antes de que se borre un dato.
- En el protocolo de ordenación por marcas temporales se debe hacer una prueba similar a la que se hacía con escribir.
Inserción. Inserta en la BD el elemento de datos Q y le asigna un valor inicial. Puesto que insertar(Q) asigna un valor al elemento de datos Q, se trata insertar de forma similar a escribir:
- En el protocolo de bloqueo de dos fases, si Ti realiza la operación insertar(Q) se concede a Ti un bloqueo exclusivo sobre Q.
- En el protocolo de ordenación por marcas temporales se fijan valores MTL(Q) y MTE(Q) a MT(Ti).
El fenómeno fantasma.
Las inserciones pueden producir el fenómeno fantasma en el cual hay un conflicto entre una inserción y una consulta, incluso si las dos transacciones no acceden a tuplas comunes. Tales conflictos no se pueden detectar si el bloqueo se ha hecho sólo sobre las tuplas a las que han accedido las transacciones. Se necesita bloquear la información acerca de qué tuplas pertenecen a la relación.
Soluciones para el fenómeno fantasma.
La solución más sencilla consiste en asociar un elemento de datos con la propia relación. Éste representa la información utilizada para encontrar las tuplas en la relación. Este elemento de datos se bloqueará, evitando que otras transacciones actualicen el valor acerca de qué tuplas pertenecen a la relación. El inconveniente principal es que esto conlleva un bajo nivel de concurrencia.
Una solución mejor es la técnica del bloqueo del índice. Toda transacción que inserte una tupla en una relación debe insertar información en cada uno de los índices que se mantenga en la relación. Esta solución exige bloqueos sobre ciertos cajones de índices. Estos bloqueos aseguran que todas las transacciones conflictivas están en conflicto por elementos de datos reales y no fantasma.
15. Niveles débiles de consistencia: grado dos, estabilidad del cursor. Consistencia de grado dos. El objetivo es evitar abortar en cascada sin asegurar necesariamente la secuencialidad. Utiliza los mismos modos de bloqueo que el protocolo de bloqueo en dos fases. La diferencia es que los bloqueos compartidos pueden liberarse y establecerse en cualquier momento. Los exclusivos no pueden liberarse hasta que la transacción aborte o se comprometa. La posibilidad de que se produzca inconsistencia hace que este enfoque no sea conveniente para muchas aplicaciones.
Estabilidad del cursor. Es una forma de consistencia de grado dos diseñada para programas escritos en lenguajes de propósito general, los cuales iteran sobre las tuplas de una relación utilizando cursores. En vez de bloquear toda la relación, la estabilidad del cursor asegura que:
- La tupla que está procesando la iteración está bloqueada en modo compartido.
- Todas las tuplas modificadas están bloqueadas en modo exclusivo hasta que se comprometa la transacción.
No se garantiza la secuencialidad. Se utiliza en la práctica sobre relaciones a las que se accede muchas veces como forma de incrementar la concurrencia. Se debe asegurar la consistencia.
Niveles débiles de consistencia en SQL: 1992.
Los niveles especificados por SQL92 son:
- Secuenciables. Es el predeterminado.
- Lectura repetible. Sólo permite leer los registros comprometidos y además requiere que, entre dos lecturas de un registro realizadas por una transacción, no se permita que ninguna otra transacción actualice el registro.
- Compromiso de lectura. Sólo se permite leer registros comprometidos, pero ni siquiera requiere lecturas repetibles.
- Sin compromiso de lectura. Permite incluso leer registros no comprometidos.