A partir do Oracle Database 23ai (e mantido no 26ai), a Oracle introduziu o Lock-Free Reservation, uma forma diferente de lidar com concorrência em colunas numéricas “quentes” – aquelas que recebem atualizações o tempo todo, como:
- saldo de conta corrente;
- quantidade em estoque;
- limite de crédito;
- contadores de uso (número de acessos, milhas, pontos etc.).
Ao invés de gerar um lock na linha (row lock) durante toda a transação (até que o commit ou rollback sejam realizados), o banco registra uma "reserva" em uma coluna especial chamada RESERVABLE, usando um journal interno. As transações podem seguir em paralelo, e o update real na linha só acontece no commit.
No modelo tradicional, quando uma sessão executa um processo de DML (insert, update, delete) a linha inteira fica bloqueada até o commit ou rollback. Se mais de uma sessão tentar atualizar a mesma linha ao mesmo tempo, as demais ficarão esperando o lock gerado pela sessão inicial, gerando:
- esperas em enq: TX - row lock contention;
- aumento de tempo de resposta;
- percepção de lentidão ou “travamento” na aplicação.
Muitas vezes a sessão precisava apenas reservar um delta na coluna numérica (exemplo: reservar 1 unidade de um determinado produto), sem impedir que outras sessões façam reservas compatíveis no mesmo recurso.
O que é o Lock-free reservation e colunas
Com o Lock-Free Reservation os updates nessa coluna seguem um padrão específico. Em vez de alterar a linha imediatamente, o Oracle grava a operação em uma tabela de journal (SYS_RESERVJRNL_<obj#>), associada à tabela base.
O valor final na coluna é ajustado no commit, levando em conta todas as reservas concorrentes que foram aprovadas.
Ao criar a tabela com uma coluna RESERVABLE, o Oracle cria automaticamente a tabela de journal (reservations journal) no mesmo schema/tablespace para registrar as reservas.
De forma resumida, para usar lock-free reservations
- A coluna deve ser numérica: NUMBER, INTEGER ou FLOAT;
- A tabela precisa ter PRIMARY KEY;
- A coluna RESERVABLE só pode participar de
CHECKconstraints (incluindo constraints de tabela); - Não pode ter índice;
- Máximo de 10 colunas reservable por tabela;
- Tabelas especiais NÃO suportam colunas reservable: Blockchain, sharded, external, cluster, IOT, temporárias;
- Não é permitido usar a coluna reservable em partição, nem especificar storage para ela;
- Só funciona em tabelas de usuário – se tentar aplicar em tabelas de sistema, você verá erros como ORA-5575x.
Padrão para o processo de update
- Proibido: SET col_reservable = :valor_direto → gera erro.
- Em PK composta, todas as colunas da PK têm que ir no WHERE.
- Você pode atualizar várias colunas reservable na mesma instrução, mas não pode misturar reservable + não-reservable no mesmo UPDATE, nem usar RETURNING.
Exemplo:
UPDATE <tabela> SET <col_reservable> = <col_reservable> + (<expr>) WHERE <pk_column> = <expr>; -- ou UPDATE <tabela> SET <col_reservable> = <col_reservable> - (<expr>) WHERE <pk_column> = <expr>;
Exemplo prático - conta bancária com e sem lock-free
Cenário tradicional (sem lock-free)
Criação da tabela sem lock-free e inserção de dados para simulação
SQL> SQL> CREATE TABLE conta_trad ( 2 id_conta NUMBER PRIMARY KEY, cliente VARCHAR2(50), saldo NUMBER CONSTRAINT ck_conta_trad_saldo_min CHECK (saldo >= 0) ); 3 4 5 6 Table created. SQL> INSERT INTO conta_trad (id_conta, cliente, saldo) VALUES (1, 'Cliente 1', 100); 2 1 row created. SQL> COMMIT; Commit complete. SQL>
Simulação do processo com um update na sessão01 sem commit, em seguida um update sem commit na sessão02.
### SESSÃO 01 SQL> SQL> UPDATE conta_trad 2 SET saldo = saldo - 90 3 WHERE id_conta = 1; 1 row updated. SQL> ### SESSÃO 02 [oracle@oracle26ai ~]$ sqlplus USER/PASSWORD@FREEPDB1 SQL*Plus: Release 23.26.0.0.0 - Production on Wed Oct 29 10:28:58 2025 Version 23.26.0.0.0 Copyright (c) 1982, 2025, Oracle. All rights reserved. Last Successful login time: Wed Oct 29 2025 10:27:51 -03:00 Connected to: Oracle AI Database 26ai Free Release 23.26.0.0.0 - Develop, Learn, and Run for Free Version 23.26.0.0.0 SQL> UPDATE conta_trad 2 SET saldo = saldo - 5 3 WHERE id_conta = 1; ******* LOCK GERADO COMO ESPERADO *******
Como esperado no modelo tradicional, quando duas sessões tentam atualizar o mesmo registro, ocorre contenção de bloqueio: a primeira sessão obtém o lock da linha e a segunda fica aguardando a liberação desse lock para prosseguir.
Cenário com lock-free
Criação da tabela com lock-free e inserção de dados para simulação.
SQL> SQL> CREATE TABLE conta ( id_conta NUMBER PRIMARY KEY, cliente VARCHAR2(50), saldo NUMBER RESERVABLE CONSTRAINT ck_conta_saldo_min CHECK (saldo >= 0) ); 2 3 4 5 6 Table created. SQL> INSERT INTO conta (id_conta, cliente, saldo) VALUES (1, 'Cliente 1', 100); 2 1 row created. SQL> COMMIT; Commit complete. SQL>
Simulação do processo com um update na sessão01 sem commit, em seguida um update sem commit na sessão02.
### SESSÃO 01
SQL>
SQL> UPDATE conta
SET saldo = saldo - 20
WHERE id_conta = 1; 2 3
1 row updated.
SQL> !
### SESSÃO 02[oracle@oracle26ai ~]$ sqlplus USER/PASSWORD@FREEPDB1
SQL*Plus: Release 23.26.0.0.0 - Production on Wed Oct 29 10:40:40 2025 Version 23.26.0.0.0 Copyright (c) 1982, 2025, Oracle. All rights reserved. Last Successful login time: Wed Oct 29 2025 10:39:58 -03:00 Connected to: Oracle AI Database 26ai Free Release 23.26.0.0.0 - Develop, Learn, and Run for Free Version 23.26.0.0.0 SQL> UPDATE conta SET saldo = saldo - 5 WHERE id_conta = 1; 2 3 1 row updated. SQL>
###### VEJA QUE O UPDATE FOI REALIZADO SEM FICAR EM LOCK ###########Na primeira sessão, atualizamos o saldo da conta 1 subtraindo 20 unidades, mantendo a transação aberta, sem COMMIT. Em seguida, abrimos uma segunda sessão e executamos outro UPDATE na mesma linha, dessa vez subtraindo 5 unidades.
Embora no modelo tradicional a segunda sessão ficasse bloqueada aguardando o lock de linha da primeira transação, com a coluna saldo definida como RESERVABLE o Oracle registra apenas os deltas (-20 e -5) de forma lock-free. Assim, ambas as sessões conseguem atualizar a mesma linha sem contenção de bloqueio, e o valor final do saldo será a combinação dos deltas aplicados após os commits.
Para entender melhor o que está acontecendo, podemos enxergar a tabela de journal criada automaticamente.
SQL> SQL> SQL> UPDATE conta SET saldo = saldo - 20 WHERE id_conta = 1; 2 3 1 row updated. SQL> ! [oracle@oracle26ai ~]$ sqlplus lab_txn/lab_txn@FREEPDB1 SQL*Plus: Release 23.26.0.0.0 - Production on Wed Oct 29 10:40:40 2025 Version 23.26.0.0.0 Copyright (c) 1982, 2025, Oracle. All rights reserved. Last Successful login time: Wed Oct 29 2025 10:39:58 -03:00 Connected to: Oracle AI Database 26ai Free Release 23.26.0.0.0 - Develop, Learn, and Run for Free Version 23.26.0.0.0 SQL> UPDATE conta SET saldo = saldo - 5 WHERE id_conta = 1; 2 3 1 row updated. SQL>
Essa é a reservation journal table associada às tabelas com colunas RESERVABLE.
Atenção: Ela é mantida pela engine do banco; use só para consulta quando precisar entender o comportamento.
Logo após um UPDATE em uma coluna reservable e antes de um commit, na mesma sessão é possível consultar os dados da journal table.
Veja o exemplo da consulta feita na sessão02, antes e após o commit.
SQL> SQL> SELECT object_name, object_type FROM user_objects WHERE object_name LIKE 'SYS_RESERVJRNL%'; 2 3 OBJECT_NAME OBJECT_TYPE -------------------------------------------------------------------------------------------------------------------------------- ----------------------- SYS_RESERVJRNL_73620 TABLE SQL> select * from SYS_RESERVJRNL_73620; ORA_SAGA_ID$ ORA_TXN_ID$ ORA_STATUS$ ORA_ST ID_CONTA S SALDO_RESERVED -------------------------------- ---------------- ----------- ------ ---------- - -------------- 0500040094030000 ACTIVE UPDATE 1 - 5 SQL> commit; Commit complete. SQL> select * from SYS_RESERVJRNL_73620; no rows selected SQL>
Voltando na sessão01 e consultando a sua journal table.
SQL> set lines 210 SQL> SELECT object_name, object_type FROM user_objects WHERE object_name LIKE 'SYS_RESERVJRNL%'; 2 3 OBJECT_NAME OBJECT_TYPE -------------------------------------------------------------------------------------------------------------------------------- ----------------------- SYS_RESERVJRNL_73620 TABLE SQL> select * from SYS_RESERVJRNL_73620; ORA_SAGA_ID$ ORA_TXN_ID$ ORA_STATUS$ ORA_ST ID_CONTA S SALDO_RESERVED -------------------------------- ---------------- ----------- ------ ---------- - -------------- 01001C002C030000 ACTIVE UPDATE 1 - 20 SQL> select * from conta; ID_CONTA CLIENTE SALDO ---------- -------------------------------------------------- ---------- 1 Cliente 1 95 SQL> commit; Commit complete. SQL> select * from SYS_RESERVJRNL_73620; no rows selected SQL> select * from conta; ID_CONTA CLIENTE SALDO ---------- -------------------------------------------------- ---------- 1 Cliente 1 75 SQL>
O Lock-Free Reservation chega como uma resposta direta a um problema clássico de concorrência: a disputa por linhas “quentes” em colunas numéricas que vivem recebendo updates. Em vez de manter sessões presas em row locks enquanto uma transação não faz COMMIT, o banco passa a registrar apenas deltas internamente, aplicando o ajuste efetivo no momento do commit.
O resultado é simples de perceber na prática: menos esperas em enq: TX - row lock contention, melhor tempo de resposta e uma experiência de aplicação muito mais fluida em cenários de alta concorrência.
Fonte: Using Lock-Free Reservation
Comentários
Postar um comentário