Controle o comportamento de bloqueio do banco de dados

Concluído

Por padrão, o runtime do Business Central determina automaticamente os níveis de isolamento usados ao consultar o banco de dados. No entanto, os desenvolvedores de AL podem controlar explicitamente o nível de isolamento do banco de dados em leituras individuais em uma instância de registro.

O nível de isolamento em uma transação determina o grau em que ela é isolada de outras transações para evitar problemas em situações simultâneas. No nível de registro, o nível de isolamento melhora a integridade e a estabilidade dos dados quando várias transações estão lendo o mesmo registro. Isso protege uma transação dos efeitos de outras transações, bloqueando, impedindo leituras de dados não confirmados ou impedindo modificações.

O bloqueio do banco de dados pode ser uma das principais causas de problemas de desempenho. Quando o código AL tem menos bloqueios, aumenta o desempenho do sistema para os usuários finais. Usando o nível de isolamento da instância de registro, você pode melhorar o desempenho limitando os bloqueios do banco de dados apenas ao necessário.

O runtime do Business Central determina automaticamente os níveis de isolamento usados ao consultar o banco de dados. O nível de isolamento de uma transação é aumentado implicitamente por gravações em um registro ou explicitamente por meio de uma chamada de método LockTable, ambos por tabela. O nível elevado de isolamento persiste durante toda a transação, deixando que o código subsequente executado seja afetado por níveis elevados de isolamento, quer seja necessário, quer seja desejado.

O exemplo abaixo mostra o código AL com dicas de nível de isolamento SQL anotadas nas leituras do banco de dados, que depende exclusivamente do bloqueio determinado pela transação.

```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;
```

Com a introdução do nível de isolamento da instância de registro, é possível selecionar explicitamente o nível de isolamento para leituras em uma instância de registro. O nível de isolamento da instância de registro substitui o nível de isolamento da transação para determinada tabela. É possível aumentar e diminuir o nível de isolamento, com o efeito sendo localizado na instância do registro, em vez de permanecer por toda a duração da transação.

O exemplo a seguir mostra o código AL com dicas de nível de isolamento SQL anotadas em leituras de banco de dados, com nível de isolamento de instância de registro usado para substituir o nível de isolamento da transação.

```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;
```

A lista a seguir descreve os diferentes níveis de isolamento do tipo de opção IsolationLevel que você pode aplicar:

  • Padrão

    • Segue o estado da transação. É o mesmo que não usar o isolamento de leitura.
  • ReadUncommitted

    • Permite leituras danificadas, o que significa que pode ler linhas que foram modificadas por outras transações, mas ainda não confirmadas. Não aceita bloqueios e ignora bloqueios de outras transações.
  • ReadCommitted

    • Permite a leitura apenas de dados confirmados ou seja, não pode ler dados modificados por outras transações, mas ainda não confirmados. Porém, isso não garante que as linhas lidas permaneçam consistentes durante toda a transação.
  • RepeatableRead

    • Garante que todas as leituras sejam estáveis, mantendo bloqueios compartilhados durante o tempo de vida da transação. A transação não pode ler dados que foram modificados, mas ainda não confirmados por outras transações, e nenhuma outra transação pode modificar dados que foram lidos pela transação atual até que a transação atual seja concluída.
  • UpdLock

    • Lê para atualização, impedindo que outros leiam com a mesma intenção.

Anteriormente, o AL fornecia apenas controle de nível de isolamento explícito por meio do método LockTable, o que garantia que todas as leituras no restante da transação usassem UpdLock. Em vez disso, com o nível de isolamento da instância de registro, o código pode ser explícito sobre as garantias de isolamento necessárias e deixar que o código subsequente não seja afetado por sua execução.

O exemplo a seguir aumenta o nível de isolamento em uma instância de registro do tipo Entrada de Contabilidade. Usa o bloqueio na última linha, enquanto as leituras subsequentes não disparam mais bloqueios a serem executados. Esse uso faz sentido em casos com assinantes de eventos, em que alguém injeta código em um fluxo de lógica de negócios existente. Quando não era esperado introduzir uma chamada LockTable causando o bloqueio de leituras subsequentes em uma tabela.

```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;
```

Em uma transação, não é possível determinar o nível de isolamento atual usado na transação. Se o código executado anteriormente disparou um nível de isolamento mais alto, a contagem em toda a tabela requer bloqueios em toda a tabela. Com o nível de isolamento da instância de registro, por exemplo, você pode obter uma contagem de registros estimada sem impedir que todos os outros façam alterações na tabela.

```al-language

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

Ao usar FlowFields e o estado de transação padrão, o estado da tabela de destino da fórmula de FlowField é usado para determinar o nível de isolamento, não o estado de destino da tabela de origem. Ao usar o nível de isolamento da instância de registro, a tabela de destino não importa, pois o nível de isolamento especificado no método ReadIsolation é usado. Considere o exemplo a seguir.

```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;
```