Übermäßige Datenabfrageoperationen in einer großen Datentabelle

Beschreibung

Übermäßige Datenabfrageoperationen in einer großen Datentabelle treten auf, wenn Software Datenabfragen mit einer großen Anzahl von Joins und Unterabfragen auf großen Datentabellen durchführt. CISQ definiert Schwellenwerte wie folgt: Datentabellen mit 1 Million oder mehr Zeilen gelten als "groß", Abfragen sollten mindestens 5 Joins haben, um als problematisch zu gelten, und mindestens 3 Unterabfragen werfen Bedenken auf. Solche Abfragen verbrauchen übermäßige Datenbankressourcen (CPU, Speicher, I/O) und können erhebliche Leistungsverschlechterung verursachen, was möglicherweise zu Denial-of-Service-Bedingungen führt.

Risiko

Obwohl hauptsächlich ein Leistungsproblem, erzeugt übermäßige Abfragekomplexität Sicherheitsschwachstellen. Angreifer, die solche Abfragen auslösen können (selbst durch legitime Anwendungsfunktionen), können Denial-of-Service verursachen, indem sie Datenbankressourcen erschöpfen. Lang laufende Abfragen können Connection-Pool-Erschöpfung verursachen, was alle Benutzer betrifft. Datenbankserver unter hoher Last können sensible Daten durch Fehlermeldungen oder Timeout-Verhalten preisgeben. Langsame Abfragen können auch zu Race Conditions führen, wenn Operationen inkonsistent ablaufen. Zusätzlich sind komplexe Abfragen schwerer auf SQL-Injection oder Datenoffenlegung zu überprüfen.

Lösung

Optimieren Sie Abfragen durch: (1) Hinzufügen geeigneter Indizes für häufig abgefragte Spalten, (2) Aufteilen komplexer Abfragen in mehrere einfachere Abfragen, (3) Verwendung von Datenbank-Views oder materialisierten Views für häufige komplexe Joins, (4) Implementierung von Paginierung für große Ergebnismengen, (5) Verwendung von Query-Caching wo angemessen, (6) Denormalisierung von Daten für leseintensive Operationen, (7) Implementierung von Query-Timeouts zur Verhinderung unkontrollierter Abfragen, (8) Verwendung von Datenbank-Query-Analysatoren zur Identifizierung problematischer Abfragen. Überwachen Sie die Abfrageleistung und setzen Sie Warnungen für langsame Abfragen. Erwägen Sie die Verwendung von Lesereplikaten zur Verteilung der Abfragelast.

Häufige Auswirkungen

AuswirkungDetails
VerfügbarkeitBereich: Verfügbarkeit

DoS: Ressourcenverbrauch - Komplexe Abfragen auf großen Tabellen verbrauchen übermäßig CPU, Speicher und I/O-Ressourcen.
VerfügbarkeitBereich: Verfügbarkeit

DoS: Verstärkung - Eine einzelne Abfrage kann die Gesamtleistung der Datenbank beeinträchtigen und alle Benutzer betreffen.
AndereBereich: Ändere

Leistungsreduktion - Allgemeine Leistungsverschlechterung, die Denial-of-Service-Angriffe ermöglichen kann.

Beispielcode

Anfälliger Code

-- Anfällig: Übermäßige Joins und Unterabfragen auf großen Tabellen
-- Diese Abfrage hat 6 Joins und 4 Unterabfragen

SELECT
    c.customer_id,
    c.name,
    c.email,
    o.order_id,
    o.order_date,
    p.product_name,
    p.category,
    s.supplier_name,
    sh.shipment_date,
    r.review_text
FROM customers c
-- Join 1
JOIN orders o ON c.customer_id = o.customer_id
-- Join 2
JOIN order_items oi ON o.order_id = oi.order_id
-- Join 3
JOIN products p ON oi.product_id = p.product_id
-- Join 4
JOIN suppliers s ON p.supplier_id = s.supplier_id
-- Join 5
LEFT JOIN shipments sh ON o.order_id = sh.order_id
-- Join 6
LEFT JOIN reviews r ON p.product_id = r.product_id AND c.customer_id = r.customer_id
WHERE c.region = 'Nordamerika'
-- Unterabfrage 1
AND o.total_amount > (SELECT AVG(total_amount) FROM orders)
-- Unterabfrage 2
AND p.product_id IN (
    SELECT product_id FROM order_items
    GROUP BY product_id
    HAVING COUNT(*) > 100
)
-- Unterabfrage 3
AND c.customer_id NOT IN (
    SELECT customer_id FROM customer_blacklist
)
-- Unterabfrage 4
AND EXISTS (
    SELECT 1 FROM loyalty_points lp
    WHERE lp.customer_id = c.customer_id
    AND lp.points > 1000
)
ORDER BY o.order_date DESC;

-- Mit Millionen von Zeilen in orders und order_items
-- kann diese Abfrage Minuten dauern und erhebliche Ressourcen verbrauchen
// Anfällig: Aufbau einer komplexen dynamischen Abfrage
public class VulnerableReportGenerator {

    public List<ReportRow> generateComplexReport(ReportCriteria criteria) {
        StringBuilder sql = new StringBuilder();

        // Aufbau einer Abfrage mit vielen Joins
        sql.append("SELECT * FROM transactions t ");
        sql.append("JOIN accounts a ON t.account_id = a.id ");
        sql.append("JOIN customers c ON a.customer_id = c.id ");
        sql.append("JOIN branches b ON a.branch_id = b.id ");
        sql.append("JOIN regions r ON b.region_id = r.id ");
        sql.append("JOIN account_types at ON a.type_id = at.id ");
        sql.append("LEFT JOIN transaction_details td ON t.id = td.transaction_id ");

        // Hinzufügen mehrerer Unterabfragen basierend auf Kriterien
        if (criteria.hasAmountFilter()) {
            sql.append("WHERE t.amount > (SELECT AVG(amount) FROM transactions) ");
        }

        if (criteria.hasVolumeFilter()) {
            sql.append("AND a.id IN (SELECT account_id FROM transactions ");
            sql.append("GROUP BY account_id HAVING COUNT(*) > ?) ");
        }

        // Keine Paginierung, gibt alle passenden Zeilen zurück
        // Mit Millionen von Transaktionen ist dies gefährlich

        return jdbcTemplate.query(sql.toString(), new ReportRowMapper());
    }
}

Korrigierter Code

-- Korrigiert: Optimierte Abfrage mit reduzierter Komplexität

-- Schritt 1: Indizierte Views oder materialisierte Views für häufige Aggregationen verwenden
CREATE MATERIALIZED VIEW mv_order_stats AS
SELECT
    customer_id,
    COUNT(*) as order_count,
    AVG(total_amount) as avg_order_amount,
    SUM(total_amount) as total_spent
FROM orders
GROUP BY customer_id;

-- Schritt 2: In mehrere fokussierte Abfragen aufteilen

-- Abfrage 1: Qualifizierende Kunden ermitteln (mit Index auf region)
SELECT customer_id, name, email
INTO #qualifying_customers
FROM customers c
WHERE c.region = 'Nordamerika'
AND EXISTS (
    SELECT 1 FROM loyalty_points lp
    WHERE lp.customer_id = c.customer_id
    AND lp.points > 1000
)
AND c.customer_id NOT IN (SELECT customer_id FROM customer_blacklist);

-- Abfrage 2: Aktuelle Bestellungen mit Paginierung abrufen
SELECT
    c.customer_id,
    c.name,
    o.order_id,
    o.order_date,
    o.total_amount
FROM #qualifying_customers c
JOIN orders o ON c.customer_id = o.customer_id
JOIN mv_order_stats os ON c.customer_id = os.customer_id
WHERE o.total_amount > (SELECT overall_avg FROM global_stats)
AND os.order_count > 100
ORDER BY o.order_date DESC
OFFSET @page * @pageSize ROWS
FETCH NEXT @pageSize ROWS ONLY;
// Korrigiert: Optimierte Berichterstellung mit Paginierung und Caching
public class FixedReportGenerator {

    private final QueryOptimizer queryOptimizer;
    private final CacheManager cacheManager;

    @Cacheable(value = "reportCache", key = "#criteria.cacheKey()")
    public PagedResult<ReportRow> generateReport(ReportCriteria criteria, Pageable pageable) {

        // Query-Timeout verwenden, um unkontrollierte Abfragen zu verhindern
        Query query = entityManager.createQuery(buildOptimizedQuery(criteria));
        query.setHint("javax.persistence.query.timeout", 30000); // 30 Sekunden Timeout

        // Ergebnisse immer paginieren
        query.setFirstResult(pageable.getOffset());
        query.setMaxResults(pageable.getPageSize());

        List<ReportRow> results = query.getResultList();

        // Anzahl mit separater optimierter Abfrage ermitteln
        long totalCount = getCountWithCache(criteria);

        return new PagedResult<>(results, totalCount, pageable);
    }

    private String buildOptimizedQuery(ReportCriteria criteria) {
        // Query Builder verwenden, der Komplexität begrenzt
        return queryOptimizer.build()
            .select("t.id", "t.amount", "a.accountNumber", "c.name")
            .from("Transaction t")
            .join("t.account a")
            .join("a.customer c")
            .where(criteria.getFilters())
            .maxJoins(3)  // Join-Limit erzwingen
            .build();
    }
}
# Korrigiert: Query-Komplexitätsbegrenzer
class QueryComplexityLimiter:
    """Erzwingt Query-Komplexitätsgrenzen"""

    MAX_JOINS = 4
    MAX_SUBQUERIES = 2
    MAX_RESULT_SIZE = 10000
    QUERY_TIMEOUT = 30  # Sekunden

    def validate_query(self, query_string):
        join_count = query_string.lower().count(' join ')
        subquery_count = query_string.lower().count('select') - 1

        if join_count > self.MAX_JOINS:
            raise QueryTooComplexError(
                f"Abfrage hat {join_count} Joins, maximal erlaubt sind {self.MAX_JOINS}"
            )

        if subquery_count > self.MAX_SUBQUERIES:
            raise QueryTooComplexError(
                f"Abfrage hat {subquery_count} Unterabfragen, maximal sind {self.MAX_SUBQUERIES}"
            )

    def execute_with_limits(self, session, query):
        """Abfrage mit Sicherheitsgrenzen ausführen"""
        # Zeilenlimit hinzufügen
        query = query.limit(self.MAX_RESULT_SIZE)

        # Timeout setzen
        query = query.execution_options(timeout=self.QUERY_TIMEOUT)

        try:
            return query.all()
        except TimeoutError:
            raise QueryTimeoutError("Abfrage hat Zeitlimit überschritten")

CVE-Beispiele

Diese CWE beschreibt ein Leistungsproblem, das zu Denial-of-Service-Schwachstellen beitragen kann. Obwohl nicht direkt spezifischen CVEs zugeordnet, haben Datenbankleistungsprobleme durch komplexe Abfragen zu Verfügbarkeitsvorfällen beigetragen.


Verwandte CWEs

  • CWE-1176: Inefficient CPU Computation (Eltern)
  • CWE-400: Uncontrolled Resource Consumption (kann führen zu)
  • CWE-1006: Bad Coding Practices (Kategoriemitglied)

Referenzen

  1. MITRE Corporation. "CWE-1049: Excessive Data Query Operations in a Large Data Table." https://cwe.mitre.org/data/definitions/1049.html

  2. CISQ. "Automated Source Code Quality Measures."

  3. PostgreSQL Documentation. "Performance Tips."

  4. Oracle. "SQL Tuning Guide."