Skip to content

Analyzers Overview

django-query-doctor ships with seven built-in analyzers. Each one targets a specific category of ORM performance issue, receives fingerprinted query data captured during a request, and returns Prescription objects that describe the problem, its severity, and the exact code change needed to fix it.

How Analyzers Work

The analysis pipeline follows four stages:

  1. INTERCEPT -- the middleware installs an execute_wrapper that records every SQL query together with its Python stack trace.
  2. FINGERPRINT -- each captured query is normalized (literal values replaced with placeholders) and hashed so that structurally identical queries share a single fingerprint.
  3. ANALYZE -- every enabled analyzer receives the full list of fingerprinted queries and inspects them for a specific issue pattern.
  4. REPORT -- the collected Prescription objects are handed to a reporter (console, JSON, or HTML) for output.

Each analyzer subclasses BaseAnalyzer and implements a single method:

class BaseAnalyzer(ABC):
    @abstractmethod
    def analyze(self, queries: list[CapturedQuery]) -> list[Prescription]:
        """Examine captured queries and return prescriptions for any issues found."""
        ...

A Prescription contains:

Field Type Description
issue_type IssueType Enum: N_PLUS_ONE, DUPLICATE_QUERY, MISSING_INDEX, etc.
severity Severity Enum: CRITICAL, WARNING, or INFO
description str Human-readable description of the problem
fix_suggestion str The exact code fix as a string
callsite CallSite Source file path, line number, and function name
query_count int Number of queries involved in this issue
time_saved_ms float Estimated time savings if the fix is applied
fingerprint str SHA-256 fingerprint of the query group
extra dict Additional metadata (e.g., table name, field name)

Built-in Analyzers

Analyzer Detects Default Severity Documentation
N+1 Query Repeated queries caused by accessing related objects inside loops CRITICAL / WARNING nplusone.md
Duplicate Query Identical SQL executed multiple times within the same request WARNING duplicate.md
Missing Index Filters, ordering, or grouping on columns that lack a database index INFO missing-index.md
Fat SELECT Selecting all columns when only a subset is used INFO fat-select.md
QuerySet Evaluation Unintended queryset evaluation patterns such as len() vs .count() WARNING queryset-eval.md
DRF Serializer N+1 queries originating from Django REST Framework serializer nesting WARNING drf-serializer.md
Query Complexity Overly complex SQL with excessive JOINs, subqueries, or aggregations WARNING / CRITICAL query-complexity.md

Disabling Specific Analyzers

By default all analyzers are enabled. To disable one or more, set QUERY_DOCTOR_DISABLED_ANALYZERS in your Django settings:

# settings.py

QUERY_DOCTOR = {
    "DISABLED_ANALYZERS": [
        "query_doctor.analyzers.fat_select.FatSelectAnalyzer",
        "query_doctor.analyzers.query_complexity.QueryComplexityAnalyzer",
    ],
}

You can also disable analyzers on a per-request basis using the @diagnose decorator or the diagnose_queries() context manager:

from query_doctor.decorators import diagnose

@diagnose(disabled_analyzers=["FatSelectAnalyzer"])
def my_view(request):
    ...

Custom Analyzer Plugins

You can write your own analyzer by subclassing BaseAnalyzer and registering it via the QUERY_DOCTOR["EXTRA_ANALYZERS"] setting. See the Custom Plugins Guide for a full walkthrough.