Grimpi IT Blog

febrero 24, 2011

Breve introducción de Read Uncommitted a Snapshot Isolation

Filed under: MVCC, SQL Server — grimpi @ 6:24 pm

Otras de las dudas muy habituales para los iniciados (y no tan iniciados) en SQL Server (y también en cualquier desarrollador de cualquier motor de base de datos) es que tipo de niveles de bloqueos existen (isolation level), para que sirven, en que caso usarlos y que desventajas tiene su uso.
Por lo tanto, decidí hacer un muy breve resumen de los distintos niveles existentes. Si bien, este post especifico esta enfocado en SQL Server, conceptualmente, puede servir para cualquier motor. Los niveles de aislamiento están especificados en el estandar ANSI.

Read Uncommitted: A veces llamado “lectura sucia” o “diry read”. Una transacción ejecutada en este nivel de aislamiento, puede leer datos que están siendo modificados por otra transacción concurrente. Ejemplo: Transacción T2 hace un update sobre el registro R1, sin embargo, la transacción T1 puede leer el registro R1 modificado, a pesar de que T2 potencialmente puede hacer un roll back sobre el mismo.

  • Ventajas: No se necesita hacer un lockeo para leer datos, aunque si para poder modificarlos.
  • Contras: No se puede garantizar la consistencia de los datos leídos. Puede darse el caso de que sea lea un dato que luego no exista más.
  • Uso habitual: En situaciones de alta concurrencia donde la potencial inconsistencia de datos sea tolerada.

Read Committed: Una transacción ejecutada en este nivel de aislamiento puede leer solamente datos commiteados. Por ejemplo, si una transacción concurrente T2 hace un update sobre el registro R1, la transacción T1 no podrá acceder al mismo y va a quedar bloqueada hasta que T2 haga un commit o un rollback sobre el registro.

  • Ventajas: Buen balance entre concurrencia y consistencia.
  • Contras: Mayor nivel de bloqueo.
  • Uso habitual: Es el isolation level mas común, de hecho, el que se usa por defecto en SQL Server.

Repeatable Read: Una transacción ejecutada en este nivel de aislamiento solo puede leer información comiteada con la garantía de que mientras dure la transacción, nadie va a poder leer esos datos. Esto significa que si vuelvo a leer esos datos dentro de la transacción, tengo total seguridad de que el resultado va a ser exactamente el mismo, ya que están bloqueados para la escritura en otra transacción.
Ejemplo: Asumamos que tenemos la tabla Cuentas que guarda el balance monetario de los clientes. Empezamos la transacción T1 con filtrando todos los balances cuyo monto sea mayor a 1000. Supongamos que nos devuelve 10 registros. Ahora empieza otra transacción T2, que inserta un nuevo registro cuyo balance es de 1020 y comitea. Si consultamos nuevamente en la transacción T1 todos los balances mayores a 1000, ahora nos va a devolver 11 registros. Esto se debe a que la transacción T1 bloqueó el conjunto de registros seleccionados en la consulta, pero no el predicado del filtro.
Ahora, si dentro de la transacción T2 intento eliminar todos los registros cuyo balance sea mayor a 1000, va a ocurrir un bloqueo, ya que están lockeados por la transacción T1.

  • Ventajas: Alta consistencia en los datos.
  • Contras: Mayor nivel de bloqueos y disminución del grado de concurrencia.
  • Uso habitual: En operaciones bancarias o financieras que requieran un grado de exactitud muy alto.

Serializable: Una transacción T1 ejecutada en este nivel ofrece el mayor grado de consistencia posible, eliminado los registros fantasmas. El motor puede llegar a bloquear incluso la tabla entera, para garantizar la máxima consistencia de datos.

  • Ventajas: Consistencia de datos absoluta.
  • Contras: Altísimo grado de bloqueo y muy bajo nivel de concurrencia, ya que las tablas pueden ser bloqueadas en su totalidad mientras se realiza una transacción.
  • Uso habitual: No es muy habitual. Casos muy raros cuando se requiera una gran consistencia de datos.

Adicionalmente, existen nuevos niveles de aislamiento a partir de SQL Server 2005.

Read-Committed-Snapshot : Este no es un nuevo nivel de aislamiento, sino una reimplementación del read comitted pero sin bloqueos.
SQL Server implementa versionado de registros mediante copias (snapshots) para evita el bloqueo.

  • Ventajas: Acceso no bloqueante a datos consistentes.
  • Contras: Mayor overhead para mantener el versionado de datos.
  • Uso habitual: Minimar los bloqueos lectura/escritura y el uso de el hint NOLOCK.

Snapshot Isolation: Ya habíamos hablado acá sobre este nivel de lockeo. Una transacción bajo este nivel de aislamiento, trabaja sobre una copia (o “snapshot”) de la base de datos. Cuando finaliza la transacción, hace un merge entre la copia y la base de datos.

  • Ventajas: Mayor nivel de consistencia incluso que un “repetable red”, con mayor nivel de concurrencia, ya que no realiza lockeos.
  • Contras: Mayor overhead para mantener las distintas versiones de un conjunto de datos, lo que impacta en la performance.
  • Uso Habitual: Cuando se necesita obtener información muy consistente y en entornos de alta concurrencia.

Recordemos que para activar especificar el nivel de aislamiento, se debe ejecutar SET TRANSACTION ISOLATION LEVEL <ISOLATION>

febrero 22, 2011

Link recomendado: Stairway to SQL Server

Filed under: Links, SQL Server — Etiquetas: , — grimpi @ 9:07 pm

Para quienes estén interesados en SQL Server, recomiendo y mucho este link del excelente portal SQL Server Central. La idea del “Stairway to SQL Server” es hacer una serie de tutoriales diseñados para pasar de conocimiento cero sobre un determinado tema de SQL Server, a un nivel de conocimiento profundo que permita empezar a utilizar esa característica en un entorno de producción.
Por el momento estos son los temas tratados (aunque se van a agregar nuevos):

  • SQL Server Agent
  • Server-side Tracing
  • SQL Server Indexes
  • Transactional and Merge Replication
  • Database Design
  • MDX
  • Integration Services
  • Reporting Services
  • StreamInsight

Realmente lo recomiendo.
Para poder entrar, hay que registrarse en el portal.

febrero 8, 2011

Control de concurrencia multiversión MVCC

Filed under: Engine, MVCC, PostgreSQL, SQL Server — grimpi @ 8:08 pm

Antes de explicar que es MVCC, hay que aclarar dos conceptos muy importantes: bloqueo pesimista y bloqueo optimista. El bloqueo optimista supone que no se va a hacer nada en el código de la aplicación que imponga explícitamente bloqueos de los recursos cuando se esté trabajando con ellos. Mientras que por otro lado, el bloqueo pesimista supone una intervención por parte de la aplicación como gestor del bloqueo.
Para ser más simples: El enfoque optimista delega en la base de datos el bloqueo y manejo de datos, mientras que el bloqueo pesimista, la aplicación es la encargada de gestionar la concurrencia.
MVCC (Multi version concurrency control) es una técnica de concurrencia optimista en donde ninguna tarea o hilo es bloqueado mientras se realiza una operación en la tabla, porque el otro hilo usa su propia copia (versión) del objeto dentro de una transacción.
Si bien obviamente la implementación interna de este algoritmo es distinta en cada motor de bases de datos, a grandes rasgos se podría decir que el MVCC internamente lo que hace es identificar cada transacción con un numero univoco y a cada registro de la tabla, con un numero de versión. Entonces, cada transacción trabaja con su copia y si se modifica un registro de una tabla, el contador de versión del registro se incrementa. Cuando se comitea, la copia del objeto reemplaza a la que existía en la base de datos. Si 2 transacciones modificaron la misma tabla, se hace un mergue de ambas tablas combinando las últimas versiones de cada registro. Si las 2 transacciones, modificaron exactamente el mismo registro, entonces en ese caso, cuando se commitee, el registro que finalmente queda, corresponde a la ultima transacción en realizar una modificación sobre ese registro.
De esta manera se logra que una lectura no bloquee una transacción de escritura y que una transacción de escritura tampoco bloquee una transacción de lectura. Para ser más claros, cuando hago un SELECT, puedo simultáneamente hacer un UPDATE y cuando hago un UPDATE puedo hacer simultáneamente un SELECT, algo que en el isolation habitual de las bases de datos, no es posible.
Al no existir ningún lockeo ni espera por parte del proceso, en determinadas situaciones concurrentes de fuerte escritura donde la información es modificada frecuentemente y de manera concurrente por muchos usuarios, se logran un gran mejoramiento de la performance del sistema. Es muy habitual usar este tipo modelo de concurrencia para evitar los famosos “deadlocks” que suelen ocurrir en bases de datos con mucha demanda.

Sin embargo, MVCC no debe ser usado en cualquier ocasión, ya que tiene un overhead muy importante. Todo el manejo de versionado tiene un alto costo, ya que las versiones de registros se copian y almacenan en tablas o estructuras físicas temporales que luego se descartan (por ejemplo, para guardar las copias, SQL Server usa la tempdb, mientras que Oracle usa el rollback segment). En SQL Server este overhead es un poco alto, por lo tanto solo en escenarios específicos conviene usar este modelo de concurrencia. A medida que las operaciones de escritura sobre la base de datos cobran relevancia sobre las operaciones de lectura, y por otro lado, nuestras lecturas son largas, los beneficios de usar MVCC aumentan, ya que reducimos los bloqueos.
Es fundamental la existencia de un alto grado de paralelismo en nuestra aplicación. MVCC es por definición, un modelo muy escalable y donde mejor se ven sus ventajas, es en escenarios de alta demanda.

Escenarios típicos:
• Aplicaciones que procesan transacciones en línea en gran escala.
• Aplicaciones mixtas que usan reportes contra datos sobre tablas en línea.
• Aplicaciones donde se ejecutan consultas largas mientras que simultaneamente se realizan operaciones sobre ella.

Escenarios donde no generalmente no debería usarse MVCC:
• Aplicaciones de datawarehousing que extraen información sobre tablas históricas donde no hay escritura.
• Aplicaciones que procesan datos en línea con poca concurrencia.
• Aplicaciones realtime, donde la velocidad es mas importante aun que la consistencia de información.

Todas las bases de datos modernas implementan actualmente este algoritmo de concurrencia optimista. En SQL Server se llama SNAPSHOT ISOLATION, en MySql se llama InnoDB Multi Version, en Firebird MGA y en Oracle y PostgreSQL, simplemente MVCC.

Links:
http://msdn.microsoft.com/en-us/library/ms345124(v=sql.90).aspx
http://www.codinghorror.com/blog/2008/08/deadlocked.html
http://www.rtcmagazine.com/articles/view/101612

febrero 2, 2011

Diferencias entre Unique Index vs Unique Constraint

Filed under: SQL Server — grimpi @ 3:30 pm

La otra vez tuve una discusión con un cliente que me inspiró a hacer este post. ¿Cual es la diferencia entre una constraint UNIQUE y un índice único? Pregunta bastante común para los iniciados en bases de datos.
Y la respuesta a esta pregunta, es que en el 90% de los casos, a efectos prácticos, es lo mismo. La diferencia es más conceptual que técnica.
Tanto una constraint de este tipo como un índice único, restringen los valores de una o varias columnas, impidiendo que se repitan valores. Cuando creamos una UNIQUE constraint, internamente se crea un índice sobre esa columna.
Veamos el siguiente ejemplo:

CREATE TABLE Emp (Id int, Name varchar(20), Email varchar(50))
GO

ALTER TABLE Emp ADD CONSTRAINT IX_Emp UNIQUE (Name)
GO

Ahora consultemos a las vistas de sistema para ver que pasó.

SELECT * FROM sys.indexes WHERE name = ‘IX_Emp’
GO
 
  

 

 

 

Vea que SQL Server crea automáticamente este índice nonclustered. Está marcado como unique constraint y se le da el mismo nombre que a nuestra constraint de la tabla.

El tema es que una constraint es vista como un elemento y requisito del negocio plasmado en la tabla, mientras que un índice único es una característica interna del motor de bases de datos, pero externa a las reglas de negocio de la aplicación. La diferencia es conceptual, a nivel técnico y de performance es lo mismo, pero hace mas clara la auto documentación de la bases de datos, definir una constraint por sobre un índice único.

Sin embargo, hay una diferencia teoría técnica fundamental. Una constraint de tipo UNIQUE sobre una o varias columnas, es una restricción que aplica a toda la tabla. En cambio, un índice único, la restricción aplica solamente sobre el conjunto de registros implicados en el índice. Como en la mayoría de los casos, siempre creamos índices sobre toda la tabla, no hay diferencias, pero hay varias situaciones particulares, donde no es lo mismo:

  • Foreign key: Puedo definir una constraint UNIQUE sobre una columna y usar esa columna como foreign key de otra tabla. Pero no se puede hacer lo mismo con un índice único.
  • Índices filtrados: A partir de SQL Server 2008, es posible definir un índice no por toda la tabla, sino por un conjunto restringido de registros. Si definimos a ese índice como único, esta restricción aplicaría entonces solamente al conjunto de registros implicados en el índice, no a toda la tabla.
    Esta opción nos podría ayudar a resolver la limitación de SQL Server de que solo podamos tener una clave NULL en una restricción de unicidad.
  • Vistas indexadas: Es una situación parecida a la del índice filtrado. Podría darse el caso de tener columnas cuyos valores se repiten, una está dentro de la vista y la otra no, por lo tanto, no se podría usar una constraint única.
    También podría darse el caso de que queremos unicidad en una columna dentro de un conjunto de varias tablas.
    Podríamos crear un índice único sobre una vista que agrupe a esas tablas. Con una constraint no podríamos hacerlo, porque la restriccion solo aplica a una sola tabla.
  • Performance: También hay cuestiones de performance muy finas que en situaciones particulares, hacen más conveniente usar un índice sobre una constraint.
    Por ejemplo, si bien, como ya dijimos antes, cuando creamos una UNIQUE, se crea un índice, este índice es interno, y las posibilidades de asignarle opciones del índice cuando es creado son mucho mas limitada. Podemos decirle con que FILLFACTOR debe ser creado ese índice pero por ejemplo, no podemos crear una constraint UNIQUE cuyo índice tenga la opción INCLUDE COLUMN habilitada. También cuando crea una restricción única, no hay forma de especificar que el índice creado para soportarlo sea ascendente o descendente. Existen algunas (extrañas) situaciones, donde esto podría interesarnos.

Ejemplo:
 
 CREATE TABLE Emp (Id int, Name varchar(20))
 GO

 ALTER TABLE Emp
  ADD CONSTRAINT IX_Emp UNIQUE (Name) WITH (FILLFACTOR = 20) INCLUDE (Email);
 GO 

Si corremos este script, nos va a tirar error. Ya que SQL Server no permite la opcion INCLUDE cuando creamos una UNIQUE constraint.

Resumiendo:
En la mayoría de los casos es lo mismo usar una u otra. Pero es preferible usar una constraint por sobre un índice, porque ayuda a documentar mejor las reglas de negocio de la base, pero por otro lado, usar un índice único nos permite hacer un tunning mas fino sobre la tabla.

Crea un blog o un sitio web gratuitos con WordPress.com.