Größe Datentabelle mit übermäßiger Anzahl von Indizes
Beschreibung
Größe Datentabelle mit übermäßiger Anzahl von Indizes tritt auf, wenn ein Produkt eine große Datentabelle implementiert, die eine übermäßig große Anzahl von Indizes enthält. CISQ legt empfohlene Schwellenwerte fest: Tabellen mit mehr als 1.000.000 Zeilen gelten als "groß", und mehr als 3 Indizes gelten als "übermäßig". Während Indizes die Leseleistung verbessern, haben sie erheblichen Overhead für Schreiboperationen. Jeder Index muss bei jedem INSERT, UPDATE oder DELETE aktualisiert werden, und Indizes verbrauchen Speicherplatz. Bei sehr großen Tabellen verlangsamen übermäßige Indizes Schreiboperationen dramatisch.
Risiko
Übermäßige Indizes auf großen Tabellen haben Sicherheitsimplikationen. Schreiboperationen werden langsam, was Denial-of-Service-Möglichkeiten schafft. Angreifer können langsame Schreibpfade ausnutzen, um Datenbankressourcen zu erschöpfen. Lock-Contention erhöht sich mit mehr Indizes, was Deadlock-Angriffe ermöglicht. Der Speicher-Overhead durch Indizes kann den Festplattenplatz erschöpfen. Index-Wartungsoperationen (Rebuild, Reorganize) dauern länger und können andere Operationen blockieren. Langsame Schreibleistung kann Transaktions-Timeouts und Dateninkonsistenz verursachen. Backup- und Wiederherstellungszeiten erhöhen sich erheblich.
Lösung
Analysieren Sie Abfragemuster, um wirklich notwendige Indizes zu identifizieren. Entfernen Sie unbenutzte Indizes mit Datenbank-Monitoring-Tools. Verwenden Sie zusammengesetzte Indizes anstelle mehrerer Einzelspalten-Indizes. Erwägen Sie partielle Indizes, die nur relevante Zeilen indizieren. Implementieren Sie Covering-Indizes für häufig ausgeführte Abfragen. Verwenden Sie geeignete Indextypen (B-tree, Hash, GiST) basierend auf Abfragemustern. Überwachen Sie Index-Nutzungsstatistiken und entfernen Sie wenig wertvolle Indizes. Erwägen Sie Index-organisierte Tabellen oder Clustered-Indizes sorgfältig. Verwenden Sie Datenbank-Berater, um optimale Indizierung zu empfehlen. Balancieren Sie Lese- vs. Schreibleistungsanforderungen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Verfügbarkeit | Bereich: Verfügbarkeit DoS: Ressourcenverbrauch - Schreiboperationen verbrauchen übermäßige Ressourcen beim Aktualisieren aller Indizes. |
| Verfügbarkeit | Bereich: Verfügbarkeit Reduzierte Leistung - INSERT, UPDATE, DELETE Operationen werden sehr langsam. |
| Andere | Bereich: Ändere Qualitätsverschlechterung - Speicherplatz und Wartungs-Overhead erhöhen sich erheblich. |
Beispielcode
Anfälliger Code
-- Anfällig: Größe Tabelle mit zu vielen Indizes
CREATE TABLE vulnerable_user_activity (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
activity_type VARCHAR(50) NOT NULL,
activity_timestamp TIMESTAMP NOT NULL,
ip_address VARCHAR(45),
user_agent TEXT,
request_path VARCHAR(500),
response_code INT,
processing_time_ms INT,
session_id VARCHAR(100),
country_code CHAR(2),
device_type VARCHAR(20),
browser VARCHAR(50),
os_name VARCHAR(50),
referrer VARCHAR(500),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Tabelle wird 10+ Millionen Zeilen haben
-- Übermäßige Indizes (mehr als 3 auf größer Tabelle)
CREATE INDEX idx_user_id ON vulnerable_user_activity(user_id);
CREATE INDEX idx_activity_type ON vulnerable_user_activity(activity_type);
CREATE INDEX idx_timestamp ON vulnerable_user_activity(activity_timestamp);
CREATE INDEX idx_ip_address ON vulnerable_user_activity(ip_address);
CREATE INDEX idx_session_id ON vulnerable_user_activity(session_id);
CREATE INDEX idx_country ON vulnerable_user_activity(country_code);
CREATE INDEX idx_device ON vulnerable_user_activity(device_type);
CREATE INDEX idx_browser ON vulnerable_user_activity(browser);
CREATE INDEX idx_os ON vulnerable_user_activity(os_name);
CREATE INDEX idx_response_code ON vulnerable_user_activity(response_code);
CREATE INDEX idx_created_at ON vulnerable_user_activity(created_at);
-- 11 Indizes! Jedes INSERT muss ALLE aktualisieren
-- Mit 10M+ Zeilen werden Schreiboperationen extrem langsam
-- Insert-Leistungsauswirkung:
-- Ohne Indizes: ~1000 inserts/Sekunde
-- Mit 11 Indizes: ~50 inserts/Sekunde (oder schlechter)
-- Speicherauswirkung:
-- Daten: 5 GB
-- Indizes: 15+ GB (3x die Daten!)
// Anfällig: ORM mit übermäßigen Indizes definiert
@Entity
@Table(name = "user_activity", indexes = {
// Übermäßig - 10 Indizes auf einer großen Tabelle
@Index(name = "idx_user", columnList = "userId"),
@Index(name = "idx_type", columnList = "activityType"),
@Index(name = "idx_time", columnList = "activityTimestamp"),
@Index(name = "idx_ip", columnList = "ipAddress"),
@Index(name = "idx_session", columnList = "sessionId"),
@Index(name = "idx_country", columnList = "countryCode"),
@Index(name = "idx_device", columnList = "deviceType"),
@Index(name = "idx_browser", columnList = "browser"),
@Index(name = "idx_os", columnList = "osName"),
@Index(name = "idx_response", columnList = "responseCode")
})
public class VulnerableUserActivity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "user_id", nullable = false)
private Long userId;
@Column(name = "activity_type", length = 50, nullable = false)
private String activityType;
@Column(name = "activity_timestamp", nullable = false)
private LocalDateTime activityTimestamp;
// Viele weitere Spalten...
}
// Batch-Insert wird sehr langsam
@Service
public class VulnerableActivityService {
@Transactional
public void logActivities(List<UserActivity> activities) {
// Mit 10 Indizes ist dieses Batch-Insert extrem langsam
// Jedes Insert aktualisiert alle 10 Indizes
for (UserActivity activity : activities) {
entityManager.persist(activity);
}
// 1000 Aktivitäten könnten 30+ Sekunden dauern statt < 1 Sekunde
}
}
Korrigierter Code
-- Korrigiert: Größe Tabelle mit optimierten Indizes
CREATE TABLE fixed_user_activity (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
activity_type VARCHAR(50) NOT NULL,
activity_timestamp TIMESTAMP NOT NULL,
ip_address VARCHAR(45),
user_agent TEXT,
request_path VARCHAR(500),
response_code INT,
processing_time_ms INT,
session_id VARCHAR(100),
country_code CHAR(2),
device_type VARCHAR(20),
browser VARCHAR(50),
os_name VARCHAR(50),
referrer VARCHAR(500),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Korrigiert: Nur 3 sorgfältig ausgewählte Indizes
-- Index 1: Zusammengesetzter Index für häufigstes Abfragemuster
-- Deckt ab: Benutzersuche + Zeitbereich + Aktivitätstypfilterung
CREATE INDEX idx_user_time_type ON fixed_user_activity(user_id, activity_timestamp, activity_type);
-- Index 2: Für Session-basierte Abfragen
CREATE INDEX idx_session ON fixed_user_activity(session_id);
-- Index 3: Für zeitbasierte Bereinigungs-/Archivierungsoperationen
CREATE INDEX idx_timestamp ON fixed_user_activity(activity_timestamp);
-- Das ist alles! Nur 3 Indizes
-- Für andere Abfragemuster, Table-Scan verwenden oder erwägen:
-- 1. Materialized Views für Reporting
-- 2. Separate Analysetabellen
-- 3. Zeitbasierte Partitionierung
-- Partitionierung für sehr große Tabellen (alternativer Ansatz)
CREATE TABLE fixed_user_activity_partitioned (
id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
activity_type VARCHAR(50) NOT NULL,
activity_timestamp TIMESTAMP NOT NULL,
-- andere Spalten...
PRIMARY KEY (id, activity_timestamp)
) PARTITION BY RANGE (activity_timestamp);
-- Partitionen nach Monat erstellen
CREATE TABLE activity_2024_01 PARTITION OF fixed_user_activity_partitioned
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
CREATE TABLE activity_2024_02 PARTITION OF fixed_user_activity_partitioned
FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');
-- usw.
-- Jede Partition hat kleinere Indizes, schnellere Schreiboperationen
// Korrigiert: Optimierte Indizes mit ordnungsgemäßer Strategie
@Entity
@Table(name = "user_activity", indexes = {
// Nur 3 sorgfältig entworfene Indizes
@Index(name = "idx_user_time_type",
columnList = "userId, activityTimestamp, activityType"),
@Index(name = "idx_session", columnList = "sessionId"),
@Index(name = "idx_timestamp", columnList = "activityTimestamp")
})
public class FixedUserActivity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "user_id", nullable = false)
private Long userId;
@Column(name = "activity_type", length = 50, nullable = false)
private String activityType;
@Column(name = "activity_timestamp", nullable = false)
private LocalDateTime activityTimestamp;
@Column(name = "session_id", length = 100)
private String sessionId;
// Andere Spalten ohne individuelle Indizes...
}
@Service
public class FixedActivityService {
private final EntityManager entityManager;
// Korrigiert: Effizientes Batch-Insert
@Transactional
public void logActivities(List<UserActivity> activities) {
int batchSize = 50;
for (int i = 0; i < activities.size(); i++) {
entityManager.persist(activities.get(i));
// Batch-Flush für bessere Leistung
if (i > 0 && i % batchSize == 0) {
entityManager.flush();
entityManager.clear();
}
}
}
// Korrigiert: Für Analyseabfragen, die keine Echtzeitdaten benötigen,
// separate Reporting-Tabelle oder Materialized View verwenden
public List<ActivitySummary> getActivitySummary(Long userId,
LocalDateTime start, LocalDateTime end) {
// Abfrage gegen Materialized View anstelle der Haupttabelle
return entityManager.createQuery(
"SELECT new ActivitySummary(a.activityType, COUNT(a)) " +
"FROM ActivitySummaryView a " +
"WHERE a.userId = :userId " +
"AND a.activityDate BETWEEN :start AND :end " +
"GROUP BY a.activityType",
ActivitySummary.class)
.setParameter("userId", userId)
.setParameter("start", start)
.setParameter("end", end)
.getResultList();
}
}
# Korrigiert: SQLAlchemy mit optimierten Indizes
from sqlalchemy import Column, BigInteger, String, DateTime, Index
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class FixedUserActivity(Base):
"""Benutzeraktivität mit optimierter Indizierung."""
__tablename__ = 'user_activity'
id = Column(BigInteger, primary_key=True)
user_id = Column(BigInteger, nullable=False)
activity_type = Column(String(50), nullable=False)
activity_timestamp = Column(DateTime, nullable=False)
ip_address = Column(String(45))
session_id = Column(String(100))
country_code = Column(String(2))
device_type = Column(String(20))
browser = Column(String(50))
# Weitere Spalten...
__table_args__ = (
# Korrigiert: Nur 3 strategisch entworfene Indizes
# Zusammengesetzter Index für häufiges Abfragemuster
Index('idx_user_time_type', user_id, activity_timestamp, activity_type),
# Session-Suche
Index('idx_session', session_id),
# Zeitbasierte Operationen (Bereinigung, Archivierung)
Index('idx_timestamp', activity_timestamp),
)
class ActivityService:
"""Service mit optimierten Massenoperationen."""
def __init__(self, session):
self._session = session
def log_activities_bulk(self, activities: list) -> None:
"""Effizientes Massen-Insert."""
# bulk_insert_mappings für beste Leistung verwenden
self._session.bulk_insert_mappings(
FixedUserActivity,
[a.to_dict() for a in activities]
)
self._session.commit()
def cleanup_old_activities(self, before_date: datetime) -> int:
"""Löscht alte Aktivitäten mit indiziertem Timestamp."""
# Verwendet idx_timestamp Index
result = self._session.query(FixedUserActivity).filter(
FixedUserActivity.activity_timestamp < before_date
).delete(synchronize_session=False)
self._session.commit()
return result
# Alternative: Zeitreihen-Datenbank für sehr hohes Volumen
# Erwägen Sie TimescaleDB, InfluxDB oder ähnliches für
# Aktivitäts-Logging in großem Maßstab
CVE-Beispiele
Diese CWE ist für direkte CVE-Zuordnung als VERBOTEN markiert, da sie ein Leistungs-/Qualitätsproblem und keine direkte Sicherheitsschwachstelle darstellt.
Verwandte CWEs
- CWE-405: Asymmetric Resource Consumption (Amplification) (Eltern)
- CWE-400: Uncontrolled Resource Consumption (kann führen zu)
- CWE-1067: Excessive Execution of Sequential Searches (verwandt)
Referenzen
- MITRE Corporation. "CWE-1089: Large Data Table with Excessive Number of Indices." https://cwe.mitre.org/data/definitions/1089.html
- CISQ. "Automated Source Code Quality Measures."
- Use The Index, Luke. "Too Many Indexes." https://use-the-index-luke.com/