PRIMERA PARTE
Cuando de ADO se trata un lugar común para muchos de nosotros es decidir, al momento de analizar estrategias de acceso a la data, que proveedor usar?, en que lugar correrá el cursor; del lado del server? (Server side cursor?) o del lado del cliente (Client side cursor), que tipo de Cursor (Forward-Only, Static, Keyset o Dinamic?)… y por si fuera poco, aun nos falta decidir que tipo de Bloqueo usar (Locking ) (read only, pessimistic, optimistic o batchoptimistic?).
Todos los autores coinciden en que la decisión no es simple y es mucho mas un problema de análisis del sistema que una aplicación matemática.
Vamos por partes:
Que es un «CURSOR» para ADO?
Un CURSOR en términos de bases de datos representa la manera de tratar un numero discreto de registros en forma de filas de data. El cursor le permite a uno moverse a través del set de registros y posicionarse en uno de ellos para acceder a su data.
Al momento de elegir el cursor tenemos que decidir el lugar donde este correrá (Cursor location), el comportamiento del mismo en términos de moverse a través del RecordSet (tipo de cursor) y su habilidad para permitir al usuario alterar la data original y la manera de hacerlo para evitar conflictos (Bloqueo o «Locking»).
Cursor Location. (Lugar donde correrá el cursor)
Para determinar el lado donde correrá el cursor del RecordSet se deberá implementar la propiedad CursorLocation del objeto RecordSet. Si no se especifica el RecordSet tendrá por «default» el valor que tenga la propiedad CursorLocation del objeto Connection que usara el RecordSet. El valor «default» (por defecto) de la propiedad CursorLocation del objeto Connetion es adUseServer (cursor del lado del server), que no siempre es el optimo.
Hay cuatro posibilidades de elección, pero nos enfocaremos en dos de ellos, ya que los otros dos son obsoletos, y a decir de MS se ha mantenido para efectos de compatibilidad con anteriores versiones
1 – adUseNone (obsoleto)
2 – adUseServer (default)
3 – adUseClient
3 – adUseClientBatch (obsoleto, pero no entiendo por que tiene el mismo numérico que anterior…no importa :-))
Entonces nos enfocaremos en los dos del medio.
Client-Side Cursors (3 – adUseClient, Cursores del lado del cliente)
Usa los recursos (memoria, CPU, etc.) de la maquina local (la del cliente), para implementar el cursor y su set de records.
Ventajas
– Dado que corre en la maquina del cliente la performance será mejor si se trata de Recordset de tamaño razonable (depende de la configuración, básicamente de la memoria de la maquina para definir «razonable»).
– También la escalabilidad del sistema se ve favorecida dado que la performance depende de cada maquina cliente y no del server, en consecuencia los cursores del lado del cliente exige menos demanda del server cuando el numero de clientes crece que cuando se usa el cursor del lado del Server (Server-side Cursor).
– Es el único cursor que permite usar recordsets desconectados (de especial uso en N Tier systems).
Desventajas:
– Para recordset muy extensos (otra vez , extensos para una maquina cliente dependerá de sus recursos), la estación de trabajo podría tener problemas para manejar grandes volúmenes de data.
– Dado que el cursor del lado del cliente tiene que traer la data a través del network, recordsets grandes significaran un alto consumo del trafico del network. Esto es especialmente un problema cuando se usa la internet.
– No todos los «proveedores» manejan bien los campos auto numéricos o Identity field (el proveedor de Jet si lo hace muy bien)
Server-side Cursors (2 – adUseServer (default), cursores del lado del server).
Usa los recursos del server para implementar el cursor y su set de records.
Ventajas
– Las maquinas del cliente o estaciones de trabajo no se verán en peligro con grades porciones de recordsets ya que el cursor es manejado totalmente el el server. (thin client)
– Dado que no toda la data es transferida al cliente el trafico del network no se ve sobrecargado.
– Para recordsets que se prevén muy grandes es mejor manejarlos de esta manera
Desventajas
– Para recordsets pequeños es ineficiente ya que todo pedido de movimiento tiene que se hecho a través del network, en tal caso es mejor transferir todo el recordset al cliente (usando cliente-side cursor).
– Se presentan problemas de escalabilidad, mientras mas usuarios se incremente, el sistema mas carga significara para el server, pudiendo de esta manera rápidamente consumirse los recursos del server.
– Problemas de concurrencia a la data, exige un mejor manejo de las conexiones. (se hace necesario el manejo del «pool» de conexiones y el uso de programas como el MTS).
SEGUNDA PARTE
En esta segunda parte escribiré sobre los cuatro tipos de Cursores que ADO maneja, sus comportamientos y algunas otras consideraciones que pienso pertinentes tener en cuenta.
Tipo de Cursor (Curso type), en términos de ADO, indica el comportamiento que tiene dicho Cursor, lo que se puede o no hacer con el y hasta se puede saber cuan liviano o pesado es en términos de los recursos del sistema.
Se puede definir el tipo de cursor a usar en un recordset, especificando la propiedad CursorType antes de abrir dicho recordset (no se puede cambiar el tipo de Cursor luego de que el recordset haya sido adquirido). El siguiente seria un ejemplo:
MiRecord.CursorType = adOpenOptimistic
MiRecord.Open
Existe, como ya dijimos en la primera parte, cuatro tipo de cursores en ADO:
Forward-Only, Static, Keyset y Dynamic.
Forward-Only. (adOpenForwardOnly)
Este tipo de cursor se comporta mas como un archivo secuencial y solo puede manipularse en la maquina del cliente de uno en uno y en estricto orden secuencial desde el primer record hasta el ultimo.
Vale decir que no se puede saltar records ni regresar a uno anterior, la única manera de moverse es de uno en uno y partiendo del primer record hasta el ultimo.
Para enfatizar lo anterior debe pensarse que el único método, respecto a movimiento entre registro, que se puede usar es MoveNext, cualquier otro intento generara un error (runtime error).
Cursor tipo Forward-Only es el cursor por default por que es el mas económico en términos de recursos, sin embargo no esta disponible para Cursores en el lado del Cliente (Client Cursors).
MyRecord.CursorType = adOpenForwardOnly
Nota: Traten de usar este tipo de cursor si lo que necesitan solamente es mostrar datos (en grids o en reportes). una vez «poblados» sus variable u objetos entonces cierren el recordset para «liberar» recursos (incluso si pueden cerrar la conexión, háganlo).
Static (adOpenStatic)
Es menos «económico» que Forward-Only pero tiene una gran flexibilidad en términos de movimiento a través del recordset. Podemos movernos por el recordset en todas las direcciones de uno en uno o saltar records, hacer filtros, búsquedas, «bookmarks».
Static es el único cursor soportado por Cursores en el lado del Cliente (Client side Cursors),.
Una desventaja muy grande para este tipo de cursores se presenta en el hecho de que cualquier cambio hecho por otro usuario (o programa corriendo al mismo momento) no se ve reflejado en el recordset.
Esto es especialmente importante en ambientes multiusuarios donde, asumiendo que, por ejemplo, un usuario «X» abre un recordset con cursor «static» del lado del cliente, no le será posible ver los cambios hechos por un usuario «Y» desde otra maquina, ya sean modificaciones adiciones o borrados (deletes) de dichos records. Entonces al momento de hacer Update el usuario X podría tener colisiones o conflictos que deberá manejarlos mediante código (atrapando errores)
Nota: dos propiedades de el objeto Field de ADO ayudan a manejar este tipo de conflictos (Field.OriginalValue y Field.UnderlyingValue), el primero representa el valor del campo cuando se solicito el record (sin considerar cualquier cambio que el usuario haga) mientras el segundo (y mas importante a lo hora de verificar cambios) representa el valor del campo en la misma base de datos incluyendo cualquier cambio que otro usuario haya hecho en el interin.
Hay además un par de maneras mas solucionar estos conflictos uno de ellos es capturar los errores en el momento de hacer UpdateBatch. si un error ha ocurrido entonces podrán hacer un filtro del recordset usando adFilterConflictingRecords que ver cuales de los registros han presentado problemas.
Keyset (adOpenKeyset)
Este tipo de cursores tienen la misma flexibilidad de «Static cursor» para el movimiento. Adicionalmente cualquier cambio hecho por otros usuarios a los records serán inmediatamente visibles por recordset activo; pero no se verán adiciones o borrados (deletes) hechos por otros usuarios.
Es obvio saber que un cursor de este tipo es mucho mas «costoso» en términos de recursos de sistema, si a esto agregamos el hecho que este tipo de cursor solo puede ser usado en Cursores del lado del Server (Server side cursor) entonces tendremos un problema en términos de «escalabilidad» ya que pocos usuarios podrían causar «atoros» en el server.
Dynamic (adOpenDynamic)
Caramba, por favor traten de evitarlo… :-), en verdad es muy «pesado».
Tiene toda la flexibilidad del cursor tipo Keyset, con la ventaja de que se pueden «ver» además los borrados (deletes) o adiciones hechos por otros usuarios en el recordset.
Traten de evitarlo pues en términos de «escalabilidad» es una calamidad.
TERCERA PARTE (ultima)
En esta tercera y ultima entrega escribiré sobre las diferentes estrategias de bloqueo (locking strategies) que reconoce ADO para poder garantizar la integridad de la data.
También, al finalizar, hare un resumen sobre algunos criterios que se pueden tener al momento de elegir un Cursor para el recordset (su lado, su tipo y su estrategia de bloqueo, «CursorLocation», «CursorType» y «LockType»).
Estrategias de bloqueo (Locking Strategies)
En lo que refiere a ADO existen cuatro tipos de estrategias de bloqueo: Read-Only (solo para lectura), Pessimistic, Optimistic y Batch-Optimistic. (la propiedad LockType del objeto recordset debe ser establecida antes de abrir el recordset)
Ejemplo:
MiRocord.LockType = adLockReadOnly
MiRecord.Open
Las estrategias de bloqueo nos ayudan a garantizar la integridad de la data en el momento de actualizar la misma después de manipularla mientras esta a nuestra disposición a través de un recordset. Sobre todo nos permitirá elegir la estrategia que consideremos adecuada para evitar conflictos de data si es que dos o mas usuarios tratan de acceder y manipular la misma data al mismo tiempo.
Pintando esto un poco mas imaginemos que el vendedor A esta haciendo la venta de 3 unidades del producto X que en inventario se le presentan 5 unidades; por otro lado el vendedor B también esta haciendo una venta del producto X y necesita 4 unidades; aquí claramente hay un problema de «timing», si ambos leen que hay 5 unidades en algún momento habrá un conflicto de cantidades si ambos llegan a finalizar la venta. Las diferentes estrategias de bloqueo nos permitirán manejar este tipo de circunstancias de
una manera relativamente simple.
Read-Only (adLockreadOnly)
Cuando un cursor es abierto con esta opción entonces el usuario no podrá alterar ni adicionar ni borrar ninguno de los registros del recordset. Esto obviamente garantiza que no habrá conflictos con cualquier manipulación que otro usuario pueda hacer a la data en ese momento.
Este tipo de bloqueo es muy útil cuando uno esta seguro que no se alterara la data (solo para consulta). Una combinación de este tipo de bloqueo con un cursor tipo Forward-Only producirá el cursor mas «económico» disponible en términos de recursos (solo puede Cursor ser en el lado del Server). Usado ampliamente para consultas de data.
Pessimistic (adLockPessimistic)
Este tipo de bloqueo garantiza que ningún otro usuario podrá manipular la data presente en nuestra recordset mientras este se encuentra abierto. Este tipo de estrategia, si bien nos evita manipular conflictos puede ser un poco peligroso si un cliente deja abierto del recordset por mucho rato de tal manera que nadie mas pueda acceder a esa data y manipularla. (solo disponible el cursores del lado del server)
Optimistic (adLockOptimistic)
No hay ninguna garantía que el record en edición no presentara conflictos al momento de hacer update (hay que usar las estrategias de captura de conflictos indicados en la segunda parte). El record solamente será bloqueado mientras este en proceso de Update.
Batch Optimistic (adLockBatchOptimistic)
Para updates hechos con el método updateBatch del objeto recordset. Todos los cambios hechos al recordset se capturan en un cache desde el momento que se abre el recordset con esta estrategia de bloqueo; al momento de hacer UpdateBatch hay que analizar la colección de errores del objeto conexión (Connection Object) para capturar los conflictos que hayan ocurrido en el momento del Update.
ESCOGIENDO UNA OPCION DE CURSOR.
Como hemos visto a través de estas tres entregas la elección de un cursor adecuado dependerá básicamente de un buen análisis de las aplicaciones que tendrá el sistema que se esta desarrollando.
Sera Monousuario?.. si es así, se ampliara a sistemas multiusuario?, de ser multiusuario, habrán muchos usuarios que va a acceder a la misma data a la vez?, será un sistema 2 Tier (dos capas?) o será uno de N-tier (N capas).
También podríamos no usar ningún tipo de Cursor en particular (que tal si usamos un sistema Client/server, donde todo la manipulación de la data se hace en el server o en un server que se dedique a eso «Data server»).
En cualquier caso, lo que debemos tener en cuenta a la hora de elegir un cursor es Construir un cursor que use la menor cantidad de recursos posibles de todo el sistema y que aun sea útil, que aun haga el trabajo bien.
Recuerden siempre que el mas económico de todos es el que combina Forward-Only con bloqueo Read-Only y corre del lado del servidor. Traten en lo posible de usar este tipo de cursores a partir de ahí y si necesitan mas recursos traten de usar otras alternativas que sean siempre económicas y hagan bien su trabajo.
Adicionalmente, yo recomendaría usar en lo posible Store Procedures (SP) y encargar al server todo el trabajo de actualización de data, incluyendo el manejo de posibles conflictos.
Hasta acá termino el presente articulo que espero sirva a algunos de nosotros como partida para entender un poco mas los diferentes «sabores» de ADO.
Excelente información muy completa gracias
Buenisimo! Gracias por la data crack 😉