From Block to Lock – Lock Modes von der Theorie bis zur Praxis Tobias Deml Trivadis GmbH München Schlüsselworte Performance, Troubleshooting, Datenmodell, Sperre, Lock, Constraints Einleitung Ein bekanntes Szenario für Datenbank Administratoren: Von den Anwendern wird ein "Lock“ in der Datenbank" gemeldet. Nun beginnt die Analyse der vorgefundenen Situation mit der Klassifizierung der Sperre und der Identifizierung des Blockers. Im produktiven Betrieb ist ein solcher Zustand meist kritisch und lässt daher oftmals wenig Zeit für diese Tätigkeiten. Aus diesem Grund ist es wichtig, die vorherrschende Sachlage richtig verstehen und einschätzen zu können. Diese Sperren sind ein elementarer Bestandteil der Sicherstellung von Regeln, die ein relationales Datenbanksystem ausmachen. Konzeptionelle Grundlage der Sperren Mit dem bekannten Schriftstück „A Relational Model of Data for Large Shared Data Banks“ legte der britische Mathematiker und Physiker Edgar F. Codd die Grundlagen eines modernen relationalen Datenbanksystems fest. Hiermit wurden Grundsätze festgelegt die noch immer Bestand und Anwendung finden. Einige dieser Regeln werden in einer Oracle Datenbank mit Sperren in der Datenbankstruktur sichergestellt. Eine davon ist die referenzielle Integrität, welche die Beziehungen (Constraint) zwischen Objekten in einer Datenbank regelt. So muss gesichert sein, dass ein entsprechend gegenstehender Primärschlüssel bereits vorhanden ist, wenn ein neuer Datensatz mit einem Fremdschlüssel in eine Datenbank geladen wird. Umgekehrt gilt, wenn Daten gelöscht werden, dürfen keine Daten mehr existieren, welche diesen Schlüssel referenzieren. Ein weiterer Grundsatz ist die Konsistenz in einer Datenbank. Es muss sichergestellt werden, dass die Daten zum Zeitpunkt der Abfrage immer den aktuell festgeschriebenen Zustand widerspiegeln. Ebenso genannt wird die Konkurrenz bzw. Nebenläufigkeit, welche Regeln für die simultane Bearbeitung der Daten von mehreren Parteien festlegt. Hierbei wird sichergestellt, dass die gleichzeitige Änderungsanforderung konsistent und fehlerfrei abläuft. Arten von Sperren in einer Datenbank Um diesen divergierenden Anforderungen gerecht zu werden, gibt es eine Vielzahl von verschiedenen Sperren. Diese Locks können in drei Gruppen eingeteilt werden. Die folgende Abbildung zeigt die verschiedenen Arten und die jeweilig vorhandenen Untergruppierungen: DML Locks Bei dieser Art unterscheidet man grundsätzlich zwei verschiedene Typen: - „Row Lock“(TX) - „Table Lock“(TM) Ein TX-Lock beschreibt die Sperrung einer oder mehrerer Zeilen in einer Tabelle durch eine DMLOperation (Data Manipulation Language). Diese Sperren sind immer exklusiv, also kann diese jeweils nur einmal pro Row gesetzt werden. Das Setzen des Locks geschieht über den Eintrag der jeweiligen Session-Informationen in den Header des entsprechenden Datenbankblocks. Wenn ein Benutzer eine solche Aktion durch INSERT, UPDATE oder DELETE auslöst, resultiert daraus immer eine Sperre auf die entsprechende Tabelle. Diese Sperre wird implizit gelöst, sobald die jeweilige Transaktion durch einen Commit oder einen Rollback abgeschlossen ist. Daneben gibt es die „Table Locks“ (TM), die eine Sperrung einer Tabelle beschreiben. Hier wird zwischen fünf verschiedenen Lock-Arten unterschieden: Row Shared Lock (SS/RSX) Diese Art von Sperre ist am wenigsten restriktiv und erlaubt somit die meiste Konkurrenz auf die entsprechende Tabelle. Dieser Lock zeigt an, dass eine Transaktion, welche diese Sperre auf der Tabelle hält, beabsichtigt, eine Zeile zu ändern. Row Exclusive Table Lock (SX/RX) Diese Variante zeigt an, dass eine Session Daten eine DML-Operation ausgelöst oder durch ein SELECT…FOR UPDATE hervorgerufen hat. Dieser Lockerlaubt jedoch SX-Sperren von weiteren Transaktionen. Somit ist es auch möglich, andere Datensätze der gleichen Tabelle zu bearbeiten. Shared Table Lock (S) Dieser Lock erlaubt generell die Abfrage aller Daten. DML-Operationen(INSERT, UPDATE, DELETE) der Daten sind nur möglich, wenn nur ein Shared Lock auf der Tabelle existiert. Außerdem reicht das Setzen dieser Sperre nicht aus, um die Daten der Tabelle zu ändern. Shared Row Exclusive Table Lock (SSX/SRX) Etwas restriktiver als der S-Lock kann hier nur ein SSX pro Tabelle gehalten werden. Die Selektion der Daten ist für alle weiteren Sessions möglich, aber deren Änderung wird durch diese Sperre verhindert. Exclusive Lock (X) Dieser Lock ist der restriktivste seiner Art. Diese Sperre verhindert jegliches Ausführen von DMLOperation und Setzten eines anderen Lock-Modes. Jedoch ist die Selektion von Daten noch immer blockadefrei möglich. Hier eine Matrix mit den oben beschriebenen Lock-Modes und welche Operationen bzw. Sperren bei den jeweiligen Optionen noch möglich sind. DDL Locks Diese Art von Sperren schützt während einer laufenden DDL-Operation die Definition von Datenbankobjekten, die in dem ausgeführten Statement referenziert werden. DDL Locks beziehen sich immer auf ein einzelnes Objekt und nie auf das gesamte Data Dictionary. Bei der Erstellung beispielsweise einer Procedure wird eine Sperre gesetzt, um während des Aufbaus des Objekts zu verhindern, dass eine referenzierte Tabelle strukturell geändert oder sogar entfernt wird. Bei dieser Gruppierung von Locks wird zwischen drei verschiedenen Typen unterschieden: Exclusive DDL Lock Dieser exklusive Lock verhindert die Ausführung jeglicher DDL oder DML-Operationen auf ein bestimmtes Objekt. Die meisten DDL-Operationen lösen eine solche exklusive Sperre aus, um einen alleinigen Zugriff auf das entsprechende Objekt zu gewährleisten. Beispielsweise ist es während einer DROP-Operation auf eine Tabelle nicht möglich, gleichzeitig deren Struktur durch ein ALTERStatement zu verändern. Eine exklusive DDL Sperre dauert während der Ausführung der entsprechenden Operation an und wird automatisch nach deren Beendigung wieder entfernt. Share DDL Lock Die Share DDL Sperre verhindert, dass ein exklusiver Lock gesetzt werden kann. Es dürfen auf der Tabelle DML und DDL-Operationen ausgeführt werden, solange diese keine exklusive Sperre auf das Objekt benötigen. Während eines CREATE PROCEDURE-Statement etwa darf die referenzierte Tabelle nicht strukturell geändert werden. Jedoch ist es möglich, dass eine weitere Session gleichzeitig eine Procedure erstellt, welche die selbige Referenz auf die genannte Tabelle besitzt. Wie ein exklusive Lock, dauert die Share DDL Sperre ebenfalls nur während der Laufzeit der jeweiligen Operation an. Nach Beendigung des Statements wird der Lock automatisch gelöst. Breakable Parse Lock Diese Art von Sperre hält einen Lock auf einen entsprechen Bereich der Shared SQL Area während des Parse-Vorgangs eines Objekts (View, Procedure…). In diesem Bereich sind Informationen zu den vorhandenen Abhängigkeiten auf weitere Datenbankobjekte hinterlegt. Die Sperre kann durchbrochen und folglich invalidiert werden, wenn eine kollidierende DDLOperation auf die referenzierenden Datenbankobjekte aufgeführt wird. Diese Art von Sperre wird gehalten, solange der oben genannte Bereich nicht aus dem Shared Pool gealtert ist. System Locks Eine Oracle Datenbank verwendet eine Vielzahl von Sperren, um die interne Datenbank und Memory Struktur zu schützen. Diese Mechanismen sind dem Benutzer unzugänglich, da er keine Kontrolle über deren Auftreten oder Dauer hat. Latches Bei Latches handelt es sich um Serialisierungsmechanismen, welche die Zugriffe von mehreren Benutzern auf Datenbankobjekte, Dateien und geteilte Datenbankstrukturen regeln. Ebenfalls verhindern sie das Auftreten von Memory-Korruption, die aufgrund konkurrierender Zugriffe auftreten können. Im Speziellen unterbinden sie ebenfalls das Auftreten folgender Situationen: - Konkurrierende Modifikation der gleichen Daten von verschiedenen Sessions - Lesen von noch nicht festgeschriebenen Daten - Ausaltern von Daten aus der Memory-Struktur während diese verwendet werden Wenn beispielsweise mehrere Prozesse einer Instanz einen Speicherbereich im Shared Pool allokieren möchten, wird der neu reservierte Teil im Memory durch einen Latch geschützt. Somit wird verhindert, dass mehrere Prozesse gleichzeitig auf den entsprechenden Speicherbereich Zugriff bekommen. Ein Latch in einer Oracle Instanz bzw. Datenbank ist immer nur von sehr kurzer Dauer. Wenn beispielweise das Gehalt eines Mitarbeiters verändert wird, ist es nicht unüblich, dass während dieses Vorgangs mehrere tausend von Latches gesetzt und wieder gelöst werden. Die Dauer, die Größe des gesperrten Bereichs und die Wartezeit auf einen gesetzten Latch sind betriebssystemabhängig. Je größer die Anzahl der Sperren, desto geringer die Möglichkeit, ungehindert Daten zu manipulieren. Zur Veranschaulichung: Wenn etwa die Anzahl von Hard Parses steigt, erhöht sich auch die Menge an Latches im Library Cache. Mutexes Ein Mutex verhindert die Ausalterung oder die Korruption von Objekten im Memory, solange diese im Zugriff sind. Diese Art von Lock hat eine Ähnlichkeit zum Latch. Der Unterschied besteht darin, dass dieser nicht eine Gruppe von Objekten sondern nur einen Teil davon schützt. Ein Mutex hat folgende Vorteile gegenüber des Latches: - Dieser reduziert die Konkurrenz verschiedener Sessions bzw. Prozesse, da er nur einzelne Objekte und nicht eine Gruppe davon schützt. Dies bewirkt, dass auf einem bestimmten Speicherbereich mehr Verbindungen konkurrenzfrei agieren können. - Ein Latch verbraucht mehr Memory. Je nach Betriebssystem wird für einen Latch ca. 100-200B benötigt, für einen Mutex lediglich ein Zehntel davon. Internal Locks Diese haben eine höhere Komplexität als Latches oder Mutexes. Unter folgenden Typen wird unterschieden: - Dictionary cache locks Diese Sperren sind von sehr kurzer Dauer und werden während des Zugriffs oder der Manipulation des Data Dictionary verwendet. Hier wird wie bei den DDL Locks auch zwischen shared und exklusiven Sperren unterschieden. Das Verhalten ähnelt dem der Data Dictionary Locks, nur das diese im Speicher stattfinden. - File und Log Management Locks Diese Locks schützen diverse Dateien einer Oracle Datenbank. Beispielweise verhindert diese Art von Sperre den gleichzeitigen Zugriff mehrerer Prozesse auf ein Control File. Ebenfalls wird durch diesen Typus sichergestellt, dass ein Datafile nicht gleichzeitig an mehreren Datenbanken gemountet sein kann. - Tablespace and Undo Segment Locks Diese Sperre stellt den fehlerfreien Zugriff auf Tablespaces und die Undo Segmente sicher. Es wird damit geregelt, dass nur eine Instanz einer Datenbank in ein einzelnes Segment des Undo Tablespaces schreiben kann. Außerdem werden sie auch dazu verwendet, um über Instanzen hinweg zu bestimmen, ob ein Tablespace den Status Online oder Offline trägt. Locks und Foreign Key Constraints In einem Datenbanksystem wird die Integrität von Daten ebenfalls über Tabellen hinweg sichergestellt. Hierfür werden Beziehungen über Foreign Key Constraints realisiert. Das heißt wenn ein Datensatz in eine „Child“-Tabelle eingefügt wird, müssen bereits alle referenzierten Daten existieren. Wenn ein Mitarbeiten einer neuen Abteilung über eine ID-Spalte zugewiesen wird, muss bereits der entsprechende Satz der Abteilung inklusive ID vorhanden sein. Umgekehrt ist es ebenfalls der Fall: Wenn eine Abteilung gelöscht wird, darf es keinen zugewiesen Mitarbeiter mehr geben. Diese oben beschriebenen Regeln werden durch einen Lock auf die „Child“-Tabelle umgesetzt. Bei der Löschung einer Abteilung wird während der Ausführung des DELETE-Statements ein exklusiver Lock auf die referenzierte Tabelle Mitarbeiter gesetzt. Dadurch wird sichergestellt, dass keine neuen Mitarbeiter mit der zuvor gelöschten Abteilung eingefügt werden können. Oftmals wird für die Optimierung dieses Konstrukts ein Index auf die Foreign Key Spalte der Child Tabelle erstellt. Durch dieses zusätzliche Objekt muss die referenzierte Tabelle zur Sicherstellung der Integrität nicht mehr gesperrt werden. Stattdessen wird bei einer Löschung eines Parent-Datensatzes der gelöschte Wert im Foreign-Key-Index vorbelegt. Somit können keine weiteren Daten mit diesem Referenzschlüssel in die Child-Tabelle eingefügt werden. Die Regel der Index Erstellung auf Foreign Key Constraints kann und darf aber nicht generalisiert werden. Jeder Index, der auf der Child Tabelle angelegt wird, verschlechtert die Performance bei Insert- und Update Operationen. Falls sichergestellt werden kann, dass auf der Parent Tabelle selten oder generell keine Daten gelöscht werden, kann in Erwägung gezogen werden, den Index zugunsten der Performance nicht zu erstellen. Deadlocks Ein Deadlock beschreibt eine Situation, in der zwei oder mehrere Verbindungen auf die Lösung einer Sperre warten, aber der jeweils andere diese inne hält. Diese Vorkommnisse werden durch die Oracle Datenbank automatisch erkannt und durch Beendigung einer der Verbindungen gelöst. Der betroffene Benutzer wird über eine entsprechende Meldung auf SQL Ebene informiert. Zusätzlich wird das Auftreten dieser Situation ins Alert.log der Datenbank protokolliert. Hier ein entsprechender Auszug nach dem Auftreten eines Deadlocks: Es wird ebenfalls der Pfad zum entsprechenden Trace-File angegeben, in dem man detailliertere Informationen über die gelöste Sperre findet. Im oberen Teil dieser Dateien befinden sich zunächst generelle Informationen über das System und genauere Merkmale der Session inklusive deren Service, Module, ClientID und Action-Angaben. Darunter befindet sich der sogenannte „Deadlock Graph“, der Details über die involvierten Sessions aufzeigt. Außerdem sind hier alle gehaltenen und angeforderten Sperren der jeweiligen Verbindungen aufgeführt. Hierbei werden betroffene Objekte mit deren ID in Hexadezimal-Format angegeben. Diese kann man nach einer Konvertierung zum Dezimal-Format als Filterprädikat auf die DBA_OBJECTS verwenden um somit das entsprechende Objekt zu ermitteln. Im nächsten Teil befinden sich genauere Informationen über die weiteren Sessions, die in diesem Deadlock involviert waren. Hier sieht man ebenfalls die ausgeführten Statements inklusiv deren SQL_IDs. Einem Deadlock liegt meistens ein Problem in der Applikation zugrunde. Ein mögliches Szenario könnten verschiedene Applikationsprozesse bilden, die einen gegenläufigen Abarbeitungszyklus besitzen und sich demzufolge gegenseitig bei der Abarbeitung ihrer Queues behindern würden. Da das Auftreten solch einer Situation nicht allein seitens der Datenbank unterbunden werden kann, ist hier eine Zusammenarbeit mit den entsprechenden Applikationsentwicklern gefordert. Erkennung und Analyse von Lock-Situationen Zur Identifizierung und Klassifizierung der Sperren bei einem Problemfall stellt die Oracle Datenbank verschiedene hilfreiche Views zur Verfügung. Hier ein kurzer Auszug der Wichtigsten: GV$LOCK In dieser Abfrage werden alle aktuell gesetzten Sperren der Datenbank aufgelistet. Über die Spalte TYPE wird klassifiziert, um welchen Lock es sich handelt. Außerdem wird in der Spalte BLOCK dargestellt, ob dieser Vorgang einen weiteren Prozess blockiert. GV$LOCK_TYPE Hierin werden alle Typen dargestellt, welche die Spalte TYPE der GV$LOCK annehmen kann. Diese Klassen werden hier mit Beschreibungen und entsprechenden Abkürzungen gelistet. GV$LOCKED_OBJECT Es werden nur Ergebnisse angezeigt, wenn ein weiterer Prozess auf eine gesetzte Sperre wartet. Es werden ebenfalls Identifikationsmerkmal wie die SESSION_ID und die zugehörige OBJECT_ID aufgeführt. GV$LATCH Dort ist eine statistische Übersicht aller Latches in der Datenbank zu sehen. Gruppiert sind diese Daten nach den jeweiligen Typ. V$WAIT_CHAINS Bei der Selektion dieser View werden Informationen über blockierte Sessions anzeigt. Eine Wait Chain ist eine Kette von Verbindungen, die sich aneinander gereiht blockieren. Jede Zeile dieser Abfrage repräsentiert eine blockierte und eine blockende Session. DBA_DML_LOCKS Diese View zeigt alle DML Locks inklusive einiger Session Informationen an. DBA_BLOCKERS Hier werden Verbindungen angezeigt, wenn diese Sperren auf Datenbankobjekte inne halten und somit andere Session blockieren. Falls diese Verbindung ebenfalls auf einen Lock wartet und somit Teil einer Wait Chain ist, wird diese nicht aufgeführt. Bei RAC-Umgebungen werden hier nur Blocker der aktuellen Instanz angezeigt. Detaillierte Abfrage Um eine ganzheitliche Analyse eines Systems durchführen zu können und im Fehlerfall entsprechende Informationen erheben zu können, muss man über mehrere Views und Tabelle selektieren. Im Anhang befindet sich ein SQL-Statement, das bei einer Lock-Problematik diverse Informationen zurückgibt Unter anderem um welche Sperre es sich handelt und welche Objekte involviert sind. Darüber hinaus wird zusätzlich ein Statement ausgegeben, mit dem sich die blockierende Session beenden lässt. Fazit Grundsätzlich sind Sperren in einer Datenbank nötig und werden zur Umsetzung diverser Regeln eines relationalen Datenbanksystem benötigt. Falls jedoch Tabelle-Sperren vermehrt auftreten und zum Problem werden, muss eine ausgiebige Ursachenanalyse betrieben werden. Hierbei kann es vielerlei Ursachen geben, wie Probleme bei der Serialisierung von Applikationsprozessen. Ebenfalls sind strukturelle Ungereimtheiten im Datenbankmodell, beispielsweise ein störender Constraint oder andererseits ein fehlender Index auf dem Foreign Key, eine potenzielle Problemquelle. Auch nachdem eine bestehende Lock-Problematik behoben scheint, kann davon ausgegangen werden, dass die Lösung von Dauer ist. Sobald sich die Bedingungen oder die Anforderungen an das Datenbanksystem ändern, verschiebt sich auch die Belastung. Daraus folgen die Verschiebung des Heatpoints eines Systems und auch die Bereiche für mögliches Auftreten von diversen Locks. Kontaktadresse: Tobias Deml Trivadis GmbH Lehrer-Wirth-Str. 4 D-81928 München Telefon: Fax: E-Mail Internet: +49 89 99 27 59 32 4 +49 89 99 27 59 59 [email protected] www.trivadis.com Anhang SELECT S1.sid "blocking SID", S1.username "blocking user", S1.module "blocking Module", TRIM(S1.action) "blocking Action", S2.sid "blocked SID", S2.module "blocked Module", TRIM(S2.action) "blocked Action", L2.ctime "lock waiting time [s]", S1.event "blocking session waits for", S2.event "blocked session waits for", DECODE(L1.lmode, 0, L1.lmode || ' - no lock', 1, L1.lmode || ' - N/A', 2, L1.lmode || ' - row shared lock (SS)', 3, L1.lmode || ' - row exclusive lock (SX)', 4, L1.lmode || ' - object shared lock (S)', 5, L1.lmode || ' - object shared / row exclusive lock (SSX)', 6, L1.lmode || ' - object exclusive / row exclusive lock (X)', L1.lmode) "lock mode blocking session", DECODE(L2.request, 0, L2.request || ' - no lock', 1, L2.request || ' - N/A', 2, L2.request || ' - row shared lock (SS)', 3, L2.request || ' - row exclusive lock (SX)', 4, L2.request || ' - object shared lock (S)', 5, L2.request || ' - object shared / row exclusive lock (SSX)', 6, L2.request || ' - object exclusive / row exclusive lock (X)', L2.request) "lock request blocked session", L1.type || ' - ' || LT.name "lock type", LT.description "lock type description", ( SELECT SQLT.sql_text FROM GV$SQLText SQLT WHERE SQLT.piece = 0 AND SQLT.address = S2.sql_address AND SQLT.hash_value = S2.sql_hash_value) "blocked session SQL-Text", ( SELECT LISTAGG(AO.object_name, ', ') WITHIN GROUP (ORDER BY AO.object_name) FROM All_Objects AO, GV$Locked_Object LO, GV$Lock L3 WHERE AO.object_id = LO.object_id AND LO.xidSqn = L3.id2 AND LO.session_id = L3.sid AND L3.block = 1 AND L3.sid = L1.sid) "locked object(s)", ( SELECT AO.object_name FROM All_Objects AO WHERE AO.object_id = S2.row_wait_obj#) "waiting object", ( SELECT 'ALTER SYSTEM KILL SESSION ''' || S3.sid || ',' || S3.serial# || ', @' || inst_id || ''' IMMEDIATE;' FROM GV$Session S3 WHERE S3.sid = S1.sid AND ROWNUM = 1) "Kill SQL" FROM GV$Session S2, GV$Lock L2, GV$Lock_Type LT, GV$Lock L1, GV$Session S1 WHERE S2.sid = L2.sid AND L2.request > 0 AND S2.blocking_session = S1.sid AND LT.type = L1.type AND L1.BLOCK = 1 AND L1.sid = S1.sid;