ABAP Lock Objects: ENQUEUE, DEQUEUE, Sperrverwaltung

kategorie
ABAP-Statements
Veröffentlicht
autor
Johannes

Lock Objects (Sperrobjekte) gewährleisten die Datenkonsistenz bei gleichzeitigem Zugriff mehrerer Benutzer. Mit ENQUEUE und DEQUEUE werden Sperren gesetzt und freigegeben.

Grundkonzept

BegriffBeschreibung
Lock ObjectDefinition in SE11, generiert ENQUEUE/DEQUEUE FuBas
ENQUEUESperre setzen
DEQUEUESperre freigeben
Exclusive Lock (E)Exklusiver Schreibzugriff
Shared Lock (S)Gemeinsamer Lesezugriff
Optimistic Lock (O)Optimistisch, nur bei Änderung sperren

Sperrmodi

ModusBeschreibungKompatibel mit
E (Exclusive)SchreibsperreKeine
S (Shared)LesesperreS
X (Exclusive, nicht kumulierend)Strikte SchreibsperreKeine
O (Optimistic)OptimistischS, O

Beispiele

1. Lock Object erstellen (SE11)

Lock Object: EZ_CUSTOMER
Tabellen:
- KNA1 (Primary Table)
Sperrargumente:
- MANDT (aus KNA1)
- KUNNR (aus KNA1)
Generierte Funktionsbausteine:
- ENQUEUE_EZ_CUSTOMER
- DEQUEUE_EZ_CUSTOMER

2. Einfache Sperre setzen und freigeben

DATA: lv_kunnr TYPE kunnr VALUE '0000001000'.
" Sperre setzen
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER'
EXPORTING
mode_kna1 = 'E' " Exclusive Lock
mandt = sy-mandt
kunnr = lv_kunnr
EXCEPTIONS
foreign_lock = 1 " Bereits von anderem gesperrt
system_failure = 2
OTHERS = 3.
IF sy-subrc = 0.
" Sperre erfolgreich - Daten bearbeiten
UPDATE kna1 SET name1 = 'Neuer Name'
WHERE kunnr = lv_kunnr.
COMMIT WORK.
" Sperre freigeben
CALL FUNCTION 'DEQUEUE_EZ_CUSTOMER'
EXPORTING
mode_kna1 = 'E'
mandt = sy-mandt
kunnr = lv_kunnr.
ELSEIF sy-subrc = 1.
" Von anderem Benutzer gesperrt
MESSAGE |Kunde { lv_kunnr } ist gesperrt von { sy-msgv1 }| TYPE 'E'.
ENDIF.

3. Sperrklasse für saubere Handhabung

CLASS zcl_customer_lock DEFINITION.
PUBLIC SECTION.
METHODS: lock
IMPORTING iv_kunnr TYPE kunnr
RETURNING VALUE(rv_success) TYPE abap_bool.
METHODS: unlock
IMPORTING iv_kunnr TYPE kunnr.
METHODS: unlock_all.
METHODS: is_locked
IMPORTING iv_kunnr TYPE kunnr
RETURNING VALUE(rv_locked) TYPE abap_bool.
METHODS: get_lock_owner
IMPORTING iv_kunnr TYPE kunnr
RETURNING VALUE(rv_user) TYPE sy-uname.
PRIVATE SECTION.
DATA: mt_locked_customers TYPE TABLE OF kunnr.
ENDCLASS.
CLASS zcl_customer_lock IMPLEMENTATION.
METHOD lock.
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER'
EXPORTING
mode_kna1 = 'E'
mandt = sy-mandt
kunnr = iv_kunnr
_wait = abap_true " Warten wenn gesperrt
_collect = abap_false
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc = 0.
rv_success = abap_true.
APPEND iv_kunnr TO mt_locked_customers.
ELSE.
rv_success = abap_false.
ENDIF.
ENDMETHOD.
METHOD unlock.
CALL FUNCTION 'DEQUEUE_EZ_CUSTOMER'
EXPORTING
mode_kna1 = 'E'
mandt = sy-mandt
kunnr = iv_kunnr.
DELETE mt_locked_customers WHERE table_line = iv_kunnr.
ENDMETHOD.
METHOD unlock_all.
LOOP AT mt_locked_customers INTO DATA(lv_kunnr).
unlock( lv_kunnr ).
ENDLOOP.
ENDMETHOD.
METHOD is_locked.
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER'
EXPORTING
mode_kna1 = 'E'
mandt = sy-mandt
kunnr = iv_kunnr
_wait = abap_false
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc = 1.
rv_locked = abap_true.
ELSE.
" Sperre wieder freigeben (war nur Test)
CALL FUNCTION 'DEQUEUE_EZ_CUSTOMER'
EXPORTING
mode_kna1 = 'E'
mandt = sy-mandt
kunnr = iv_kunnr.
rv_locked = abap_false.
ENDIF.
ENDMETHOD.
METHOD get_lock_owner.
DATA: lt_locks TYPE TABLE OF seqg3.
" Alle Sperren lesen
CALL FUNCTION 'ENQUEUE_READ'
EXPORTING
gname = 'KNA1'
garg = |{ sy-mandt }{ iv_kunnr }|
TABLES
enq = lt_locks
EXCEPTIONS
communication_failure = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc = 0 AND lt_locks IS NOT INITIAL.
rv_user = lt_locks[ 1 ]-guname.
ENDIF.
ENDMETHOD.
ENDCLASS.
" Verwendung
DATA(lo_lock) = NEW zcl_customer_lock( ).
IF lo_lock->lock( '0000001000' ).
" Bearbeitung...
lo_lock->unlock( '0000001000' ).
ELSE.
DATA(lv_owner) = lo_lock->get_lock_owner( '0000001000' ).
MESSAGE |Gesperrt von { lv_owner }| TYPE 'E'.
ENDIF.

4. Warten auf Sperre

" Mit _WAIT = abap_true wartet der Aufruf bis zu 10 Sekunden
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER'
EXPORTING
mode_kna1 = 'E'
kunnr = lv_kunnr
_wait = abap_true " Warten aktivieren
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.
" Oder: Eigene Warteschleife mit Timeout
DATA: lv_attempts TYPE i VALUE 0,
lv_max_attempts TYPE i VALUE 10.
WHILE lv_attempts < lv_max_attempts.
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER'
EXPORTING
kunnr = lv_kunnr
_wait = abap_false
EXCEPTIONS
foreign_lock = 1
OTHERS = 2.
IF sy-subrc = 0.
EXIT. " Sperre erhalten
ENDIF.
lv_attempts = lv_attempts + 1.
WAIT UP TO 1 SECONDS.
ENDWHILE.
IF sy-subrc <> 0.
MESSAGE 'Sperre konnte nicht erhalten werden' TYPE 'E'.
ENDIF.

5. Kollektive Sperren (_COLLECT)

" Sperren sammeln statt sofort setzen
DATA: lt_customers TYPE TABLE OF kunnr.
lt_customers = VALUE #( ( '0000001000' ) ( '0000001001' ) ( '0000001002' ) ).
" Sperren sammeln
LOOP AT lt_customers INTO DATA(lv_kunnr).
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER'
EXPORTING
kunnr = lv_kunnr
_collect = abap_true " Sammeln
EXCEPTIONS
foreign_lock = 1
OTHERS = 2.
IF sy-subrc <> 0.
" Fehlerbehandlung
ENDIF.
ENDLOOP.
" Alle gesammelten Sperren auf einmal setzen
CALL FUNCTION 'FLUSH_ENQUEUE'
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc <> 0.
" Mindestens eine Sperre konnte nicht gesetzt werden
" Alle bereits gesetzten werden zurückgenommen
ENDIF.

6. Shared Lock für Lesezugriff

" Shared Lock - mehrere Leser gleichzeitig möglich
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER'
EXPORTING
mode_kna1 = 'S' " Shared Lock
kunnr = lv_kunnr
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc = 0.
" Lesen (andere können auch lesen)
SELECT SINGLE * FROM kna1
WHERE kunnr = @lv_kunnr
INTO @DATA(ls_customer).
CALL FUNCTION 'DEQUEUE_EZ_CUSTOMER'
EXPORTING
mode_kna1 = 'S'
kunnr = lv_kunnr.
ENDIF.

7. Optimistic Locking

CLASS zcl_optimistic_lock DEFINITION.
PUBLIC SECTION.
METHODS: read_for_update
IMPORTING iv_kunnr TYPE kunnr
EXPORTING es_customer TYPE kna1
ev_timestamp TYPE timestampl.
METHODS: save_changes
IMPORTING is_customer TYPE kna1
iv_timestamp TYPE timestampl
RETURNING VALUE(rv_success) TYPE abap_bool.
ENDCLASS.
CLASS zcl_optimistic_lock IMPLEMENTATION.
METHOD read_for_update.
" Optimistic Lock setzen
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER'
EXPORTING
mode_kna1 = 'O' " Optimistic
kunnr = iv_kunnr
EXCEPTIONS
OTHERS = 1.
" Daten lesen
SELECT SINGLE * FROM kna1
WHERE kunnr = @iv_kunnr
INTO @es_customer.
GET TIME STAMP FIELD ev_timestamp.
ENDMETHOD.
METHOD save_changes.
" Vor dem Speichern: Exklusive Sperre versuchen
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER'
EXPORTING
mode_kna1 = 'E'
kunnr = is_customer-kunnr
_convert = abap_true " O -> E konvertieren
EXCEPTIONS
foreign_lock = 1
OTHERS = 2.
IF sy-subrc = 0.
" Prüfen ob Daten geändert wurden
SELECT SINGLE aedat aezet FROM kna1
WHERE kunnr = @is_customer-kunnr
INTO @DATA(ls_check).
" Wenn zwischenzeitlich geändert -> Konflikt
" (Vereinfachte Prüfung)
UPDATE kna1 FROM @is_customer.
rv_success = xsdbool( sy-subrc = 0 ).
CALL FUNCTION 'DEQUEUE_EZ_CUSTOMER'
EXPORTING
mode_kna1 = 'E'
kunnr = is_customer-kunnr.
ELSE.
rv_success = abap_false.
ENDIF.
ENDMETHOD.
ENDCLASS.

8. Alle Sperren eines Objekttyps lesen

DATA: lt_locks TYPE TABLE OF seqg3.
" Alle Sperren für Tabelle KNA1 lesen
CALL FUNCTION 'ENQUEUE_READ'
EXPORTING
gclient = sy-mandt
gname = 'KNA1' " Tabellenname
guname = '*' " Alle Benutzer
TABLES
enq = lt_locks
EXCEPTIONS
communication_failure = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc = 0.
LOOP AT lt_locks INTO DATA(ls_lock).
WRITE: / 'Objekt:', ls_lock-garg,
/ 'Benutzer:', ls_lock-guname,
/ 'Modus:', ls_lock-gmode.
ENDLOOP.
ENDIF.

9. DEQUEUE_ALL - Alle eigenen Sperren freigeben

" Alle vom aktuellen Programm gesetzten Sperren freigeben
CALL FUNCTION 'DEQUEUE_ALL'.
" Alternativ: Spezifisches Lock Object
CALL FUNCTION 'DEQUEUE_EZ_CUSTOMER'
EXPORTING
mode_kna1 = 'E'
kunnr = space. " Leer = Alle Sperren dieses Objekts

10. Lock Object mit mehreren Tabellen

Lock Object: EZ_ORDER (SE11)
Tabellen:
- VBAK (Primary Table - Kopf)
- VBAP (Secondary Table - Positionen)
Sperrargumente:
- MANDT
- VBELN (Gemeinsamer Schlüssel)
" Eine Sperre sperrt Kopf und Positionen
CALL FUNCTION 'ENQUEUE_EZ_ORDER'
EXPORTING
mode_vbak = 'E'
mode_vbap = 'E'
vbeln = lv_vbeln
EXCEPTIONS
foreign_lock = 1
OTHERS = 2.
IF sy-subrc = 0.
" Kopf und Positionen bearbeiten
UPDATE vbak SET ... WHERE vbeln = lv_vbeln.
UPDATE vbap SET ... WHERE vbeln = lv_vbeln.
COMMIT WORK.
CALL FUNCTION 'DEQUEUE_EZ_ORDER'
EXPORTING
mode_vbak = 'E'
mode_vbap = 'E'
vbeln = lv_vbeln.
ENDIF.

11. Sperre mit Scope

" _SCOPE Parameter bestimmt Lebensdauer
" 1 = Dialog (Standard) - Sperre bei COMMIT freigegeben
" 2 = Update Task - Sperre wird an Update Task übergeben
" 3 = Beide
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER'
EXPORTING
mode_kna1 = 'E'
kunnr = lv_kunnr
_scope = '2' " Für Update Task
EXCEPTIONS
OTHERS = 1.
" Änderung in Update Task
CALL FUNCTION 'Z_UPDATE_CUSTOMER' IN UPDATE TASK
EXPORTING
is_customer = ls_customer.
COMMIT WORK.
" Sperre wird nach Update Task automatisch freigegeben

12. Deadlock-Vermeidung

CLASS zcl_deadlock_safe DEFINITION.
PUBLIC SECTION.
METHODS: lock_multiple
IMPORTING it_keys TYPE ty_key_tab
RETURNING VALUE(rv_success) TYPE abap_bool.
PRIVATE SECTION.
METHODS: sort_keys
CHANGING ct_keys TYPE ty_key_tab.
ENDCLASS.
CLASS zcl_deadlock_safe IMPLEMENTATION.
METHOD lock_multiple.
DATA: lt_keys TYPE ty_key_tab.
lt_keys = it_keys.
" WICHTIG: Immer in gleicher Reihenfolge sperren
" verhindert Deadlocks
sort_keys( CHANGING ct_keys = lt_keys ).
" Sperren setzen
LOOP AT lt_keys INTO DATA(ls_key).
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER'
EXPORTING
kunnr = ls_key-kunnr
_wait = abap_false
EXCEPTIONS
foreign_lock = 1
OTHERS = 2.
IF sy-subrc <> 0.
" Rollback: Bereits gesetzte Sperren freigeben
DATA(lv_index) = sy-tabix - 1.
LOOP AT lt_keys INTO DATA(ls_unlock) TO lv_index.
CALL FUNCTION 'DEQUEUE_EZ_CUSTOMER'
EXPORTING
kunnr = ls_unlock-kunnr.
ENDLOOP.
rv_success = abap_false.
RETURN.
ENDIF.
ENDLOOP.
rv_success = abap_true.
ENDMETHOD.
METHOD sort_keys.
SORT ct_keys BY kunnr.
ENDMETHOD.
ENDCLASS.

13. RAP: ETag und Optimistic Concurrency

" In RAP wird Concurrency über ETags gesteuert
" Behavior Definition:
define behavior for ZI_Customer
with etag master last_changed_at
lock master
{
update;
delete;
}
" Implementierung für Custom Lock
CLASS lhc_customer DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS lock FOR LOCK
IMPORTING keys FOR LOCK Customer.
ENDCLASS.
CLASS lhc_customer IMPLEMENTATION.
METHOD lock.
LOOP AT keys INTO DATA(ls_key).
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER'
EXPORTING
kunnr = ls_key-kunnr
EXCEPTIONS
foreign_lock = 1
OTHERS = 2.
IF sy-subrc <> 0.
APPEND VALUE #(
%tky = ls_key-%tky
) TO failed-customer.
APPEND VALUE #(
%tky = ls_key-%tky
%msg = NEW zcm_customer( severity = if_abap_behv_message=>severity-error
textid = zcm_customer=>locked )
) TO reported-customer.
ENDIF.
ENDLOOP.
ENDMETHOD.
ENDCLASS.

14. Transaktion SM12 - Sperreinträge anzeigen

" Programmatisch Sperren anzeigen (wie SM12)
DATA: lt_locks TYPE TABLE OF seqg3.
CALL FUNCTION 'ENQUEUE_READ'
EXPORTING
guname = sy-uname " Nur eigene Sperren
TABLES
enq = lt_locks
EXCEPTIONS
OTHERS = 1.
" Report
WRITE: / 'Aktive Sperren:'.
WRITE: / '---------------'.
LOOP AT lt_locks INTO DATA(ls_lock).
WRITE: / 'Tabelle:', ls_lock-gname,
/ 'Argument:', ls_lock-garg,
/ 'Modus:', ls_lock-gmode,
/ 'Benutzer:', ls_lock-guname,
/ 'Zeit:', ls_lock-gttime.
SKIP.
ENDLOOP.

15. Cleanup bei Programmabbruch

CLASS zcl_lock_manager DEFINITION.
PUBLIC SECTION.
METHODS: constructor.
METHODS: destructor. " Wird bei Garbage Collection aufgerufen
METHODS: lock_customer
IMPORTING iv_kunnr TYPE kunnr.
PRIVATE SECTION.
DATA: mt_locks TYPE TABLE OF kunnr.
ENDCLASS.
CLASS zcl_lock_manager IMPLEMENTATION.
METHOD constructor.
" Optional: Registrierung für Cleanup
ENDMETHOD.
METHOD destructor.
" Sperren freigeben wenn Objekt zerstört wird
LOOP AT mt_locks INTO DATA(lv_kunnr).
CALL FUNCTION 'DEQUEUE_EZ_CUSTOMER'
EXPORTING
kunnr = lv_kunnr.
ENDLOOP.
ENDMETHOD.
METHOD lock_customer.
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER'
EXPORTING
kunnr = iv_kunnr
EXCEPTIONS
OTHERS = 1.
IF sy-subrc = 0.
APPEND iv_kunnr TO mt_locks.
ENDIF.
ENDMETHOD.
ENDCLASS.
" Verwendung mit TRY-CLEANUP
TRY.
DATA(lo_lock) = NEW zcl_lock_manager( ).
lo_lock->lock_customer( '1000' ).
" Verarbeitung...
CLEANUP.
" Wird auch bei Exceptions ausgeführt
IF lo_lock IS BOUND.
CLEAR lo_lock. " Triggert Destruktor
ENDIF.
ENDTRY.

Lock Object in SE11 erstellen

  1. SE11 → Lock Object → Name: EZ_<Name>
  2. Primärtabelle angeben
  3. Sperrargumente definieren (Schlüsselfelder)
  4. Aktivieren → generiert ENQUEUE/DEQUEUE FuBas

Wichtige Hinweise / Best Practice

  • Immer DEQUEUE aufrufen – auch im Fehlerfall.
  • Sperren kurz halten – nur während der Änderung.
  • Gleiche Reihenfolge bei mehreren Sperren (Deadlock-Vermeidung).
  • _WAIT = abap_true für automatisches Warten.
  • _COLLECT für atomares Setzen mehrerer Sperren.
  • Optimistic Locking für lange Bearbeitungszeiten.
  • SM12 für Analyse aktiver Sperren.
  • COMMIT WORK gibt Sperren frei (Scope 1).
  • Lock Objects mit EZ_ Namenskonvention benennen.
  • Kombinieren Sie mit Exception Classes für Fehlerbehandlung.