T2.1: Ensures Data Integrity

Knowledge Review - InterSystems ObjectScript Specialist

1. Gestiona transacciones

Puntos Clave

  • TSTART: Inicia una transacción e incrementa $TLEVEL en 1
  • TCOMMIT: Confirma la transacción y decrementa $TLEVEL en 1; la confirmación real ocurre solo cuando $TLEVEL vuelve a 0
  • $TLEVEL: Variable especial que rastrea la profundidad de anidamiento de transacciones (0 = sin transacción activa, máximo 255)
  • Transacciones anidadas: Cada par TSTART/TCOMMIT crea un nivel de anidamiento; las confirmaciones internas se difieren hasta el TCOMMIT más externo
  • Journaling: Todos los cambios en globals dentro de una transacción se registran en el journal automáticamente, independientemente de la configuración de journal de la base de datos

Notas Detalladas

Descripción general

Las transacciones en ObjectScript garantizan atomicidad: o se aplican todos los cambios dentro de la transacción, o no se aplica ninguno. El comando TSTART marca el inicio de una transacción e incrementa la variable especial $TLEVEL. TCOMMIT marca el final de una transacción exitosa y decrementa $TLEVEL. La confirmación real en la base de datos solo ocurre cuando $TLEVEL llega a 0, lo que significa que la transacción más externa ha completado.

Patrón básico de transacción

 TSTART
 TRY {
     SET ^Data("account", 1001, "balance") = ^Data("account", 1001, "balance") - 500
     SET ^Data("account", 1002, "balance") = ^Data("account", 1002, "balance") + 500
     TCOMMIT
 }
 CATCH ex {
     TROLLBACK
     WRITE "Error: ", ex.DisplayString(), !
 }

Transacciones anidadas

InterSystems IRIS soporta transacciones anidadas hasta 255 niveles. Cada TSTART incrementa $TLEVEL, y cada TCOMMIT lo decrementa. El concepto clave es que las llamadas internas a TCOMMIT no confirman realmente los datos -- la confirmación se difiere hasta que $TLEVEL llega a 0.

 WRITE "$TLEVEL = ", $TLEVEL, !   // 0
 TSTART
 WRITE "$TLEVEL = ", $TLEVEL, !   // 1
 SET ^Data("outer") = "value1"
 TSTART
 WRITE "$TLEVEL = ", $TLEVEL, !   // 2
 SET ^Data("inner") = "value2"
 TCOMMIT                           // $TLEVEL vuelve a 1 (aún no confirmado)
 TCOMMIT                           // $TLEVEL vuelve a 0 (todos los cambios confirmados)

Este diseño permite código modular donde los métodos invocados pueden emitir sus propios TSTART/TCOMMIT sin saber si el invocador ya inició una transacción.

Referencias de Documentación

2. Gestiona reversiones

Puntos Clave

  • TROLLBACK (sin argumento): Revierte TODOS los niveles de transacción y restablece $TLEVEL a 0
  • TROLLBACK 1: Revierte solo el nivel de anidamiento actual y decrementa $TLEVEL en 1
  • Reversión parcial: TROLLBACK 1 deshace solo los cambios realizados en el $TLEVEL actual
  • Operaciones no reversibles: Las variables locales, $INCREMENT, $SEQUENCE, operaciones LOCK y los globals privados de proceso NO se revierten
  • Manejo de errores: Siempre empareja TROLLBACK con bloques TRY-CATCH para una gestión robusta de transacciones

Notas Detalladas

Reversión completa vs reversión parcial

TROLLBACK sin argumento realiza una reversión completa de todos los niveles de transacción, restableciendo $TLEVEL a 0 y deshaciendo todos los cambios en globals realizados desde el primer TSTART. TROLLBACK 1 realiza una reversión parcial, deshaciendo solo los cambios realizados en el $TLEVEL actual y decrementando $TLEVEL en 1.

 TSTART
 SET ^Data("A") = 100
 TSTART
 SET ^Data("B") = 200
 TROLLBACK 1                  // Solo ^Data("B") = 200 se revierte; $TLEVEL decrementado a 1
 WRITE "$TLEVEL = ", $TLEVEL, !  // 1
 TCOMMIT                      // ^Data("A") = 100 se confirma

Patrón de reversión completa

 TSTART
 SET ^Data("A") = 100
 TSTART
 SET ^Data("B") = 200
 TROLLBACK                    // TODOS los cambios revertidos; $TLEVEL restablecido a 0
 WRITE "$TLEVEL = ", $TLEVEL, !  // 0

Crítico: Lo que TROLLBACK NO deshace

Varias operaciones no se ven afectadas por TROLLBACK:

  • Variables locales: Los cambios en variables locales persisten después de la reversión
  • $INCREMENT / $SEQUENCE: Estas operaciones atómicas no son transaccionales
  • Operaciones LOCK: Los bloqueos adquiridos dentro de una transacción se liberan al final de la transacción, pero TROLLBACK no "deshace" las operaciones de bloqueo
  • Globals privados de proceso (^||PPG): No se registran en el journal, por lo tanto no se revierten
 SET localVar = "before"
 TSTART
 SET localVar = "during"
 SET ^Data("test") = "transactional"
 TROLLBACK
 WRITE localVar, !          // "during" -- la variable local NO se revirtió
 WRITE $GET(^Data("test"))  // "" -- el cambio en el global SÍ se revirtió

Referencias de Documentación

3. Describe cómo los LOCKs aplican control de concurrencia

Puntos Clave

  • Bloqueo incremental: `LOCK +lockname` adquiere un bloqueo sin liberar los bloqueos existentes
  • Desbloqueo incremental: `LOCK -lockname` libera un bloqueo específico sin afectar otros
  • LOCK clásico: `LOCK lockname` libera TODOS los bloqueos existentes y adquiere el nuevo (raramente usado)
  • Tiempo de espera del bloqueo: `LOCK +lockname:timeout` especifica el tiempo máximo de espera en segundos; verificar $TEST para éxito
  • Bloqueos exclusivos: Cualquier bloqueo sin `#"S"` es exclusivo (por defecto); bloquea todos los demás bloqueos sobre ese recurso
  • Bloqueos compartidos: `LOCK +lockname#"S"` permite múltiples lectores; los bloqueos exclusivos bloquean otros procesos
  • Indicadores de tipo de bloqueo: `#"S"` (compartido), `#"E"` (escalable), `#"I"` (desbloqueo inmediato), `#"D"` (desbloqueo diferido) — combinables (ej., `#"SI"`)
  • Desbloqueo inmediato: `LOCK -lockname#"I"` libera un bloqueo inmediatamente incluso dentro de una transacción, anulando el comportamiento diferido por defecto
  • Tabla de bloqueos: Recurso a nivel de sistema visible en el Portal de Administración bajo System Operation > Locks

Notas Detalladas

Patrón de bloqueo incremental

El patrón estándar en ObjectScript moderno usa bloqueos incrementales (+/-) para gestionar la concurrencia sin liberar accidentalmente otros bloqueos mantenidos por el proceso.

 // Adquirir bloqueo con tiempo de espera de 5 segundos
 LOCK +^Data("account", acctId):5
 IF '$TEST {
     WRITE "Could not acquire lock", !
     QUIT
 }
 TRY {
     // Sección crítica: leer-modificar-escribir
     SET balance = ^Data("account", acctId, "balance")
     SET ^Data("account", acctId, "balance") = balance + amount
     TCOMMIT
 }
 CATCH ex {
     TROLLBACK
 }
 LOCK -^Data("account", acctId)     // Siempre liberar el bloqueo

Bloqueos compartidos vs exclusivos

Cualquier bloqueo sin el indicador #"S" es un bloqueo exclusivo (también llamado bloqueo de escritura). No existe un indicador explícito "exclusivo" — la ausencia de #"S" hace que un bloqueo sea exclusivo por defecto. Los bloqueos compartidos permiten que múltiples procesos lean concurrentemente, mientras que los bloqueos exclusivos proporcionan acceso exclusivo:

  • Un bloqueo compartido existente impide que otros procesos adquieran un bloqueo exclusivo sobre ese recurso
  • Un bloqueo exclusivo existente impide que otros procesos adquieran cualquier bloqueo (compartido o exclusivo) sobre ese recurso
  • Múltiples procesos pueden mantener bloqueos compartidos sobre el mismo recurso simultáneamente
 // Bloqueo compartido para lectura (múltiples lectores permitidos)
 LOCK +^Data("report")#"S":5
 IF $TEST {
     // Operaciones de lectura
     LOCK -^Data("report")#"S"
 }

 // Bloqueo exclusivo para escritura (bloquea todos los demás bloqueos)
 LOCK +^Data("report"):5
 IF $TEST {
     // Operaciones de escritura
     LOCK -^Data("report")
 }

Indicadores de tipo de bloqueo

El comando LOCK soporta indicadores de tipo especificados después de # entre comillas. Indicadores disponibles:

IndicadorSignificadoBloqueoDesbloqueoDescripción
"S"CompartidoMúltiples lectores permitidos
"E"EscalableParticipa en la escalación de bloqueos
"I"InmediatoNoLibera el bloqueo inmediatamente, incluso dentro de una transacción
"D"DiferidoNoDifiere el desbloqueo hasta que termine la transacción (comportamiento por defecto dentro de transacciones)

Los indicadores se pueden combinar: #"SE" (compartido escalable), #"SI" (desbloqueo compartido inmediato), #"EI" (desbloqueo escalable inmediato).

Desbloqueo inmediato vs diferido dentro de transacciones

Por defecto, los bloqueos adquiridos dentro de una transacción se mantienen hasta TCOMMIT o TROLLBACK, incluso si se liberan explícitamente con LOCK -. Este es el comportamiento de desbloqueo diferido (#"D", por defecto dentro de transacciones).

El desbloqueo inmediato (#"I") anula este comportamiento y libera el bloqueo de inmediato, incluso dentro de una transacción:

 TSTART
 LOCK +^Data("temp"):5
 // ... trabajar con ^Data("temp") ...
 LOCK -^Data("temp")#"I"          // Liberado AHORA, no en TCOMMIT
 // ... otro trabajo continúa en la transacción ...
 TCOMMIT

Use el desbloqueo inmediato cuando necesite liberar un bloqueo dentro de una transacción porque el recurso protegido ya no es necesario y desea minimizar la contención de bloqueos. Use el comportamiento diferido por defecto cuando el bloqueo debe mantenerse durante toda la duración de la transacción para garantizar la integridad de los datos.

Tabla de bloqueos

La tabla de bloqueos es una estructura en memoria a nivel de sistema que rastrea todos los bloqueos actuales. Se puede ver a través del Portal de Administración (System Operation > Locks) o programáticamente. La tabla de bloqueos tiene un tamaño configurable (parámetro locksiz). Cuando la tabla de bloqueos está llena, las nuevas solicitudes de bloqueo fallan con un error .

Referencias de Documentación

4. Describe el umbral de escalación de bloqueos y el efecto en bloqueos de fila versus bloqueos de tabla

Puntos Clave

  • Umbral por defecto: 1000 bloqueos a nivel de fila por tabla activa la escalación automática a un bloqueo a nivel de tabla
  • Comportamiento de escalación: Todos los bloqueos individuales de fila se reemplazan por un único bloqueo a nivel de tabla
  • Compromiso de rendimiento: Los bloqueos de tabla reducen la sobrecarga de la tabla de bloqueos pero bloquean todo acceso concurrente a la tabla
  • Umbral configurable: Ajustable mediante $SYSTEM.SQL.Util.SetOption("LockThreshold", value) o el Portal de Administración
  • Alcance: El umbral se aplica por proceso, por tabla

Notas Detalladas

Cómo funciona la escalación de bloqueos

Cuando un proceso adquiere bloqueos a nivel de fila a través de operaciones SQL (INSERT, UPDATE, DELETE), InterSystems IRIS rastrea el número de bloqueos por tabla. Una vez que el número de bloqueos de fila para una sola tabla excede el umbral de escalación de bloqueos (por defecto 1000), el sistema escala automáticamente a un bloqueo a nivel de tabla. Esto reemplaza todos los bloqueos individuales de fila con un único bloqueo sobre toda la tabla.

 // Verificar el umbral actual de escalación de bloqueos
 WRITE $SYSTEM.SQL.Util.GetOption("LockThreshold"), !   // Por defecto: 1000

 // Cambiar el umbral
 DO $SYSTEM.SQL.Util.SetOption("LockThreshold", 2000)

Impacto en la concurrencia

  • Antes de la escalación: Otros procesos pueden acceder a filas que no están individualmente bloqueadas. Se mantiene una concurrencia de grano fino.
  • Después de la escalación: Toda la tabla está bloqueada. Otros procesos que intenten bloquear cualquier fila en la misma tabla quedarán bloqueados (o fallarán por tiempo de espera). Esto puede causar cuellos de botella significativos de concurrencia.

Cuándo ocurre la escalación

La escalación de bloqueos típicamente ocurre durante:

  • Operaciones masivas de INSERT
  • UPDATE masivo que afecta muchas filas
  • Operaciones DELETE sobre conjuntos de resultados grandes
  • Transacciones de larga duración que acumulan bloqueos de fila

Mejores prácticas

  • Mantener las transacciones cortas para minimizar el número de bloqueos de fila acumulados
  • Para operaciones masivas intencionales, considerar adquirir explícitamente un bloqueo a nivel de tabla primero (para evitar la escalación gradual) y usar %NOLOCK en consultas de lectura concurrentes, o incrementar el umbral temporalmente
  • Monitorear la tabla de bloqueos en el Portal de Administración para detectar eventos de escalación
  • El umbral es una configuración a nivel de sistema; cambiarlo afecta a todos los procesos

Referencias de Documentación

5. Diferencia entre controles de concurrencia pesimista y optimista

Puntos Clave

  • Concurrencia pesimista: Bloquea recursos ANTES de leer; garantiza que no haya conflictos pero reduce el rendimiento
  • Concurrencia optimista: Lee sin bloquear; verifica conflictos en el momento de la confirmación; mejor rendimiento para escenarios de baja contención
  • %ConcurrencyMode: Propiedad del objeto que controla el comportamiento de bloqueo (0=None, 1=Atomic, 2=SharedAtomicRetain, 3=SharedRetain, 4=PessimisticRetain)
  • Optimista vía %VersionId: Usa un contador de versión para detectar modificaciones concurrentes
  • SQL ISOLATION LEVEL: READ UNCOMMITTED, READ COMMITTED (por defecto), READ VERIFIED proporcionan diferentes garantías de concurrencia

Notas Detalladas

Concurrencia pesimista

En la concurrencia pesimista, un proceso adquiere un bloqueo exclusivo sobre un recurso antes de leerlo o modificarlo. Esto impide que otros procesos accedan al recurso hasta que el bloqueo se libere. Es adecuada para escenarios de alta contención donde los conflictos son frecuentes.

 // Enfoque pesimista: bloquear primero, luego leer y modificar
 LOCK +^Data("customer", custId):5
 IF '$TEST {
     WRITE "Resource busy", !
     QUIT
 }
 SET data = ^Data("customer", custId)
 // ... modificar datos ...
 SET ^Data("customer", custId) = data
 LOCK -^Data("customer", custId)

Concurrencia optimista

En la concurrencia optimista, un proceso lee datos sin adquirir bloqueos. Antes de confirmar los cambios, verifica que ningún otro proceso haya modificado los datos mientras tanto. Si se detecta un conflicto, la operación se reintenta o se reporta como fallo. Esto funciona bien en entornos de baja contención.

 // Enfoque optimista usando %VersionId en clases persistentes
 SET obj = ##class(MyApp.Customer).%OpenId(custId)
 // ... modificar propiedades de obj ...
 SET sc = obj.%Save()
 IF $$$ISERR(sc) {
     // Verificar si es un conflicto de concurrencia
     // Una discrepancia en %VersionId significa que otro proceso modificó el objeto
     WRITE "Concurrency conflict - retry needed", !
 }

Modos de concurrencia de objetos (%ConcurrencyMode)

Los objetos persistentes de InterSystems IRIS soportan varios modos de concurrencia:

  • 0 (None): Sin bloqueo; el más rápido pero sin protección
  • 1 (Atomic): Bloqueo solo durante %Save; protege escrituras pero las lecturas pueden estar desactualizadas
  • 2 (SharedAtomicRetain): Bloqueo compartido en lectura, exclusivo en escritura; enfoque equilibrado
  • 3 (SharedRetain): Bloqueo compartido al abrir, retenido hasta cerrar
  • 4 (PessimisticRetain): Bloqueo exclusivo al abrir, retenido hasta cerrar; máxima protección
 // Abrir con bloqueo pesimista
 SET obj = ##class(MyApp.Customer).%OpenId(custId, 4, .sc)  // concurrency = 4

Niveles de aislamiento SQL

  • READ UNCOMMITTED: Sin bloqueos; puede leer datos no confirmados (lecturas sucias)
  • READ COMMITTED (por defecto): Lee solo datos confirmados; usa bloqueos compartidos breves
  • READ VERIFIED: Relee datos al final de la operación para verificar consistencia

Referencias de Documentación

6. Usa transacciones y aplica controles de concurrencia en scripts SQL

Puntos Clave

  • &sql(START TRANSACTION): Inicia una transacción SQL (equivalente a TSTART)
  • &sql(COMMIT): Confirma la transacción SQL
  • &sql(ROLLBACK): Revierte la transacción SQL
  • SQLCODE: Verificar después de cada sentencia SQL; 0 = éxito, 100 = sin datos, negativo = error
  • Indicador %NOLOCK: Omite el bloqueo de filas para operaciones SELECT en escenarios de solo lectura
  • Consultas parametrizadas: Usar variables host (:variable) en SQL embebido para vinculación segura de valores

Notas Detalladas

Patrón básico de transacción SQL embebido

 &sql(START TRANSACTION)
 &sql(UPDATE Account SET Balance = Balance - :amount WHERE AccountId = :fromAcct)
 IF SQLCODE < 0 {
     &sql(ROLLBACK)
     WRITE "Error on debit: ", %msg, !
     QUIT
 }
 &sql(UPDATE Account SET Balance = Balance + :amount WHERE AccountId = :toAcct)
 IF SQLCODE < 0 {
     &sql(ROLLBACK)
     WRITE "Error on credit: ", %msg, !
     QUIT
 }
 &sql(COMMIT)

Mezcla de transacciones ObjectScript y SQL

TSTART y &sql(START TRANSACTION) funcionan con el mismo mecanismo de transacción subyacente. Comparten $TLEVEL:

 TSTART
 SET ^AuditLog($INCREMENT(^AuditLog)) = $HOROLOG _ "|Transfer"
 &sql(UPDATE Account SET Balance = Balance - :amount WHERE AccountId = :fromAcct)
 IF SQLCODE < 0 {
     TROLLBACK
     QUIT
 }
 TCOMMIT

Uso de %NOLOCK para operaciones de solo lectura

El indicador %NOLOCK le dice a SQL que no adquiera bloqueos compartidos durante operaciones SELECT, mejorando el rendimiento para consultas de solo lectura donde la consistencia estricta no es necesaria:

 &sql(SELECT Name, Balance INTO :name, :bal
      FROM Account %NOLOCK
      WHERE AccountId = :acctId)

Esto es particularmente útil para consultas de reportes que no deberían bloquear operaciones de escritura concurrentes y no necesitan participar en el aislamiento de transacciones.

Verificación de permisos SQL dentro de transacciones

Las operaciones SQL dentro de transacciones respetan el mismo modelo de privilegios. Combine la gestión de transacciones con el manejo de errores para fallos de privilegios:

 &sql(START TRANSACTION)
 &sql(INSERT INTO SensitiveData (PatientId, Diagnosis) VALUES (:pid, :diag))
 IF SQLCODE = -99 {
     &sql(ROLLBACK)
     WRITE "Insufficient SQL privileges", !
     QUIT
 }
 &sql(COMMIT)

Referencias de Documentación

Resumen de Preparación para el Examen

Conceptos críticos a dominar:

  1. Ciclo de vida de la transacción: TSTART incrementa $TLEVEL, TCOMMIT lo decrementa, la confirmación real ocurre solo cuando $TLEVEL = 0
  2. TROLLBACK vs TROLLBACK 1: La reversión completa restablece $TLEVEL a 0; la reversión parcial lo decrementa en 1
  3. Bloqueos incrementales: Siempre usar el patrón LOCK +/-; entender bloqueos compartidos (#"S") vs exclusivos (por defecto); conocer los indicadores de tipo de bloqueo (#"I" inmediato, #"D" diferido, #"E" escalable)
  4. Escalación de bloqueos: Por defecto 1000 bloqueos de fila activan un bloqueo a nivel de tabla; configurable mediante $SYSTEM.SQL.Util.SetOption
  5. Pesimista vs optimista: El pesimista bloquea antes de leer, el optimista verifica al momento de confirmar; conocer los valores de %ConcurrencyMode (0-4)
  6. Transacciones SQL: &sql(START TRANSACTION/COMMIT/ROLLBACK) comparten $TLEVEL con TSTART/TCOMMIT/TROLLBACK

Escenarios comunes de examen:

  • Predecir valores de $TLEVEL después de una secuencia de comandos TSTART, TCOMMIT, TROLLBACK y TROLLBACK 1
  • Identificar qué se revierte y qué NO (las variables locales y $INCREMENT no se revierten)
  • Elegir entre concurrencia pesimista y optimista para escenarios de aplicación dados
  • Reconocer cuándo ocurrirá la escalación de bloqueos y su impacto en el acceso concurrente
  • Corregir patrones de transacciones SQL embebidas que carecen de manejo de errores o reversión adecuados
  • Entender el efecto de %NOLOCK en el comportamiento de consultas

Recomendaciones de práctica:

  • Escribir código de transacciones anidadas y verificar $TLEVEL en cada paso usando WRITE $TLEVEL
  • Experimentar con TROLLBACK 1 en transacciones anidadas y observar qué cambios persisten
  • Abrir dos sesiones de terminal y observar el comportamiento de bloqueo de LOCK con tiempos de espera
  • Usar el Portal de Administración (System Operation > Locks) para ver la tabla de bloqueos durante las pruebas
  • Escribir transacciones SQL embebidas con errores intencionales y verificar el comportamiento de ROLLBACK
  • Probar la escalación de bloqueos insertando más de 1000 filas en una sola transacción y monitoreando la tabla de bloqueos

Report an Issue