497 lines
14 KiB
Markdown
Executable File
497 lines
14 KiB
Markdown
Executable File
#datenbank
|
||
|
||
➡️ [[#Zusammenfassung]]
|
||
|
||
---
|
||
## 1. Grundidee: Was ist eine Identity Column?
|
||
|
||
Stell dir eine Tabelle „Kunden“ vor. Jeder Kunde soll eine eindeutige Nummer bekommen:
|
||
|
||
- Kunde 1
|
||
- Kunde 2
|
||
- Kunde 3
|
||
- …
|
||
|
||
Du willst diese Nummern **nicht selbst vergeben**, sondern die Datenbank soll das **automatisch** machen, wenn du einen neuen Datensatz einfügst.
|
||
|
||
Genau das ist eine **Identity Column**:
|
||
|
||
> Eine Identity Column ist eine Spalte, deren Wert von der Datenbank automatisch erzeugt wird, meist als laufende Nummer (1, 2, 3, …). Sie wird oft als Primärschlüssel benutzt.
|
||
|
||
Typische Eigenschaften:
|
||
|
||
- numerisch (z. B. `INT`, `BIGINT`)
|
||
- beim `INSERT` wird kein Wert angegeben – die DB füllt ihn selbst
|
||
- der Wert ist pro Tabelle eindeutig
|
||
- er steigt (meist) monoton an (1,2,3,…), kann aber Lücken haben
|
||
|
||
---
|
||
|
||
## 2. Ein einfaches Beispiel
|
||
|
||
Beispiel in **SQL Server**:
|
||
|
||
```sql
|
||
CREATE TABLE Kunde (
|
||
KundeID INT IDENTITY(1,1) PRIMARY KEY, -- Start bei 1, Inkrement 1
|
||
Name NVARCHAR(100) NOT NULL,
|
||
Email NVARCHAR(200) NOT NULL
|
||
);
|
||
|
||
-- Einfügen ohne KundeID anzugeben:
|
||
INSERT INTO Kunde (Name, Email)
|
||
VALUES ('Anna Beispiel', 'anna@example.com'),
|
||
('Max Muster', 'max@muster.de');
|
||
|
||
-- Auslesen:
|
||
SELECT * FROM Kunde;
|
||
```
|
||
|
||
Ergebnis (vereinfacht):
|
||
|
||
```text
|
||
KundeID | Name | Email
|
||
--------+----------------+-------------------
|
||
1 | Anna Beispiel | anna@example.com
|
||
2 | Max Muster | max@muster.de
|
||
```
|
||
|
||
Du hast `KundeID` nie selbst gesetzt – die Datenbank hat sie erzeugt.
|
||
|
||
---
|
||
|
||
## 3. Abgrenzung zu verwandten Begriffen
|
||
|
||
### 3.1 Identity Column vs. Primärschlüssel
|
||
|
||
- **Primärschlüssel (Primary Key)**:
|
||
Konzept: eine oder mehrere Spalten, die **eindeutig** einen Datensatz identifizieren.
|
||
- **Identity Column**:
|
||
Mechanismus: eine Spalte, deren Werte automatisch generiert werden.
|
||
|
||
Eine Identity Column **kann** der Primärschlüssel sein, muss aber nicht. Man kann auch:
|
||
|
||
- Identity Column + separater fachlicher Schlüssel (z. B. Kundennummer im ERP)
|
||
- Primärschlüssel auf einer anderen Spalte (z. B. E-Mail) ohne Identity Column
|
||
|
||
In der Praxis: Sehr häufig ist die Identity Column = Primärschlüssel.
|
||
|
||
---
|
||
|
||
### 3.2 Identity Column vs. Auto-Increment
|
||
|
||
Viele Systeme benutzen unterschiedliche Begriffe:
|
||
|
||
- SQL Server: `IDENTITY`
|
||
- PostgreSQL: `GENERATED AS IDENTITY` (früher `SERIAL`)
|
||
- MySQL: `AUTO_INCREMENT`
|
||
- Oracle: Identity-Spalten oder separate `SEQUENCE`
|
||
|
||
**Inhaltlich** ist „Identity Column“ oft gleichbedeutend mit „Auto-Increment-Spalte“: eine automatisch hochzählende Spalte.
|
||
|
||
PostgreSQL-Beispiel:
|
||
|
||
```sql
|
||
CREATE TABLE kunde (
|
||
kunde_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||
name TEXT NOT NULL,
|
||
email TEXT NOT NULL
|
||
);
|
||
```
|
||
|
||
MySQL-Beispiel:
|
||
|
||
```sql
|
||
CREATE TABLE kunde (
|
||
kunde_id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
name VARCHAR(100) NOT NULL,
|
||
email VARCHAR(200) NOT NULL
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
### 3.3 Identity Column vs. Sequence
|
||
|
||
**Sequence (Sequenz)** ist ein **eigenständiges Datenbankobjekt**, das Zahlen generiert, etwa so:
|
||
|
||
```sql
|
||
CREATE SEQUENCE kunde_seq START WITH 1 INCREMENT BY 1;
|
||
|
||
SELECT nextval('kunde_seq'); -- gibt 1 zurück
|
||
SELECT nextval('kunde_seq'); -- gibt 2 zurück
|
||
```
|
||
|
||
Eine Identity Column ist oft intern an eine Sequence gekoppelt.
|
||
Unterschied:
|
||
|
||
- Sequence: unabhängig von Tabellen, du kannst sie überall verwenden
|
||
- Identity Column: an genau eine Spalte gebunden, automatischer Einsatz bei `INSERT`
|
||
|
||
In einigen DB-Systemen (z. B. Oracle, PostgreSQL) sind Identity Columns technisch eine bequeme Hülle um eine Sequence.
|
||
|
||
---
|
||
|
||
### 3.4 Identity Column vs. natürlicher/fachlicher Schlüssel
|
||
|
||
- **Natürlicher/fachlicher Schlüssel**: basiert auf echten Geschäfts-Daten
|
||
Beispiele:
|
||
- E-Mail-Adresse als eindeutiger Schlüssel für Benutzer
|
||
- ISBN für Bücher
|
||
- Personalnummer aus dem HR-System
|
||
|
||
- **Surrogate Key** (hier passt die Identity Column): technischer, künstlicher Schlüssel, ohne fachliche Bedeutung.
|
||
|
||
Identity Columns sind **Surrogate Keys**:
|
||
- `KundeID = 42` sagt fachlich nichts über den Kunden aus
|
||
- sie existiert nur, um Zeilen eindeutig zu identifizieren
|
||
|
||
In vielen Projekten arbeitet man mit:
|
||
|
||
- Identity Column als Primärschlüssel
|
||
- dazu Unique-Constraints auf fachlichen Spalten (z. B. `Email UNIQUE`)
|
||
|
||
---
|
||
|
||
### 3.5 Identity Column vs. UUID / GUID
|
||
|
||
Statt einer Identity Column (INT) kann man auch **UUIDs** (z. B. `UUID`, `uniqueidentifier`) als Primärschlüssel verwenden.
|
||
|
||
Unterschiede:
|
||
|
||
- **Identity (INT/BIGINT)**:
|
||
- kleiner, effizienter Index
|
||
- gut lesbar (`KundeID = 123`)
|
||
- nicht global eindeutig über mehrere Systeme, nur innerhalb der Tabelle
|
||
- lässt leicht Rückschlüsse auf Anzahl/Abfolge (z. B. „wir sind bei Kunde 10.000“)
|
||
|
||
- **UUID**:
|
||
- sehr große, „zufällig“ wirkende Zeichenfolge
|
||
- global eindeutig (sehr hoch wahrscheinlich)
|
||
- schwerer zu merken/lesen
|
||
- Indexe sind meist größer, Einfügen kann teurer sein
|
||
|
||
Z. B. in PostgreSQL:
|
||
|
||
```sql
|
||
CREATE TABLE kunde (
|
||
kunde_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
name TEXT NOT NULL
|
||
);
|
||
```
|
||
|
||
Identity Column und UUID sind also alternative Strategien zur ID-Erzeugung.
|
||
|
||
---
|
||
|
||
## 4. Welche Probleme löst eine Identity Column?
|
||
|
||
### 4.1 Eindeutige Identifikation von Datensätzen
|
||
|
||
Jede Zeile bekommt automatisch eine eindeutige ID:
|
||
|
||
- kein Risiko, dass zwei Datensätze „versehentlich“ dieselbe ID haben
|
||
- Voraussetzung für saubere Joins, Fremdschlüssel, Referenzen
|
||
|
||
Beispiel: Fremdschlüssel von Auftrag auf Kunde:
|
||
|
||
```sql
|
||
CREATE TABLE Kunde (
|
||
KundeID INT IDENTITY(1,1) PRIMARY KEY,
|
||
Name NVARCHAR(100) NOT NULL
|
||
);
|
||
|
||
CREATE TABLE Auftrag (
|
||
AuftragID INT IDENTITY(1,1) PRIMARY KEY,
|
||
KundeID INT NOT NULL,
|
||
Betrag DECIMAL(10,2) NOT NULL,
|
||
FOREIGN KEY (KundeID) REFERENCES Kunde(KundeID)
|
||
);
|
||
```
|
||
|
||
Beim Anlegen eines Auftrags verweist `Auftrag.KundeID` auf `Kunde.KundeID`.
|
||
|
||
---
|
||
|
||
### 4.2 Vereinfachung beim Einfügen von Daten
|
||
|
||
Ohne Identity Column müsstest du bei jedem `INSERT` einen neuen eindeutigen Wert berechnen und eintragen – in Mehrbenutzer-Umgebungen ist das fehleranfällig.
|
||
|
||
Mit Identity Column:
|
||
|
||
```sql
|
||
INSERT INTO Kunde (Name) VALUES ('Lisa Test'); -- ID macht die DB
|
||
```
|
||
|
||
Du musst dir keine Gedanken machen über:
|
||
|
||
- die nächste freie Nummer
|
||
- Race Conditions (zwei Nutzer generieren zufällig dieselbe ID)
|
||
- Sperren von Tabellen, etc.
|
||
|
||
---
|
||
|
||
### 4.3 Unterstützung von Mehrbenutzerbetrieb
|
||
|
||
In einer Datenbank arbeiten meist viele Nutzer oder Prozesse parallel. Identity Columns sind so implementiert, dass:
|
||
|
||
- gleichzeitig eingefügte Zeilen **ohne Kollision** IDs bekommen
|
||
- du dich nicht um Synchronisation kümmern musst
|
||
|
||
Die Datenbank regelt intern:
|
||
|
||
- Sperren der Sequenz
|
||
- Transaktionssicherheit
|
||
|
||
---
|
||
|
||
### 4.4 Gute Performance und einfache Indizierung
|
||
|
||
Numerische Identity-Spalten:
|
||
|
||
- sind kompakt (z. B. 4 oder 8 Byte)
|
||
- lassen sich effizient indexieren
|
||
- wachsen meist monoton -> B-Tree-Indizes funktionieren sehr performant
|
||
|
||
Im Vergleich:
|
||
- komplexe Primärschlüssel aus mehreren Textspalten sind größer, langsamer
|
||
- Identity-Spalte als „technischer Schlüssel“ macht viele Operationen schneller
|
||
|
||
---
|
||
|
||
## 5. Herausforderungen und typische Fallstricke
|
||
|
||
### 5.1 Lücken in der Nummernfolge
|
||
|
||
Anfänger erwarten oft: „Wenn ich einen Datensatz lösche oder eine Transaktion zurückrolle, füllt die DB die Nummer wieder auf.“
|
||
|
||
Das **passiert nicht** (und sollte auch nicht passieren).
|
||
|
||
Gründe für Lücken:
|
||
|
||
- Transaktion wird zurückgerollt nach dem Erzeugen einer ID
|
||
- Datensatz mit ID 10 wird gelöscht
|
||
- Parallel eingefügte Datensätze
|
||
|
||
Beispiel:
|
||
- du fügst einen Datensatz ein -> bekommt ID 10
|
||
- im gleichen Moment ein zweiter -> ID 11
|
||
- deine Transaktion schlägt fehl -> Datensatz 10 existiert nicht
|
||
- Datensatz 11 bleibt erhalten
|
||
|
||
Ergebnis: Es gibt jetzt mitunter keinen Datensatz mit ID 10. Das ist normal.
|
||
|
||
**Wichtige Praxisregel**:
|
||
Eine Identity Column ist eine **technische ID**, keine „lückenlose Rechnungsnummer“, keine „fortlaufende Kundennummer“ im rechtlichen Sinn.
|
||
|
||
Wenn du wirklich lückenlose Nummern brauchst (z. B. für Rechnungen), wird das typischerweise anders gelöst (spezialisierte Logik, Sperren, eigene Tabellen).
|
||
|
||
---
|
||
|
||
### 5.2 „ID = Reihenfolge“ ist gefährlich
|
||
|
||
Menschen neigen dazu zu denken:
|
||
|
||
> „ID 100 wurde nach ID 99 erstellt.“
|
||
|
||
In der Praxis ist das oft, aber **nicht garantiert**:
|
||
|
||
- Backups/Restores
|
||
- Replikation
|
||
- Imports aus anderen Systemen
|
||
- unterschiedliche Identity-Strategien
|
||
|
||
Besser: Wenn du die zeitliche Reihenfolge brauchst, verwende eine **Zeitstempel-Spalte**:
|
||
|
||
```sql
|
||
CREATE TABLE Kunde (
|
||
KundeID INT IDENTITY(1,1) PRIMARY KEY,
|
||
Name NVARCHAR(100),
|
||
CreatedAt DATETIME2 NOT NULL DEFAULT SYSDATETIME()
|
||
);
|
||
```
|
||
|
||
Dann sortierst du nach `CreatedAt`, nicht nach `KundeID`.
|
||
|
||
---
|
||
|
||
### 5.3 Limits des Datentyps (Überlauf)
|
||
|
||
Wenn du `INT` (32-Bit) verwendest, ist irgendwann Schluss (ca. 2,1 Milliarden positive Werte). Bei sehr großen Tabellen oder sehr intensiver Nutzung kann das relevant werden.
|
||
|
||
Praktischer Tipp:
|
||
|
||
- lieber direkt `BIGINT` für Identity-Spalten verwenden – die Grenze ist so hoch, dass du in normalen Anwendungen nicht anstößt.
|
||
|
||
Beispiel:
|
||
|
||
```sql
|
||
CREATE TABLE Bestellung (
|
||
BestellungID BIGINT IDENTITY(1,1) PRIMARY KEY,
|
||
...
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
### 5.4 Datenmigrationen, Importe, Reseeding
|
||
|
||
Problemfälle:
|
||
|
||
- du willst Daten von einem System ins andere migrieren
|
||
- dort gibt es schon Datensätze mit Identity-Werten
|
||
- beim Import dürfen keine IDs kollidieren
|
||
|
||
Strategien:
|
||
|
||
1. **Identity-Werte mit übernehmen**
|
||
In vielen DB-Systemen kann man temporär eigene Werte setzen:
|
||
|
||
SQL Server:
|
||
```sql
|
||
SET IDENTITY_INSERT Kunde ON;
|
||
|
||
INSERT INTO Kunde (KundeID, Name, Email)
|
||
VALUES (1001, 'Imported User', 'import@example.com');
|
||
|
||
SET IDENTITY_INSERT Kunde OFF;
|
||
```
|
||
|
||
2. **Nach dem Import die Identity „neu starten“ (reseeden)**
|
||
SQL Server:
|
||
```sql
|
||
DBCC CHECKIDENT ('Kunde', RESEED, 2000); -- nächste ID = 2001
|
||
```
|
||
|
||
Diese Themen werden wichtig, wenn du Daten zwischen Systemen hin- und herschiebst.
|
||
|
||
---
|
||
|
||
### 5.5 Verteilte Systeme / Sharding
|
||
|
||
In modernen Architekturen gibt es manchmal:
|
||
|
||
- mehrere Datenbankserver (Shards)
|
||
- die später zusammengeführt werden sollen
|
||
|
||
Wenn jede Tabelle eine Identity-Spalte bei 1 beginnen lässt, kann es bei Merge-Vorgängen Kollisionen geben (z. B. `KundeID = 100` existiert auf zwei Servern mit unterschiedlichen Kunden).
|
||
|
||
Lösungen:
|
||
|
||
- pro Server unterschiedliche Startwerte und Inkremente:
|
||
- Server A: `IDENTITY(1, 3)` → 1,4,7,…
|
||
- Server B: `IDENTITY(2, 3)` → 2,5,8,…
|
||
- Server C: `IDENTITY(3, 3)` → 3,6,9,…
|
||
- oder Nutzung von UUIDs statt Identity-Spalten
|
||
|
||
---
|
||
|
||
### 5.6 Informationsleck (Datenschutz, Sicherheit)
|
||
|
||
Außen sichtbare IDs (z. B. in URLs) können Informationen verraten:
|
||
|
||
- wenn dein Kunde eine URL wie `/bestellung/1000` sieht, kann er ahnen, dass es ~1000 Bestellungen gibt
|
||
- er kann versuchen, `/bestellung/999` aufzurufen (ID-Raten)
|
||
|
||
Das ist kein Problem der Identity-Spalte an sich, sondern der Entscheidung, **diese ID außerhalb der Anwendung sichtbar** zu machen.
|
||
|
||
Lösungen:
|
||
|
||
- Zugriffskontrollen („zeige nur eigene Bestellungen“)
|
||
- andere, „nicht-erratbare“ IDs für externe Darstellung (z. B. UUIDs, Hashes)
|
||
|
||
---
|
||
|
||
### 5.7 Portabilität zwischen Datenbanksystemen
|
||
|
||
Jede Datenbank hat leicht andere Syntax:
|
||
|
||
- SQL Server: `INT IDENTITY(1,1)`
|
||
- PostgreSQL: `GENERATED ALWAYS AS IDENTITY`
|
||
- MySQL: `AUTO_INCREMENT`
|
||
- Oracle: `GENERATED BY DEFAULT AS IDENTITY` oder Sequences
|
||
|
||
Wenn du Wert auf **Portabilität** legst (dass dein Schema in mehreren DB-Systemen läuft), musst du darauf achten:
|
||
|
||
- eher Standard-SQL (`GENERATED [ALWAYS|BY DEFAULT] AS IDENTITY`)
|
||
- oder ID-Generierung in der Anwendung selbst
|
||
|
||
---
|
||
|
||
## 6. Praxisnahe Gesamtbeispiele
|
||
|
||
### 6.1 Einfache Kunden- und Bestellverwaltung (SQL Server-Variante)
|
||
|
||
```sql
|
||
CREATE TABLE Kunde (
|
||
KundeID INT IDENTITY(1,1) PRIMARY KEY,
|
||
Name NVARCHAR(100) NOT NULL,
|
||
Email NVARCHAR(200) NOT NULL UNIQUE,
|
||
CreatedAt DATETIME2 NOT NULL DEFAULT SYSDATETIME()
|
||
);
|
||
|
||
CREATE TABLE Bestellung (
|
||
BestellungID INT IDENTITY(1,1) PRIMARY KEY,
|
||
KundeID INT NOT NULL,
|
||
Datum DATETIME2 NOT NULL DEFAULT SYSDATETIME(),
|
||
Betrag DECIMAL(10, 2) NOT NULL,
|
||
FOREIGN KEY (KundeID) REFERENCES Kunde(KundeID)
|
||
);
|
||
|
||
-- Neuen Kunden anlegen:
|
||
INSERT INTO Kunde (Name, Email)
|
||
VALUES ('Anna Beispiel', 'anna@example.com');
|
||
|
||
-- Die ID des eben eingefügten Kunden holen:
|
||
SELECT SCOPE_IDENTITY() AS NeueKundeID;
|
||
```
|
||
|
||
`SCOPE_IDENTITY()` gibt dir in SQL Server die zuletzt erzeugte Identity in der aktuellen Session und dem aktuellen Scope. Damit kannst du *direkt danach* eine Bestellung für genau diesen Kunden anlegen.
|
||
|
||
---
|
||
|
||
### 6.2 Beispiel in PostgreSQL (moderner Standard)
|
||
|
||
```sql
|
||
CREATE TABLE kunde (
|
||
kunde_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||
name TEXT NOT NULL,
|
||
email TEXT NOT NULL UNIQUE,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||
);
|
||
|
||
CREATE TABLE bestellung (
|
||
bestellung_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||
kunde_id BIGINT NOT NULL REFERENCES kunde(kunde_id),
|
||
datum TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||
betrag NUMERIC(10,2) NOT NULL
|
||
);
|
||
|
||
-- Einfügen:
|
||
INSERT INTO kunde (name, email)
|
||
VALUES ('Max Muster', 'max@muster.de')
|
||
RETURNING kunde_id; -- PostgreSQL-spezifisch, um die ID direkt zu bekommen
|
||
```
|
||
|
||
---
|
||
|
||
## Zusammenfassung
|
||
|
||
- Eine **Identity Column** ist eine Spalte, deren Wert die Datenbank automatisch erzeugt (typisch: laufende Nummer).
|
||
- Sie wird häufig als **Primärschlüssel** verwendet, ist aber **nicht dasselbe** wie der Begriff „Primärschlüssel“.
|
||
- Sie löst v. a. diese Probleme:
|
||
- eindeutige Identifikation von Zeilen
|
||
- einfache Inserts, ohne selbst IDs zu erzeugen
|
||
- sichere Nutzung in Mehrbenutzer-Umgebungen
|
||
- gute Performance bei Indexen und Joins
|
||
- Sie ist verwandt mit:
|
||
- Auto-Increment-Spalten (`AUTO_INCREMENT`, `SERIAL`)
|
||
- Sequences (`SEQUENCE`)
|
||
- Surrogate Keys im Gegensatz zu natürlichen Schlüsseln
|
||
- Alternativen wie UUID/GUID
|
||
- Typische Herausforderungen:
|
||
- Lücken in der Nummernfolge (normal und erwünscht)
|
||
- ID ist nicht zuverlässig die zeitliche Reihenfolge
|
||
- Datenmigration, Reseeding, Sharding
|
||
- potenzielles Informationsleck, wenn IDs öffentlich sichtbar sind
|
||
- Unterschiede in der Implementierung zwischen Datenbanksystemen |