1. Por que locking existe
Sem locking, dois problemas clássicos ocorrem em ambientes concorrentes:
| Problema | O que acontece | Exemplo bancário |
|---|---|---|
| Dirty read | Programa A lê dados que Programa B ainda não commitou — e B pode fazer rollback | Consulta de saldo mostra débito que foi revertido |
| Lost update | A e B leem o mesmo valor, ambos calculam novo valor e sobrescrevem — um dos updates se perde | Dois DEBITOs simultâneos no mesmo saldo, só um é registrado |
| Non-repeatable read | A lê linha, B atualiza, A lê de novo — o valor mudou dentro da mesma transação | Relatório de posição vê saldos diferentes na mesma execução |
| Phantom read | A conta linhas, B insere nova linha, A conta de novo — apareceu uma "fantasma" | Contagem de transações do dia muda durante o processamento |
2. Modos de lock
O DB2 usa diferentes modos de lock dependendo da operação. Os mais relevantes na prática:
| Modo | Nome | Quem adquire | Compatível com |
|---|---|---|---|
S | Share | SELECT (CS/RS/RR) | Outros S — incompatível com X |
U | Update | Cursor FOR UPDATE | S — incompatível com X e outro U |
X | Exclusive | INSERT, UPDATE, DELETE | Nenhum outro lock |
IS | Intent Share | SELECT no tablespace | IS, IX, S — incompatível com X |
IX | Intent Exclusive | Modificação no tablespace | IS, IX — incompatível com S, X |
SIX | Share with Intent Exclusive | SELECT + modificação simultânea | IS — bloqueia quase tudo |
3. Granularidade — ROW, PAGE e TABLESPACE
A granularidade define o tamanho do que é bloqueado. Locks menores permitem mais concorrência, mas exigem mais memória e gerenciamento.
| LOCKSIZE | O que bloqueia | Concorrência | Overhead |
|---|---|---|---|
ROW | Linha individual | Máxima | Alto — muitos locks simultâneos |
PAGE | Página de 4KB (default) | Boa | Médio |
TABLESPACE | Todo o tablespace | Mínima — bloqueia tudo | Mínimo |
ANY | DB2 decide dinamicamente | Variável | Variável |
O LOCKSIZE é definido na DDL do tablespace ou via ALTER TABLESPACE:
-- Definir granularidade na criação do tablespace CREATE TABLESPACE TSCONTAS IN SCHEMA1 LOCKSIZE ROW LOCKMAX SYSTEM; -- Alterar em tablespace existente ALTER TABLESPACE SCHEMA1.TSCONTAS LOCKSIZE ROW;
4. Níveis de isolamento — UR, CS, RS, RR
O nível de isolamento define quais fenômenos de concorrência são permitidos para uma unidade de trabalho. Do menos restritivo ao mais restritivo:
UR — Uncommitted Read (Dirty Read)
Não adquire nenhum lock de leitura. Pode ler dados que ainda não foram commitados — inclusive de transações que serão revertidas.
- Uso típico: relatórios de posição aproximada, dashboards em tempo real onde precisão absoluta não é crítica
- Nunca use para cálculos financeiros, saldos ou processos de débito/crédito
CS — Cursor Stability (padrão)
Mantém lock S apenas na linha atual do cursor. Ao avançar para a próxima linha, o lock da anterior é liberado. Lê apenas dados commitados.
- Uso típico: a maioria dos programas COBOL em ambientes bancários — bom equilíbrio entre proteção e concorrência
RS — Read Stability
Mantém lock S em todas as linhas qualificadas pelo predicado durante toda a unidade de trabalho. Garante que uma segunda leitura das mesmas linhas retorna os mesmos dados — mas pode aparecer novas linhas (phantoms).
RR — Repeatable Read
Mantém lock S em todas as linhas examinadas — inclusive as descartadas pelo predicado. Elimina completamente phantoms e non-repeatable reads. O nível mais restritivo — máxima proteção, mínima concorrência.
- Uso típico: processos que precisam garantir consistência absoluta durante toda a transação, como fechamento de posição ou cálculo de IR
| Nível | Dirty Read | Non-repeatable Read | Phantom Read | Concorrência |
|---|---|---|---|---|
| UR | Possível | Possível | Possível | Máxima |
| CS | Impossível | Possível | Possível | Alta |
| RS | Impossível | Impossível | Possível | Média |
| RR | Impossível | Impossível | Impossível | Baixa |
5. Definindo o nível de isolamento
O isolamento pode ser definido em dois lugares — no BIND do programa ou diretamente na query.
BIND PACKAGE (SCHEMA1)
MEMBER(PGMCONTA)
ISOLATION(CS) ← default para o programa inteiro
ACTION(REPLACE)
-- Leitura suja para relatório de posição aproximada SELECT SUM(SALDO) FROM CONTAS WHERE COD_AGENCIA = '001' WITH UR; -- Leitura estável para processamento financeiro SELECT SALDO, LIMITE FROM CONTAS WHERE COD_CONTA = :WS-COD-CONTA WITH CS; -- Máximo isolamento para fechamento SELECT * FROM POSICAO_DIA WHERE DT_REF = CURRENT DATE WITH RR;
6. Lock escalation
Quando o número de locks de página ou linha de uma unidade de trabalho atinge o limite definido por LOCKMAX, o DB2 promove automaticamente para um lock de tablespace — liberando memória no IRLM mas bloqueando todo o tablespace para outras transações.
-- LOCKMAX 0 = sem escalation (lock de page/row nunca vira tablespace) ALTER TABLESPACE SCHEMA1.TSCONTAS LOCKMAX 0; -- LOCKMAX SYSTEM = usa o parâmetro global do subsistema (padrão) ALTER TABLESPACE SCHEMA1.TSCONTAS LOCKMAX SYSTEM;
DSNT376I LOCK ESCALATION. A solução é sempre fazer COMMIT periódico em batch (a cada 1.000–10.000 linhas, dependendo do volume).
7. Deadlocks e timeouts
Dois cenários distintos causam SQLCODE negativo por problemas de locking:
Timeout (-911 reason 00C9008E)
O programa esperou mais que o tempo configurado em IRLMRWT (padrão: 60 segundos) por um lock que outro programa não liberou. O DB2 faz rollback da unidade de trabalho atual e retorna SQLCODE -911.
Deadlock (-911 reason 00C90088 ou -913)
Dois programas estão esperando um pelo lock do outro — formando um ciclo sem saída. O DB2 detecta o deadlock via um thread interno (o deadlock detection interval, padrão: 5 segundos) e escolhe uma das vítimas para fazer rollback.
Tempo Programa A Programa B
───── ───────────────────────────── ──────────────────────────────
T1 LOCK X em CONTAS (conta 001)
T2 LOCK X em TRANSACOES (conta 002)
T3 Tenta LOCK X em TRANSACOES
→ aguarda Programa B
T4 Tenta LOCK X em CONTAS (conta 001)
→ aguarda Programa A
T5 ← DEADLOCK detectado →
DB2 escolhe uma vítima
e faz ROLLBACK
8. Tratando -911 e -913 no COBOL
Programas COBOL em produção precisam tratar -911 e -913 explicitamente — sem tratamento, o programa simplesmente abenda ou produz resultado incorreto.
WORKING-STORAGE SECTION.
01 WS-MAX-TENTATIVAS PIC 9 VALUE 3.
01 WS-TENTATIVA PIC 9 VALUE 0.
01 WS-SQLCODE-SAVE PIC S9(9) COMP.
PROCEDURE DIVISION.
9000-PROCESSAR-COM-RETRY.
MOVE 0 TO WS-TENTATIVA
PERFORM UNTIL WS-TENTATIVA >= WS-MAX-TENTATIVAS
ADD 1 TO WS-TENTATIVA
PERFORM 9100-EXECUTAR-OPERACAO
EVALUATE SQLCODE
WHEN 0
EXEC SQL COMMIT END-EXEC
MOVE WS-MAX-TENTATIVAS TO WS-TENTATIVA
WHEN -911
WHEN -913
* Rollback automático já foi feito pelo DB2
* Aguarda e tenta novamente
MOVE SQLCODE TO WS-SQLCODE-SAVE
IF WS-TENTATIVA < WS-MAX-TENTATIVAS
PERFORM 9200-AGUARDAR-RETRY
ELSE
MOVE 'S' TO WS-ERRO-FLAG
PERFORM 9999-TRATAR-ERRO-FATAL
END-IF
WHEN OTHER
MOVE SQLCODE TO WS-SQLCODE-SAVE
PERFORM 9999-TRATAR-ERRO-FATAL
MOVE WS-MAX-TENTATIVAS TO WS-TENTATIVA
END-EVALUATE
END-PERFORM.
9100-EXECUTAR-OPERACAO.
EXEC SQL
UPDATE CONTAS
SET SALDO = SALDO - :WS-VALOR
WHERE COD_CONTA = :WS-COD-CONTA
END-EXEC.
9200-AGUARDAR-RETRY.
* Em batch COBOL não há SLEEP nativo
* Chame subprograma de espera ou simplesmente re-execute
CALL 'SLEEP01S' USING WS-TENTATIVA. *> espera N segundos
9. Boas práticas para minimizar contenção
COMMIT frequente em batch
01 WS-CTR-COMMIT PIC 9(7) VALUE 0.
01 WS-LIMITE-COMMIT PIC 9(7) VALUE 5000.
PERFORM UNTIL WS-FIM-ARQUIVO = 'S'
READ ARQ-ENTRADA
AT END MOVE 'S' TO WS-FIM-ARQUIVO
NOT AT END
PERFORM 2000-PROCESSAR-REGISTRO
ADD 1 TO WS-CTR-COMMIT
IF WS-CTR-COMMIT >= WS-LIMITE-COMMIT
EXEC SQL COMMIT END-EXEC
MOVE 0 TO WS-CTR-COMMIT
END-IF
END-READ
END-PERFORM
EXEC SQL COMMIT END-EXEC.
FOR FETCH ONLY em cursores de leitura
-- Sem FOR FETCH ONLY: o DB2 adquire lock U na linha corrente -- (precaução caso o programa queira fazer WHERE CURRENT OF) DECLARE C1 CURSOR FOR SELECT COD_CONTA, SALDO FROM CONTAS WHERE STATUS = 'A'; -- Com FOR FETCH ONLY: só lock S — muito mais concorrência DECLARE C1 CURSOR FOR SELECT COD_CONTA, SALDO FROM CONTAS WHERE STATUS = 'A' FOR FETCH ONLY; -- Com WITH UR: sem lock nenhum — máxima concorrência para relatório DECLARE C1 CURSOR FOR SELECT COD_CONTA, SALDO FROM CONTAS WHERE STATUS = 'A' FOR FETCH ONLY WITH UR;
Ordem consistente de acesso às tabelas
A causa mais comum de deadlock é programas diferentes acessando as mesmas tabelas em ordem diferente. Padronize: se um programa sempre acessa CLIENTES antes de CONTAS, todos devem seguir a mesma ordem.
Minimize o tempo de lock
- Faça toda a lógica de negócio antes de abrir a transação SQL
- Não faça I/O de arquivo ou chamadas externas com locks abertos
- Use SELECT INTO com lock mínimo (CS ou UR) antes de decidir se vai atualizar
- Feche cursores antes de COMMIT quando possível
10. Diagnóstico de problemas de locking
-- Ver todos os locks no subsistema (produz muito output) -DB2P DISPLAY DATABASE(*) LOCKS -- Ver locks em uma database específica -DB2P DISPLAY DATABASE(SCHEMA1) LOCKS -- Ver threads aguardando lock (lock suspension) -DB2P DISPLAY THREAD(*) TYPE(ACTIVE) DETAIL
-- Threads em espera de lock (requer acesso a SYSIBM.SYSLOCKS — versão z/OS) SELECT AUTHID, CORRID, PLANNAME, LOCKSTATE, LUWID, WAITLUWID FROM SYSIBM.SYSLOCKS WHERE LOCKSTATE = 'WAIT' ORDER BY AUTHID;
WHEN -911
MOVE SQLCODE TO WS-LOG-SQLCODE
MOVE SQLERRMC TO WS-LOG-ERRMSG *> reason code + info
MOVE SQLERRP TO WS-LOG-MODULE *> módulo DB2 que gerou
MOVE SQLERRD(1) TO WS-LOG-RBA *> RBA da transação
PERFORM 8000-GRAVAR-LOG-ERRO
*
* Reason codes comuns em SQLERRMC para -911:
* 00C90088 = deadlock
* 00C9008E = timeout (lock wait timeout)
* 00C900AE = global deadlock (data sharing)