Files
work/notes/nextcloud/Notes/IT-Know-How/datenbanken/Identity Columns.md
T

497 lines
14 KiB
Markdown
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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