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

AuswirkungDetails
VerfügbarkeitBereich: Verfügbarkeit

DoS: Ressourcenverbrauch - Schreiboperationen verbrauchen übermäßige Ressourcen beim Aktualisieren aller Indizes.
VerfügbarkeitBereich: Verfügbarkeit

Reduzierte Leistung - INSERT, UPDATE, DELETE Operationen werden sehr langsam.
AndereBereich: Ä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

  1. MITRE Corporation. "CWE-1089: Large Data Table with Excessive Number of Indices." https://cwe.mitre.org/data/definitions/1089.html
  2. CISQ. "Automated Source Code Quality Measures."
  3. Use The Index, Luke. "Too Many Indexes." https://use-the-index-luke.com/