EXPLAIN QUERY PLAN în SQLite

Indexuri și performanță SQLite 3.24.0 (formatul arbore)

Arată cum va executa SQLite o interogare: ce tabele citește în întregime (SCAN) și unde folosește un index (SEARCH).

Sintaxă
EXPLAIN QUERY PLAN interogare;
.eqp on   -- în sqlite3: afișează planul înaintea fiecărei interogări

Ce face EXPLAIN QUERY PLAN

Pui EXPLAIN QUERY PLAN în fața unui SELECT, UPDATE sau DELETE și SQLite nu execută interogarea: afișează planul ales. Utilitarul sqlite3 îl desenează ca un arbore sub titlul QUERY PLAN.

Cuvintele care contează: SCAN tabel înseamnă că tabelul este citit rând cu rând; SEARCH tabel USING INDEX nume (col=?) înseamnă că se folosește indexul; USING COVERING INDEX înseamnă că toate coloanele necesare sunt în index, deci tabelul nici nu mai este deschis; USE TEMP B-TREE FOR ORDER BY înseamnă o sortare suplimentară, pe care un index potrivit o poate evita.

Textul exact al planului nu este garantat între versiuni și se poate schimba după ANALYZE sau după numărul de rânduri. Folosește-l pentru a înțelege și a compara variante, nu în cod care îl citește automat. Simplu EXPLAIN (fără QUERY PLAN) afișează instrucțiunile mașinii virtuale SQLite, utile mai ales pentru depanarea SQLite însuși.

Exemple

Exemplele de pe această pagină pornesc de la datele de mai jos. Rulează mai întâi acest cod, apoi exemplul.

CREATE TABLE produse (
  id INTEGER PRIMARY KEY,
  nume TEXT NOT NULL,
  categorie TEXT,
  pret INTEGER,
  stoc INTEGER
);
INSERT INTO produse (nume, categorie, pret, stoc) VALUES
  ('Laptop', 'Electronice', 3500, 4),
  ('Telefon', 'Electronice', 2200, 10),
  ('Birou', 'Mobilier', 800, 3),
  ('Scaun', 'Mobilier', 450, 12),
  ('Caiet', 'Papetărie', 12, 200),
  ('Pix', 'Papetărie', 5, 500);

Exemplul 1: SCAN și sortare temporară

Nu există index pe categorie sau pret: SQLite citește tot tabelul și sortează rezultatul separat.

EXPLAIN QUERY PLAN
SELECT nume, pret FROM produse
WHERE categorie = 'Mobilier'
ORDER BY pret;

Rezultat

QUERY PLAN
|--SCAN produse
`--USE TEMP B-TREE FOR ORDER BY

Exemplul 2: Un index care rezolvă și filtrul, și sortarea

Indexul pe (categorie, pret) găsește produsele din categorie deja ordonate după preț, deci sortarea dispare din plan.

CREATE INDEX idx_cat_pret ON produse (categorie, pret);

EXPLAIN QUERY PLAN
SELECT nume, pret FROM produse
WHERE categorie = 'Mobilier'
ORDER BY pret;

Rezultat

QUERY PLAN
`--SEARCH produse USING INDEX idx_cat_pret (categorie=?)

Exemplul 3: Index care acoperă interogarea și căutarea după cheie

Când interogarea cere doar coloane din index, apare COVERING INDEX. Căutarea după id (care este INTEGER PRIMARY KEY, adică rowid) apare ca INTEGER PRIMARY KEY (rowid=?).

CREATE INDEX idx_cat_pret ON produse (categorie, pret);

EXPLAIN QUERY PLAN
SELECT pret FROM produse WHERE categorie = 'Papetărie';

EXPLAIN QUERY PLAN
SELECT nume FROM produse WHERE id = 3;

Rezultat

QUERY PLAN
`--SEARCH produse USING COVERING INDEX idx_cat_pret (categorie=?)
QUERY PLAN
`--SEARCH produse USING INTEGER PRIMARY KEY (rowid=?)

Greșeli frecvente

  • Testezi planul pe un tabel aproape gol și tragi concluzii pentru producție. Cu câteva rânduri, SQLite poate alege altfel; rulează și ANALYZE pe date reale.
  • Confunzi EXPLAIN cu EXPLAIN QUERY PLAN. Primul afișează zeci de instrucțiuni de nivel jos (opcodes), al doilea planul ușor de citit.

Comenzi înrudite

  • CREATE INDEX: Creează un index pe una sau mai multe coloane, ca SQLite să găsească rândurile fără să citească tot tabelul.
  • Index parțial (CREATE INDEX ... WHERE): Un index care conține doar rândurile care îndeplinesc o condiție: mai mic, mai rapid de actualizat și util pentru reguli de unicitate.
  • Index pe expresii: Indexează rezultatul unei expresii, de exemplu lower(email) sau un câmp JSON, ca să cauți rapid după valoarea calculată.
  • rowid și INTEGER PRIMARY KEY: Orice tabel SQLite obișnuit are o coloană ascunsă rowid; o coloană INTEGER PRIMARY KEY devine alias al ei și primește automat valori.