Comportamiento de bloqueo de bases de datos de control

Completado

De manera predeterminada, el runtime de Business Central determina automáticamente los niveles de aislamiento utilizados al consultar la base de datos. Sin embargo, los desarrolladores de AL pueden controlar específicamente el nivel de aislamiento de la base de datos en lecturas individuales de una instancia de registro.

El nivel de aislamiento de una transacción determina el grado de aislamiento con respecto a otras transacciones para evitar problemas en situaciones simultáneas. En el ámbito del registro, el nivel de aislamiento mejora la integridad y la estabilidad de los datos cuando varias transacciones leen el mismo registro. Protege a una transacción de los efectos de otras transacciones; para ello, hace bloqueos, impide lecturas de datos no comprometidos o evita las modificaciones.

El bloqueo de bases de datos puede ser una causa importante de problemas de rendimiento. Cuando el código AL acepta menos bloqueos, aumenta el rendimiento del sistema para el usuario final. Utilizando el nivel de aislamiento de instancia de registro, puede mejorar el rendimiento porque puede limitar los bloqueos de base de datos a lo que sea necesario.

El runtime de Business Central determina automáticamente los niveles de aislamiento utilizados al consultar la base de datos. El nivel de aislamiento de una transacción se eleva implícitamente, con escrituras en un registro, o explícitamente, a través de una llamada al método LockTable; en ambos casos, de forma individual en cada tabla. El nivel de aislamiento aumentado se mantiene durante toda la transacción, y deja que el código ejecutado posteriormente se vea afectado por los niveles aumentados de aislamiento, tanto si es necesario como si es preferible.

El siguiente ejemplo muestra código AL con sugerencias de nivel de aislamiento SQL anotadas en las lecturas de base de datos, y solo depende del bloqueo determinado por la transacción.

```al-language

local procedure CurrentBehavior()
var
    cust: Record Customer;
    otherCust: Record Customer;
    curr: Record Currency;
begin
    cust.FindFirst(); // READUNCOMMITTED

    // Heighten isolation level for Customer table.
    cust.LockTable();
    cust.FindLast(); // UPDLOCK

    // Also impacts other instances of same table.
    otherCust.FindSet(); // UPDLOCK

    // But does not impact other tables.
    curr.Find(); // READUNCOMMITTED
end;
```

Con la introducción del nivel de aislamiento de instancia de registro, es posible seleccionar explícitamente el nivel de aislamiento para las lecturas en una instancia de registro. El nivel de aislamiento de la instancia de registro anula el nivel de aislamiento de la transacción para una tabla determinada. El nivel de aislamiento se puede aumentar o disminuir y el efecto queda localizado a la instancia de registro, en lugar de durar toda la transacción.

El siguiente ejemplo muestra código AL con sugerencias de nivel de aislamiento SQL anotadas en lecturas de base de datos; se ha utilizado un nivel de aislamiento de instancia de registro para anular el nivel de aislamiento de la transacción.

```al-language

local procedure UsingReadIsolation()
var
    cust: Record Customer;
    otherCust: Record Customer;
    curr: Record Currency;
begin
    cust.FindFirst(); // READUNCOMMITTED

    // Heighten isolation level for Customer table.
    cust.LockTable();

    // Explicitly select another isolation level than the transaction's.
    otherCust.ReadIsolation := IsolationLevel::ReadUncommitted;

    otherCust.FindSet(); // READUNCOMMITED
end;
```

La siguiente lista describe los diferentes niveles de aislamiento del tipo de opción IsolationLevel que puede aplicar:

  • Predeterminada

    • Sigue el estado de la transacción. Equivale a no usar el aislamiento de lectura.
  • ReadUncommitted

    • Permite lecturas sucias, lo que significa que puede leer filas que han sido modificadas por otras transacciones, pero que aún no han sido confirmadas. No acepta bloqueos e ignora los bloqueos de otras transacciones.
  • ReadCommitted

    • Solo permite lecturas en datos confirmados; es decir, no puede leer datos que hayan sido modificados por otras transacciones, pero que aún no hayan sido confirmadas. Sin embargo, no garantiza que las filas leídas se mantengan uniformes durante toda la transacción.
  • RepeatableRead

    • Garantiza que todas las lecturas sean estables manteniendo los bloqueos compartidos durante toda la transacción. La transacción no puede leer datos que hayan sido modificados, pero que aún no hayan sido confirmados por otras transacciones, y ninguna otra transacción puede modificar datos que hayan sido leídos por la transacción actual hasta que esta finalice.
  • UpdLock

    • Lee para actualizar e impide que otros lean con la misma intención.

Anteriormente, AL solo proporcionaba un control explícito del nivel de aislamiento mediante el método LockTable, que garantizaba que todas las lecturas del resto de la transacción utilizasen UpdLock. En cambio, con el nivel de aislamiento de instancia de registro, el código puede ser explícito sobre las garantías de aislamiento que necesita y lograr que el código posterior no se vea afectado por la ejecución.

El siguiente ejemplo aumenta el nivel de aislamiento en una instancia de registro del tipo Movimiento de contabilidad. Acepta el bloqueo de la última fila, mientras que las lecturas posteriores no desencadenarán que se acepten más bloqueos. Este uso tiene sentido en casos con suscriptores de eventos, en los que se inyecta código en un flujo de lógica de negocios existente, y donde no se esperaba introducir una llamada LockTable que causara el bloqueo de lecturas posteriores en una tabla.

```al-language

// Gets the next "Entry No." and locks just last row.
// Without causing the rest of transaction to begin taking locks.
local procedure GetNextEntryNo(): Integer
var
    GLEntry: Record "G/L Entry";
begin
    GLEntry.ReadIsolation := IsolationLevel::UpdLock;
    GLEntry.FindLast();
    exit(GLEntry."Entry No." + 1)
end;
```

Dentro de una transacción, no es posible determinar el nivel de aislamiento actual utilizado en la transacción. Si el código ejecutado previamente ha desencadenado un nivel de aislamiento superior, el recuento en toda la tabla requiere bloqueos en toda la tabla. Con el nivel de aislamiento de instancia de registro, por ejemplo, podría obtener un recuento estimado de registros sin bloquear la posibilidad de que otros hagan cambios en la tabla.

```al-language

local procedure GetEstimatedCount(tableno: Integer) : Integer
var
    rref: RecordRef;
begin
    rref.Open(tableno);
    rref.ReadIsolation := IsolationLevel::ReadUncommitted;
    exit(rref.Count);
end;
```

Al usar FlowFields y el estado de transacción predeterminado, se utiliza el estado de la tabla de destino de la fórmula FlowField para determinar el nivel de aislamiento, en lugar de usar el estado de destino de la tabla de origen. Cuando se utiliza el nivel de aislamiento de instancia de registro, la tabla de destino no es importante, ya que se utiliza el nivel de aislamiento especificado en el método ReadIsolation. Observe el siguiente ejemplo.

```al-language

local procedure Foo()
var
    purchLine: Record "Purchase Line";
    curr: Record Currency;
begin
    curr.FindFirst();

    curr.CalcFields(curr."Vendor Outstanding Orders"); // READUNCOMMITED

    purchLine.LockTable();

    curr.CalcFields(curr."Vendor Outstanding Orders"); // UPDLOCK

    curr.ReadIsolation := IsolationLevel::ReadUncommitted;

    curr.CalcFields(curr."Vendor Outstanding Orders"); // READUNCOMMITTED
end;
```