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:
| Indicador | Significado | Bloqueo | Desbloqueo | Descripción |
|---|---|---|---|---|
"S" | Compartido | Sí | Sí | Múltiples lectores permitidos |
"E" | Escalable | Sí | Sí | Participa en la escalación de bloqueos |
"I" | Inmediato | No | Sí | Libera el bloqueo inmediatamente, incluso dentro de una transacción |
"D" | Diferido | No | Sí | Difiere 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:
- Ciclo de vida de la transacción: TSTART incrementa $TLEVEL, TCOMMIT lo decrementa, la confirmación real ocurre solo cuando $TLEVEL = 0
- TROLLBACK vs TROLLBACK 1: La reversión completa restablece $TLEVEL a 0; la reversión parcial lo decrementa en 1
- 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)
- Escalación de bloqueos: Por defecto 1000 bloqueos de fila activan un bloqueo a nivel de tabla; configurable mediante $SYSTEM.SQL.Util.SetOption
- Pesimista vs optimista: El pesimista bloquea antes de leer, el optimista verifica al momento de confirmar; conocer los valores de %ConcurrencyMode (0-4)
- 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