Escribo esto para ver si se clarifican un
poco las ideas por ahí, ya que últimamente he visto unas preguntas sobre el tema
con unas contestaciones muy poco veraces y que dejaban muy mal sabor de
boca.
Es por todos bien sabido los problemas que
trae el bloqueo de registros, so pena de que sin bloquear también se tienen
problemas. Es por ello que no voy a entrar en polémicas de si se debe o no
realizar bloqueos, cada cual sabe qué desarrolla y bajo qué política de empresa
trabaja.
Hacendo un poco de memoria, hasta antes del
Jet 2.0 el bloqueo se realizaba por páginas de 4 kb cosa que cambió y se quedó
en páginas de 2 kb. En el Jet 4.0 (Access 2.000) por temas de carácteres Unicode
(cada uno mide 2 bytes) el tamaño ha aumentado de nuevo a páginas de 4 kb. El
tamaño de página de SQL Server no sabría asegurarlo (debería mirar mi
documentación), pero es parecido. Hasta la versión 7 de SQL Server el bloqueo se
realizaba por páginas, en esta versión ya se puede optar al bloqueo a nivel de
registro, exactamente igual que el Jet 4.0, en el cual también se pueden
realizar bloqueos a nivel de registro (véase en Access 2.000 Herramientas /
Opciones / Avanzadas / Abrir bases de datos usando bloqueo por
registros )
La técnica para realizar un bloqueo
pesimista a nivel de registro difiere para Access y SQL Server. Voy a explicar
la forma de realizarlos en ambos usando VB6 con ADO2.5.
Cómo bloquear a nivel de registro en bases de datos
Access
La base de datos ha de estar en formato Jet
4.0 (Access 2.000), las versiones anteriores sólo soportan bloqueo a nivel de
registro y no es suficiente con usar el proveedor
Microsoft.Jet.OLEDB.4.0, han de ser ambas cosas. Por defecto
Access 2.000 abre las bases de datos con bloqueo a nivel de registro, en contra
de cómo lo hace ADO 2.5 y el proveedor Jet 4.0; para ello debemos establecer la
propiedad Jet OLEDB:Database Locking Mode en la conexión con el
valor 1, además de otros parámetros cómo especificar un nivel
de isolación alto y cursores de lado del servidor. Como nota aclaratoria decir
que si una aplicación abre la base de datos con nivel de bloqueo por página las
siguientes aperturas concurrentes se realizarán con este tipo de
bloqueo; así que cuidado.
Ejemplo de
conexión:
Dim cn As ADODB.Connection
Set cn = New
ADODB.Connection
cn.Provider =
"Microsoft.Jet.OLEDB.4.0"
cn.Properties("Data Source").Value =
"MiBase.mdb"
cn.Properties("Jet OLEDB:Database Locking Mode").Value =
1
cn.CommandTimeout =
10 '
Tiempo de espera
cn.CursorLocation = adUseServer
cn.IsolationLevel
= adXactChaos
cn.Open
Ahora bien, ¿cómo bloqueo un registro? Tan
sencillo como abrir una transacción, localizar el registro en cuestión y ponerlo
en modo de edición. Para ello deberemos utilizar recordset's en modo pesimista,
y cuidado, porque todo lo que modifiquemos durante la transacción quedará
bloqueado.
Ejemplo de
bloqueo
En este ejemplo es un fragmento
perteneciente a un mantenimiento de clientes, en este se utilizan dos
recordset's: rsClientes sobre el cual previamente se realizó un selección de
registros y es por donde se va moviendo el usuario con los botones de
movimientos y rsAux que me permite bloquear el registro cuando el usuario pulsa
el botón para modificarlo.
' Intentar un bloqueo
On Error Resume
Next
Bloqueado = False
cn.BeginTrans
Set rsAux = New ADODB.Recordset
rsAux.Open SQLClientes
& " WHERE IDCliente = " & rsClientes!IDCliente.Value, cn, adOpenKeyset,
adLockPessimistic
rsAux!NombreComercial.Value =
rsAux!NombreComercial.Value
If Err.Number <> 0 And
cn.Errors.Count <> 0 Then
If cn.Errors(0).SQLState = 3218
Or cn.Errors(0).SQLState = 3260 Then
Bloqueado = True
rsAux.Close
Set rsAux =
Nothing
cn.RollbackTrans
Else
Err.Raise Err.Number, Err.Source, Err.Description
End
If
Else
Err.Raise Err.Number, Err.Source,
Err.Description
End If
Err.Clear
On Error GoTo
0
If Bloqueado
Then
MsgBox "Actualmente este registro está bloqueado por otro
usuario.", vbExclamation, Me.Caption
Else
rsClientes.Resync
adAffectCurrent
' **** Código para
modificaciones ó actualizaciones ...
En este
ejemplo hay que tener cuidado, ya que aunque rsClientes y rsAux apuntan al mismo
registro, el único que podrá modificarlo será rsAux. Nótese que para dejar el
registro en modo de edición sólamente hay que igualar cualquier campo a
cualquier valor, en este caso igualo el campo NombreComercial a su propio valor
(rsAux!NombreComercial.Value = rsAux!NombreComercial.Value).
Lo
siguiente a controlar son los errores que vamos a recibir, a
saber:
3218 "Could not update; currently locked": El
registro está bloqueado por otro usuario.
3260 "Couldn't update; currently locked by user
<name> on machine <name>": Está bloqueda la página donde se
encuentra actualmente el registro.
Adicionalmente otro error a controlar debería ser:
3197 "The database
engine stopped the process because you an another user are attempting to chage
de same data at same time"
Cómo bloquear a nivel de registro con SQL Server
7.0
En este caso imprescindible SQL Server 7.0
ó superior.
Ejemplo de
conexión:
Dim cn As ADODB.Connection
Set cn = New
ADODB.Connection
cn.Provider =
"SQLOLEDB"
cn.Properties("Data Source").Value = "(local)"
cnDatos.Properties("Initial Catalog").Value
= "MiBaseDatos"
cnDatos.Properties("User ID").Value =
"sa"
cnDatos.Properties("Password").Value = ""
cn.CommandTimeout =
10 '
Tiempo de espera
cn.CursorLocation = adUseServer
cn.IsolationLevel =
adXactChaos
cn.Open
cnDatos.Execute "SET LOCK_TIMEOUT
0"
Nótese en la sentencia SET LOCK_TIMEOUT, es
el tiempo de esperar antes de que SQL Server informe de que un registro está
bloqueado, cambiar al gusto (por defecto el valor creo que es bastante
alto).
Ahora bien, la forma de bloquear en SQL
Server es algo distinta, simplemente hay que seleccionar el registro en un
recordset pesimista y ya está, pero cuidado, si el registro está bloqueado por
otro usuario nos devolverá un recordset vacío (si la acción hubiera sido un
Update en vez de Select entonces sí informaría del bloqueo).
Ejemplo de
bloqueo
En este ejemplo es un fragmento
perteneciente a un mantenimiento de clientes, en este se utilizan dos
recordset's: rsClientes sobre el cual previamente se realizó un selección de
registros y es por donde se va moviendo el usuario con los botones de
movimientos y rsAux que me permite bloquear el registro cuando el usuario pulsa
el botón para modificarlo.
' Intentar un bloqueo
Bloqueado =
False
cn.BeginTrans
Set rsAux = New ADODB.Recordset
rsAux.Open SQLClientes
& " WHERE IDCliente = " & rsClientes!IDCliente.Value, cn, adOpenKeyset,
adLockPessimistic
If rsAux.RecordCount =
0 Then
rsAux.close
Set rsAux = Nothing
Bloqueado = True
End
If
If Bloqueado Then
MsgBox "Actualmente este registro está
bloqueado por otro usuario.", vbExclamation, Me.Caption
Else
rsClientes.Resync adAffectCurrent
' **** Código para
modificaciones ó actualizaciones ...
Notas finales:
Con este mensaje sólo intento clarificar algo sobre el
tema, debiendo vosotros investigar a partir de aquí. El código expuesto son
fragmentos extraidos de programas y no pueden tener mucho sentido sacados de
contexto.
Saludos a todos,
José Luis Dobón