Skip to content

Analyzers Overview

django-query-doctor ships with eight 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
SerializerMethodField N+1 queries in DRF SerializerMethodField methods (static AST analysis) WARNING drf-serializer.md
Query Complexity Overly complex SQL with excessive JOINs, subqueries, or aggregations WARNING / CRITICAL query-complexity.md
Write N+1 Repeated single-row INSERT/UPDATE/DELETE statements from a loop WARNING / CRITICAL write-nplusone.md

Disabling Specific Analyzers

By default all analyzers are enabled. To disable one, set its enabled flag under ANALYZERS in your Django settings. The keys are the analyzer config names: nplusone, duplicate, missing_index, fat_select, queryset_eval, complexity, serializer_method, and write_nplusone:

# settings.py

QUERY_DOCTOR = {
    "ANALYZERS": {
        "fat_select": {"enabled": False},
        "complexity": {"enabled": False},
    },
}

There is no per-request disable mechanism: @diagnose and diagnose_queries() take no arguments and run every enabled analyzer.

Custom Analyzer Plugins

You can write your own analyzer by subclassing BaseAnalyzer and registering it via the query_doctor.analyzers entry point group in your package's pyproject.toml. See the Custom Plugins Guide for a full walkthrough.