datos design database-design acid

design - ¿Cómo realizan las bases de datos la E/S atómica?



acid database (3)

Bases de datos como Oracle, SQL Server, etc. son muy buenos en integridad de datos. Si quisiera escribir un almacén de datos que sabía que almacenaría algunos datos o fallaría (es decir, sería ACID), usaría una base de datos como MySQL debajo como la tienda real porque esos problemas ya están resueltos.

Sin embargo, como un graduado no comp-sci, me pregunto cómo funciona realmente ACID en un nivel muy bajo. Sé que Oracle, por ejemplo, escribe datos en "registros de rehacer en línea" todo el tiempo, y luego realiza un "commit" en algún momento cuando la aplicación indica que la transacción debe estar comprometida.

Es esta etapa de "compromiso" que quiero ampliar y comprender. ¿Se trata simplemente de escribir "un byte más" en el disco, o cambiar un 0 a un 1 para decir que una fila determinada se ha almacenado correctamente?


Recuerdo una vez que encontrar los documentos de BerkeleyDB realmente útiles para tener una idea de cómo estas implementaciones pueden funcionar, porque esa era / era una base de datos de bajo nivel que implementaba transacciones sin toda la infraestructura de planificación relacional / de consulta.

No todas las bases de datos (incluso las que mencionas) funcionan de la misma manera. La implementación de nivel inferior de PostgreSQL es bastante diferente a la implementación de instantáneas de Oracle y SQL Server, aunque todas se basan en el mismo enfoque (MVCC: control de concurrencia de múltiples versiones).

Una forma de implementar las propiedades ACID es escribir todos los cambios que usted ("usted" aquí realiza algunas modificaciones) hacer en la base de datos un "registro de transacciones", así como también bloquear cada fila (unidad de atomicidad) para asegurar ninguna otra transacción puede mutar hasta que haya confirmado o revertido. Al final de la transacción, si lo hace, simplemente escriba un registro en el registro que indique que se ha comprometido y liberado los bloqueos . Si retrocede, necesita volver atrás en el registro de transacciones para deshacer todos los cambios, de modo que cada cambio escrito en el archivo de registro contiene una "imagen anterior" de cómo se veían originalmente los datos. (En la práctica, también contendrá una "imagen posterior" porque los registros de transacciones también se reproducen para la recuperación de fallos). Al bloquear cada fila que está cambiando, las transacciones concurrentes no ven sus cambios hasta que libere los bloqueos después de finalizar la transacción.

MVCC es un método por el cual las transacciones concurrentes que desean leer filas, en lugar de ser bloqueadas por su actualización, pueden acceder a la "imagen anterior" en su lugar. Cada transacción tiene una identidad y tiene una forma de determinar qué datos de transacciones puede "ver" y cuáles no: diferentes reglas para producir este conjunto se utilizan para implementar diferentes niveles de aislamiento. Por lo tanto, para obtener la semántica de "lectura repetible", una transacción debe encontrar la "imagen anterior" para cualquier fila que haya sido actualizada por una transacción que se inició después de ella, por ejemplo. Puede implementar esto ingenuamente haciendo que las transacciones miren hacia atrás a través del registro de transacciones para las imágenes anteriores, pero en la práctica están almacenadas en otro lugar: por lo tanto Oracle tiene espacios separados para rehacer y deshacer; rehacer es el registro de transacciones, deshacer antes de imágenes de bloques para transacciones simultáneas para usar; SQL Server almacena las imágenes anteriores en tempdb. Por el contrario, PostgreSQL siempre crea una nueva copia de una fila cada vez que se actualiza, por lo que las imágenes anteriores viven en los bloques de datos: esto tiene algunas ventajas (commit y rollback son operaciones muy simples, no hay espacio adicional para administrar) con tradeoffs (esas versiones de filas obsoletas deben ser aspiradas en segundo plano).

En el caso de PostgreSQL (y este es el DB con el que estoy más familiarizado) cada versión de fila en el disco tiene algunas propiedades adicionales que las transacciones deben examinar para decidir si esa versión de fila es "visible" para ellos. Para simplificar, considere que tienen "xmin" y "xmax" - "xmin" especifica el ID de transacción que creó la versión de fila, "xmax" el ID de transacción (opcional) que lo eliminó (lo que puede incluir crear una nueva versión de fila para representar una actualización de la fila). Entonces comienzas con una fila creada por txn # 20:

xmin xmax id value 20 - 1 FOO

y luego txn # 25 realiza la update t set value = ''BAR'' where id = 1

20 25 1 FOO 25 - 1 BAR

Hasta que se complete el txn # 25, las nuevas transacciones sabrán que sus cambios no son visibles. Entonces, una transacción que escanee esta tabla tomará la versión "FOO", ya que su xmax es una transacción no visible.

Si txn # 25 se retrotrae, las nuevas transacciones no saltan de inmediato, pero considerarán si txn # 25 cometido o revertido. (PostgreSQL administra una tabla de búsqueda de "estado de confirmación" para pg_clog esto, pg_clog ). Desde que txn # 25 se retrotrae, sus cambios no son visibles, por lo que nuevamente se toma la versión "FOO". (Y la versión "BAR" se omite porque su transacción xmin es invisible)

Si se confirma txn # 25, entonces la versión de fila "FOO" no se toma ahora, ya que su transacción xmax es visible (es decir, los cambios hechos por esa transacción ahora están visibles). Por el contrario, se toma la versión de la fila "BAR", ya que su transacción xmin es visible (y no tiene xmax)

Mientras txn # 25 todavía está en progreso (de nuevo, esto puede leerse desde pg_clog ) cualquier otra transacción que desee actualizar la fila esperará hasta que txn # 25 finalice, tratando de tomar un bloqueo compartido en la identificación de la transacción . Estoy destacando este punto, es por eso que PostgreSQL no suele tener "bloqueos de filas" como tales, solo bloqueos de transacciones: no hay un bloqueo en memoria para cada fila modificada. (El bloqueo usando select ... for update se realiza al establecer xmax y un indicador para indicar que xmax solo indica bloqueo, no eliminación)

Oracle ... hace algo similar, pero mi conocimiento de los detalles es mucho más complicado. En Oracle, cada transacción recibe un Número de cambio del sistema, y ​​eso se registra en la parte superior de cada bloque. Cuando un bloque cambia, sus contenidos originales se ponen en el espacio de deshacer con el nuevo bloque apuntando al bloque anterior: por lo tanto, esencialmente tiene una lista vinculada de versiones del bloque N, la última versión en el archivo de datos, con las versiones progresivamente anteriores en el tablespace de deshacer Y en la parte superior del bloque hay una lista de las "transacciones interesadas" que de alguna manera implementa el bloqueo (de nuevo, no se ha cambiado el bloqueo en memoria de cada fila), y no recuerdo los detalles más allá de eso.

Mecanismo de aislamiento de instantáneas de SQL Server, creo que es en gran medida similar a la de Oracle, utilizando tempdb para almacenar bloques que se están modificando en lugar de un archivo dedicado.

Espero que esta extraña respuesta sea útil. Todo proviene de la memoria, por lo que grandes cantidades de desinformación son posibles, particularmente para las implementaciones no postgresql.


Una descripción general de alto nivel para Oracle:

Cada sesión de Oracle es única, y cada sesión puede tener 1 * transacción activa. Cuando comienza una transacción, Oracle le asigna un número de cambio de sistema (SCN) monótonamente creciente. A medida que Oracle actualiza / inserta / elimina filas, Oracle bloquea las filas de interés en la tabla y el índice de respaldo actualizando el encabezado en los bloques que se escriben y guardando los bloques "originales" en el espacio de reversión (deshacer) de Oracle. Oracle también escribe entradas de registro de rehacer en un búfer de memoria que describe los cambios que se están realizando tanto en los bloques de tabla e índice como en los bloques de deshacer. Tenga en cuenta que los cambios que se realizan se realizan en la memoria, no directamente en el disco.

Al comprometerse, Oracle se asegura de que todo el almacenamiento intermedio de registro hasta e incluyendo el SCN para la transacción se haya escrito en el disco antes de devolver el control de la transacción al cliente.

Al retroceder, Oracle usa la información en la reversión (deshacer) para eliminar los cambios realizados.

Entonces, ¿cómo implementa esto ACID?

Atomicity: Mi sesión, mi transacción, todo se va o nada de eso va. Cuando me comprometo, no puedo hacer nada hasta que termine el compromiso.

Consistencia: Oracle verifica que las fechas sean fechas, los datos de caracteres son datos de caracteres, los números son válidos. Lo mismo con restricciones de verificación. Las restricciones de clave externa se basan en la comprobación para asegurarse de que la clave principal a la que se hace referencia es válida, y no ha sido actualizada o eliminada por una transacción en vuelo. Si la clave principal se ha actualizado o eliminado, su declaración se bloquea, en realidad está en el limbo, esperando que la declaración que afecte al registro principal se comprometa o retrotraiga.

Independencia: ¿Recuerdas esos números de cambio de sistema? Si no está haciendo cambios, Oracle sabe qué es SCN en el momento en que inicia un enunciado o declara un cursor. Por lo tanto, si tiene una declaración de larga duración en la que los datos cambian debajo de usted, Oracle verifica que los datos SE COMPROMETIERON cuando su extracto comenzó a ejecutarse. Es un control de coherencia de varias versiones, y es bastante complicado. Oracle no implementa todos los niveles de aislamiento requeridos por varios estándares SQL; por ejemplo, Oracle nunca permite lecturas sucias o fantasmas.

Durablilidad: el búfer de registro de rehacer que se va a enjuagar al disco es el nivel de la raíz de la durabilidad. Cuando un archivo de registro de rehacer está lleno, Oracle fuerza un punto de control. Este proceso hace que Oracle escriba todos los bloques modificados de tabla e índice fuera de la memoria en el disco, independientemente de si se han confirmado o no. Si la instancia falla y los datos en los archivos de datos contienen cambios no confirmados, Oracle usa los registros de rehacer para deshacer esos cambios, ya que la información de deshacer también está contenida en el registro de rehacer.

* Ignore las transacciones autónomas por el momento, porque son una complicación grave.


Ayende me sugirió en Twitter que mire a Munin , el mecanismo de almacenamiento de datos que está usando para RavenDB y Raven MQ .