1. Por que locking existe

Sem locking, dois problemas clássicos ocorrem em ambientes concorrentes:

ProblemaO que aconteceExemplo 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:

ModoNomeQuem adquireCompatível com
SShareSELECT (CS/RS/RR)Outros S — incompatível com X
UUpdateCursor FOR UPDATES — incompatível com X e outro U
XExclusiveINSERT, UPDATE, DELETENenhum outro lock
ISIntent ShareSELECT no tablespaceIS, IX, S — incompatível com X
IXIntent ExclusiveModificação no tablespaceIS, IX — incompatível com S, X
SIXShare with Intent ExclusiveSELECT + modificação simultâneaIS — bloqueia quase tudo
🦕 Analogia: imagine uma sala de reunião. Lock S é como "estou usando, mas outros podem entrar para ler os documentos". Lock X é "entrei, tranquei a porta, ninguém mais entra". Lock U é "vou ler primeiro, depois talvez modifique — pode ter outros leitores, mas só eu posso virar X".

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.

LOCKSIZEO que bloqueiaConcorrênciaOverhead
ROWLinha individualMáximaAlto — muitos locks simultâneos
PAGEPágina de 4KB (default)BoaMédio
TABLESPACETodo o tablespaceMínima — bloqueia tudoMínimo
ANYDB2 decide dinamicamenteVariávelVariável

O LOCKSIZE é definido na DDL do tablespace ou via ALTER TABLESPACE:

SQL
-- 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;
LOCKSIZE ROW para tabelas de alta concorrência. Em tabelas como CONTAS e TRANSACOES, onde múltiplos programas acessam linhas diferentes simultaneamente, LOCKSIZE ROW reduz drasticamente a contenção. O custo é mais locks no IRLM — mas com LOCKMAX SYSTEM, o DB2 pode escalar automaticamente se necessário.

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ívelDirty ReadNon-repeatable ReadPhantom ReadConcorrência
URPossívelPossívelPossívelMáxima
CSImpossívelPossívelPossívelAlta
RSImpossívelImpossívelPossívelMédia
RRImpossívelImpossívelImpossívelBaixa

5. Definindo o nível de isolamento

O isolamento pode ser definido em dois lugares — no BIND do programa ou diretamente na query.

JCL/SYSINDefinindo isolation no BIND
  BIND PACKAGE (SCHEMA1)
       MEMBER(PGMCONTA)
       ISOLATION(CS)    ← default para o programa inteiro
       ACTION(REPLACE)
SQLSobrescrevendo por query com WITH clause
-- 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;
WITH UR em cursores de relatório é uma das otimizações mais fáceis e impactantes em batch. Programas que só lêem dados para gerar relatório não precisam de lock — use WITH UR e elimine toda a contenção com programas online que estão atualizando as mesmas tabelas.

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.

SQLControlando escalation com LOCKMAX
-- 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;
⚠️ Escalation silenciosa em batch. Um programa COBOL que faz UPDATE em 2 milhões de linhas sem COMMIT intermediário vai acumular locks até disparar a escalation — e de repente bloqueia o tablespace inteiro para os programas online. O sinal no log é a mensagem 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.

CenárioDeadlock clássico em sistemas bancários
  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
🟣 -911 vs -913: ambos indicam rollback por locking, mas com origens diferentes. SQLCODE -911 significa que a unidade de trabalho inteira foi revertida (timeout ou deadlock onde o programa foi a vítima). SQLCODE -913 é mais raro — indica que apenas a operação corrente foi rejeitada sem rollback completo. O SQLERRMC de -911 contém o reason code que distingue timeout de deadlock.

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.

COBOLPadrão de tratamento de deadlock com retry
       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
⚠️ Após -911, o DB2 já fez o rollback. Não tente executar ROLLBACK explícito depois de -911 — não é necessário e pode causar comportamento inesperado. Simplesmente reexecute a unidade de trabalho inteira. Também não tente COMMIT depois de -911 — o DB2 vai retornar erro.

9. Boas práticas para minimizar contenção

COMMIT frequente em batch

COBOLCommit a cada N registros processados
       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

SQLElimina lock de update em cursores somente-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

DB2 CommandsComandos de diagnóstico de locks
-- 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
SQLConsultando locks e waits pelo catálogo em tempo real
-- 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;
COBOLExtraindo detalhes do -911 para log
       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)
🟣 Data sharing e lock global: em ambientes Parallel Sysplex com múltiplos DB2 compartilhando os mesmos dados (DB2 data sharing), os locks são gerenciados pelo GBP (Group Buffer Pool) e pelo XES (Cross-System Extended Services). Deadlocks podem ocorrer entre programas em sistemas DB2 diferentes. O reason code 00C900AE em SQLERRMC identifica esse cenário — é mais difícil de diagnosticar pois requer análise dos logs de múltiplos sistemas.