ЁЯЧГя╕П рдзрдбрд╛ 10 тАФ Database query performance: рдкреНрд░рддреНрдпреЗрдХ рдкрддреНрд░рдХ рд╡рд╛рдЪрд╛рдпрдЪреЗ, рдХреА index рд╡рд╛рдкрд░рд╛рдпрдЪрд╛?
ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 10 ┬╖ рдорд╛рдЧреЗ: lesson-09-queueing ┬╖ рдкреБрдвреЗ: lesson-11-web-performance
ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ
рдзрдбреЗ 01тАУ09, рдЖрдгрд┐ query plans: database query рдЪрд╛рд▓рд╡рдгреНрдпрд╛рдЖрдзреА рддреА рдХрд╢реА рдЪрд╛рд▓рд╡рд╛рдпрдЪреА рдпрд╛рдЪрд╛ рдЖрд░рд╛рдЦрдбрд╛ рдХрд░рддреЛ.
SQLite рдордзреАрд▓ EXPLAIN QUERY PLAN (PostgreSQL рдЖрдгрд┐ MySQL рдордзреНрдпреЗ EXPLAIN) рддреЛ рдЖрд░рд╛рдЦрдбрд╛ рджрд╛рдЦрд╡рддреЛ: рдкреНрд░рддреНрдпреЗрдХ
row рдЪрд╛ scan, рдХреА index рдордзреВрди search. рд╣рд╛ рдзрдбрд╛ SQLite рдордзрд▓реЗ рдЦрд░реЗ plans рд╡рд╛рдЪрддреЛ, indexes, рдПрдХ
covering index рдЖрдгрд┐ рдПрдХ expression index рдЬреЛрдбрддреЛ, рдЖрдгрд┐ index рдХрд╢рд╛рдореБрд│реЗ рд▓рдкрддреЛ рддреЗ рджрд╛рдЦрд╡рддреЛ.
perf/demo.py рдордзреАрд▓ database() рдЖрдгрд┐ perf/sim.py рдордзреАрд▓
laps_db рдЖрдгрд┐ plan.
ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ
Results office рдордзреНрдпреЗ рдПрдХрд╛ рдореЛрдареНрдпрд╛ рдврд┐рдЧрд╛рдд 10,000 lap рдкрддреНрд░рдХреЗ рдЖрд╣реЗрдд. ЁЯУЪ рдПрдХ рдкрд╛рд▓рдХ рд╡рд┐рдЪрд╛рд░рддрд╛рдд: "рдзрд╛рд╡рдкрдЯреВ 42 рдЪреЗ laps рдХрд╛рдп рд╣реЛрддреЗ?"
- Index рдирд╕рддрд╛рдирд╛: рдРрд╢реНрд╡рд░реНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ рдкрддреНрд░рдХ рдПрдХреЗрдХ рдХрд░реВрди рдЙрдЪрд▓рддреЗ, рдЖрдгрд┐ рдзрд╛рд╡рдкрдЯреВ 42 рдЪреА рдкрддреНрд░рдХреЗ рдмрд╛рдЬреВрд▓рд╛ рдареЗрд╡рддреЗ. 20 рд╢реЛрдзрдгреНрдпрд╛рд╕рд╛рдареА рд╕рдЧрд│реА 10,000 рдкрддреНрд░рдХреЗ. рд╣рд╛ scan.
- Index рдЕрд╕рддрд╛рдирд╛: рдХрддрд░рд┐рдирд╛рдиреЗ рдзрд╛рд╡рдкрдЯреВ рдХреНрд░рдорд╛рдВрдХрд╛рдиреБрд╕рд╛рд░ рд▓рд╛рд╡рд▓реЗрд▓реА card box рдмрдирд╡рд▓реА рдЖрд╣реЗ. ЁЯЧВя╕П рдРрд╢реНрд╡рд░реНрдпрд╛ рдереЗрдЯ "42" рд╡рд░ рдЬрд╛рддреЗ, рдЖрдгрд┐ cards рдиреЗрдордХреА рдХреЛрдгрддреА 20 рдкрддреНрд░рдХреЗ рдЖрдгрд╛рдпрдЪреА рддреЗ рд╕рд╛рдВрдЧрддрд╛рдд. рд╣рд╛ index рд╡рд╛рдкрд░реВрди search.
- рдЕрдзрд┐рдХ рдЪрд╛рдВрдЧрд▓реЗ card: рдкреНрд░рддреНрдпреЗрдХ card рд╡рд░ lap time рдЖрдзреАрдЪ рд▓рд┐рд╣рд┐рд▓реЗрд▓рд╛ рдЕрд╕реЗрд▓, рддрд░ рддрд┐рд▓рд╛ рдкрддреНрд░рдХреЗ рдЖрдгрд╛рдпрдЪреА рдЧрд░рдЬрдЪ рдирд╛рд╣реА. рд╣рд╛ covering index.
рдкрдг card box рдирд╛рд╡рд╛рдиреБрд╕рд╛рд░ рдЬрд╕реЗ рд▓рд┐рд╣рд┐рд▓реЗ рдЖрд╣реЗ рддрд╕реЗ рд▓рд╛рд╡рд▓реЗрд▓реА рдЖрд╣реЗ. рдПрдЦрд╛рджреНрдпрд╛ рдкрд╛рд▓рдХрд╛рдиреЗ "RUNNER042" рдЕрд╕реЗ рдореЛрдареНрдпрд╛ рдЕрдХреНрд╖рд░рд╛рдд рд╡рд┐рдЪрд╛рд░рд▓реЗ рдЖрдгрд┐ рддреБрд▓рдирд╛ рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рдРрд╢реНрд╡рд░реНрдпрд╛рд▓рд╛ рдкреНрд░рддреНрдпреЗрдХ рдирд╛рд╡ рд▓рд╣рд╛рди рдЕрдХреНрд╖рд░рд╛рдд рдХрд░рд╛рд╡реЗ рд▓рд╛рдЧрд▓реЗ, рддрд░ рд▓рд╛рд╡рд▓реЗрд▓реНрдпрд╛ box рдЪрд╛ рдХрд╛рд╣реА рдЙрдкрдпреЛрдЧ рдирд╛рд╣реА тАФ рдкреБрдиреНрд╣рд╛ рдкреНрд░рддреНрдпреЗрдХ card рд╡рд╛рдЪрдгреЗ. рдЬреЛрдкрд░реНрдпрдВрдд рддреА рд▓рд╣рд╛рди рдЕрдХреНрд╖рд░рд╛рддреАрд▓ рдирд╛рд╡рд╛рдиреБрд╕рд╛рд░ рд▓рд╛рд╡рд▓реЗрд▓реА рджреБрд╕рд░реА box рдмрдирд╡рдд рдирд╛рд╣реА рддреЛрдкрд░реНрдпрдВрдд.
ЁЯЧ║я╕П рдЖрдХреГрддреА
flowchart LR
q["тЭУ WHERE runner_id = 42"] --> none["no index:<br/>SCAN laps тАФ 10,000 rows"]
q --> idx["index on runner_id:<br/>SEARCH тАж USING INDEX тАФ 20 rows"]
q --> cov["index on (runner_id, lap_ms):<br/>USING COVERING INDEX тАФ no table reads"]
f["WHERE lower(name) = тАж"] --> scan2["SCAN runners"]
f --> expr["index ON runners(lower(name)):<br/>SEARCH тАж (<expr>=?)"]
ЁЯЧ║я╕П рд░реЗрдЦрд╛рдЯрд▓реЗрд▓реА рдЖрд╡реГрддреНрддреА + рдПрдХ lab: https://school-edh.pages.dev/performance/lesson-diagrams.html#l10
тЭУ рдХрд╛рдп
- Query planner (optimiser) тАФ database рдЪрд╛ рддреЛ рднрд╛рдЧ рдЬреЛ query рдХрд╢реА рдЪрд╛рд▓рд╡рд╛рдпрдЪреА рддреЗ рдирд┐рд╡рдбрддреЛ: рдХреЛрдгрддрд╛ index, joins рдЪрд╛ рдХреЛрдгрддрд╛ рдХреНрд░рдо, sort рдХрд░рд╛рдпрдЪреЗ рдХреА рдирд╛рд╣реА. рддреЗрдЪ SQL, рдкрдг рдХрд╛рдорд╛рдд рдЦреВрдк рдлрд░рдХ.
- Full table scan (SQLite рдордзреНрдпреЗ
SCAN laps, PostgreSQL рдордзреНрдпреЗSeq Scan, MySQL рдордзреНрдпреЗtype: ALL) тАФ рдкреНрд░рддреНрдпреЗрдХ row рд╡рд╛рдЪреВрди рддрдкрд╛рд╕рдгреЗ. рд▓рд╣рд╛рди tables рд╕рд╛рдареА рдХрд┐рдВрд╡рд╛ рдмрд╣реБрддреЗрдХ rows рдЬреБрд│рдд рдЕрд╕рддрд╛рдирд╛ рдареАрдХ; рдЕрдиреНрдпрдерд╛ рд╣рд│реВ рдЖрдгрд┐ table рд╕реЛрдмрдд рд╡рд╛рдврдгрд╛рд░реЗ. - Index тАФ column values рдкрд╛рд╕реВрди rows рдкрд░реНрдпрдВрддрдЪреА рд╡реЗрдЧрд│реА, рдХреНрд░рдорд╛рдиреЗ рд▓рд╛рд╡рд▓реЗрд▓реА рд░рдЪрдирд╛ (рд╕рд╣рд╕рд╛ B-tree). Search
tree рдордзреВрди рд╕рд╛рдзрд╛рд░рдг log(n) рдкрд╛рдпрд▒реНрдпрд╛рдВрдд рдЦрд╛рд▓реА рдЬрд╛рддреЛ, рдордЧ рдлрдХреНрдд рдЬреБрд│рдгрд╛рд▒реНрдпрд╛ rows рд╡рд╛рдЪрддреЛ. Indexes рдлреБрдХрдЯ рдирд╕рддрд╛рдд:
рдкреНрд░рддреНрдпреЗрдХ index рдЬрд╛рдЧрд╛ рдШреЗрддреЛ рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ
INSERT/UPDATEрдереЛрдбрд╛ рд╣рд│реВ рдХрд░рддреЛ. - Covering index тАФ query рд▓рд╛ рд▓рд╛рдЧрдгрд╛рд░рд╛ рдкреНрд░рддреНрдпреЗрдХ column index рдордзреНрдпреЗрдЪ рдЕрд╕рддреЛ, рддреНрдпрд╛рдореБрд│реЗ database table
рд▓рд╛ рдХрдзреАрдЪ рд╣рд╛рдд рд▓рд╛рд╡рдд рдирд╛рд╣реА (SQLite рдордзреНрдпреЗ
USING COVERING INDEX, PostgreSQL рдордзреНрдпреЗIndex Only Scan). - Composite index
(a, b)тАФ рдЖрдзреА a рдиреБрд╕рд╛рд░, рдордЧ b рдиреБрд╕рд╛рд░ рдХреНрд░рдорд╛рдиреЗ. рддреЛWHERE a = ?рд▓рд╛, рдЖрдгрд┐ sort рд╢рд┐рд╡рд╛рдпWHERE a = ? ORDER BY bрд▓рд╛ рдорджрдд рдХрд░рддреЛ; рдлрдХреНрддWHERE b = ?рд▓рд╛ рдорджрдд рдХрд░рдд рдирд╛рд╣реА (leftmost prefix рдирд┐рдпрдо). - Index рдХрд╢рд╛рдореБрд│реЗ рд▓рдкрддреЛ тАФ column рд╡рд░ function рдХрд┐рдВрд╡рд╛ рдЕрдВрдХрдЧрдгрд┐рдд (
lower(name),runner_id + 0), рд╕реБрд░реБрд╡рд╛рддреАрд▓рд╛ wildcard (LIKE '%042'), рд╡реЗрдЧрд╡реЗрдЧрд│реНрдпрд╛ types рдЪреА рддреБрд▓рдирд╛. Expression index (SQLite 3.9+, PostgreSQL) рдиреЗ рдХрд┐рдВрд╡рд╛ query рдкреБрдиреНрд╣рд╛ рд▓рд┐рд╣реВрди рджреБрд░реБрд╕реНрдд рдХрд░рд╛. - Statistics тАФ
ANALYZEvalues рдХрд╢рд╛ рдкрд╕рд░рд▓реЗрд▓реНрдпрд╛ рдЖрд╣реЗрдд рддреЗ рд╕рд╛рдард╡рддреЛ. рдЪрд╛рдВрдЧрд▓реНрдпрд╛ statistics рд╕рд╣, rows рдЪрд╛ рдореЛрдард╛ рд╡рд╛рдЯрд╛ рдЬреБрд│рдд рдЕрд╕рддрд╛рдирд╛ planner scan рдпреЛрдЧреНрдпрд░рд┐рддреНрдпрд╛ рдирд┐рд╡рдбреВ рд╢рдХрддреЛ, рдХрд╛рд░рдг рддреЗрд╡реНрд╣рд╛ index рдордзреВрди рдЗрдХрдбреЗ-рддрд┐рдХрдбреЗ рдЙрдбреНрдпрд╛ рдорд╛рд░рдгреЗ рд╕рд░рд│ рд╡рд╛рдЪрдд рдЬрд╛рдгреНрдпрд╛рдкреЗрдХреНрд╖рд╛ рд╣рд│реВ рдЕрд╕рддреЗ. - Plan рдЪрд╛ рдордЬрдХреВрд░ version рдиреБрд╕рд╛рд░ рдмрджрд▓рддреЛ тАФ рд╣рд╛ рдзрдбрд╛ SQLite 3.51 рджрд╛рдЦрд╡рддреЛ (
SCAN laps); 3.36 рдкреВрд░реНрд╡реАрдЪреЗ SQLiteSCAN TABLE lapsрдЫрд╛рдкрдд рдЕрд╕реЗ, рдЖрдгрд┐ PostgreSQL рдЪрд╛EXPLAINрдмрд░рд╛рдЪ рд╡реЗрдЧрд│рд╛ рджрд┐рд╕рддреЛ.
Database school SQL, joins рдЖрдгрд┐ indexes рд╕реБрд░реБрд╡рд╛рддреАрдкрд╛рд╕реВрди рд╢рд┐рдХрд╡рддреЗ.
ЁЯдФ рдХрд╛
рдХрд╛рд░рдг рд╣рд│реВ endpoint рдЪреЗ рд╕рд░реНрд╡рд╛рдд рд╕рд╛рдорд╛рдиреНрдп рдХрд╛рд░рдг рдореНрд╣рдгрдЬреЗ рдирд╕рд▓реЗрд▓рд╛ index, рдЖрдгрд┐ table рдЬрд╕рдЬрд╕реЗ рд╡рд╛рдврддреЗ рддрд╕рддрд╕реЗ рддреЗ рджрд░рд░реЛрдЬ
рдЕрдзрд┐рдХ рд╡рд╛рдИрдЯ рд╣реЛрддреЗ. рдзрдбрд╛ 04 рдордзрд▓реЗ O(n) рд╡рд┐рд░реБрджреНрдз O(log n) рдиреЗрдордХреЗ рд╣реЗрдЪ рдЖрд╣реЗ: рдЖрдЬ 10,000 rows рдЪрд╛ scan рдкреБрдврдЪреНрдпрд╛ рд╡рд░реНрд╖реА
1 рдХреЛрдЯреА (10 million) rows рдЪрд╛ scan рд╣реЛрддреЛ. Plan рд╡рд╛рдЪрд▓реНрдпрд╛рдиреЗ "database рд╣рд│реВ рдЖрд╣реЗ" рдпрд╛рдЪреЗ рд░реВрдкрд╛рдВрддрд░ "рд╣реА query laps
scan рдХрд░рддреЗ тАФ рд╣рд╛ index рдЬреЛрдбрд╛" рдордзреНрдпреЗ рд╣реЛрддреЗ.
ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)
perf/sim.py рдордзреАрд▓ laps_db() рдПрдХ in-memory SQLite database рдмрдирд╡рддреЗ: 5 рджрд┐рд╡рд╕рд╛рдВрддреАрд▓
500 рдзрд╛рд╡рдкрдЯреВ рдЖрдгрд┐ 10,000 laps (рдкреНрд░рддреНрдпреЗрдХ рдзрд╛рд╡рдкрдЯреВрдЪреЗ 20). plan(conn, sql, params) EXPLAIN QUERY PLAN
рдЪрд╛рд▓рд╡рддреЗ рдЖрдгрд┐ detail column рдкрд░рдд рджреЗрддреЗ тАФ SQLite рдЪреЗ рд╕реНрд╡рддрдГрдЪреЗ рд╢рдмреНрдж. vm_steps progress handler рд╡рд╛рдкрд░реВрди
SQLite рдЪреНрдпрд╛ virtual-machine рдкрд╛рдпрд▒реНрдпрд╛ рдореЛрдЬрддреЗ; рд╣реЗ рдХрд╛рдорд╛рдЪреЗ рдвреЛрдмрд│ рдореЛрдЬрдорд╛рдк рддреБрдордЪреНрдпрд╛ SQLite version рд╡рд░ рдЕрд╡рд▓рдВрдмреВрди
рдЕрд╕рддреЗ (рдореНрд╣рдгреВрди рддреЗ рдХрдзреАрдЪ рдиреЗрдордХреЗ рд╕рд╛рдВрдЧрд┐рддрд▓реЗ рдЬрд╛рдд рдирд╛рд╣реА). perf/demo.py рдордзреАрд▓ database()
рдПрдХреЗрдХ рдХрд░реВрди indexes рдЬреЛрдбрддреЗ рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХрд╛рдирдВрддрд░ plan рдЫрд╛рдкрддреЗ.
ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛
python3 perf/demo.py database
python3 - <<'EOF'
import sys; sys.path.insert(0, "perf"); from sim import laps_db, plan, vm_steps
db = laps_db()
q = "SELECT runner_id, MIN(lap_ms) FROM laps WHERE day = ? GROUP BY runner_id"
print("before:", plan(db, q, ("2026-09-21",)))
db.execute("CREATE INDEX idx_laps_day ON laps(day)")
print("after: ", plan(db, q, ("2026-09-21",)))
q2 = "SELECT * FROM laps WHERE runner_id = ? ORDER BY lap_ms"
print("sort: ", plan(db, q2, (42,)))
db.execute("CREATE INDEX idx_laps_runner_ms ON laps(runner_id, lap_ms)")
print("sorted:", plan(db, q2, (42,)))
q3 = "SELECT id FROM runners WHERE lower(name) = 'runner042'"
db.execute("CREATE INDEX idx_runners_lower ON runners(lower(name))")
print("expr: ", plan(db, q3))
db2 = laps_db(); a = vm_steps(db2, "SELECT lap_ms FROM laps WHERE runner_id = 42")
db2.execute("CREATE INDEX i ON laps(runner_id)"); b = vm_steps(db2, "SELECT lap_ms FROM laps WHERE runner_id = 42")
print(f"SQLite steps: scan about {a:,} ┬╖ index about {b:,} (exact counts depend on your SQLite version)")
EOF
рд╢реЗрд╡рдЯрдЪреА рдУрд│ SQLite рдЪреНрдпрд╛ рдЕрдВрддрд░реНрдЧрдд рдкрд╛рдпрд▒реНрдпрд╛ рдореЛрдЬрддреЗ тАФ SQLite 3.51 рд╕рд╣ scan рд╕рд╛рдареА рд╕реБрдорд╛рд░реЗ 30,000 рдЖрдгрд┐ index рд╕рд╛рдареА 100. рджреБрд╕рд▒реНрдпрд╛ SQLite version рд╕рд╣ рддреБрдордЪреЗ рдЖрдХрдбреЗ рд╡реЗрдЧрд│реЗ рдЕрд╕реВ рд╢рдХрддрд╛рдд; рдордзрд▓реЗ рдЕрдВрддрд░ рдмрджрд▓рдгрд╛рд░ рдирд╛рд╣реА.
тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ
database рд╣реЗ рдЫрд╛рдкрддреЗ:
тФАтФА 10,000 laps, 500 runners ┬╖ EXPLAIN QUERY PLAN for: SELECT lap_ms FROM laps WHERE runner_id = ?
no index тЖТ SCAN laps (reads all 10,000 rows)
index on runner_id тЖТ SEARCH laps USING INDEX idx_laps_runner (runner_id=?) (jumps to runner 42's 20 rows)
index on (runner_id, lap_ms) тЖТ SEARCH laps USING COVERING INDEX idx_laps_runner_ms (runner_id=?)
covering: every column the query needs is in the index, so the table itself is never read
runners WHERE name = 'runner042' тЖТ SEARCH runners USING COVERING INDEX idx_runners_name (name=?)
runners WHERE lower(name) = 'runner042' тЖТ SCAN runners
runners WHERE name LIKE '%042' тЖТ SCAN runners
рддреБрдордЪрд╛ snippet рдЕрд╕рд╛ рд╕реБрд░реВ рд╣реЛрддреЛ:
before: ['SCAN laps', 'USE TEMP B-TREE FOR GROUP BY']
after: ['SEARCH laps USING INDEX idx_laps_day (day=?)', 'USE TEMP B-TREE FOR GROUP BY']
sort: ['SCAN laps', 'USE TEMP B-TREE FOR ORDER BY']
sorted: ['SEARCH laps USING INDEX idx_laps_runner_ms (runner_id=?)']
expr: ['SEARCH runners USING COVERING INDEX idx_runners_lower (<expr>=?)']
ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ
рдлрдХреНрдд рдПрдХ index рдЬреЛрдбреВрди рддреЗрдЪ SQL 10,000 rows рд╡рд╛рдЪрдгреНрдпрд╛рд╡рд░реВрди 20 рд╡рд░ рдЖрд▓реЗ тАФ рдЖрдгрд┐ covering index рд╕рд╣ table
рдЪреЗ рд╡рд╛рдЪрдирдЪ рд╢реВрдиреНрдп. Composite index (runner_id, lap_ms) рдиреЗ USE TEMP B-TREE FOR ORDER BY рд╣реА рдкрд╛рдпрд░реАрд╣реА
рдХрд╛рдвреВрди рдЯрд╛рдХрд▓реА: rows index рдордзреВрди рдЖрдзреАрдЪ рдХреНрд░рдорд╛рдиреЗ рдпреЗрддрд╛рдд. Expression index рдЬреБрд│реЗрдкрд░реНрдпрдВрдд lower(name) рдиреЗ
name index рд▓рдкрд╡рд▓рд╛ рд╣реЛрддрд╛. day query рд▓рд╛ рддрд┐рдЪреНрдпрд╛ GROUP BY рд╕рд╛рдареА рдЕрдЬреВрдирд╣реА temporary B-tree рд▓рд╛рдЧрддреЛ тАФ day рд╡рд░рдЪрд╛
index rows рд╢реЛрдзрддреЛ, рдкрдг рддреНрдпрд╛рдВрдирд╛ рдзрд╛рд╡рдкрдЯреВрдиреБрд╕рд╛рд░ group рдХрд░рдд рдирд╛рд╣реА.
тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛
EXPLAINрд╡рд╛рдЪрдгреНрдпрд╛рдРрд╡рдЬреА indexes рдмрджреНрджрд▓ рдЕрдВрджрд╛рдЬ рдмрд╛рдВрдзрдгреЗ- "рдЕрд╕реВ рджреЗ" рдореНрд╣рдгреВрди рдкреНрд░рддреНрдпреЗрдХ column рд╡рд░ index тАФ рд╣рд│реВ writes, рдЕрдзрд┐рдХ рдЬрд╛рдЧрд╛, рдХреНрд╡рдЪрд┐рддрдЪ рд╡рд╛рдкрд░
- query рд╕рд╛рдареА рдЪреБрдХреАрдЪреНрдпрд╛ column рдХреНрд░рдорд╛рддреАрд▓ composite index
- column рд╡рд░ рд╕рд╛рдзрд╛ index рдЕрд╕рддрд╛рдирд╛
WHERE lower(email) = ?рдХрд┐рдВрд╡рд╛WHERE created_at::date = ? - рдЕрдЧрджреА рд▓рд╣рд╛рди table рд╡рд░ test рдХрд░рдгреЗ, рдЬрд┐рдереЗ scan рд╣рд╛рдЪ рдпреЛрдЧреНрдп plan рдЕрд╕рддреЛ рдЖрдгрд┐ рд╕рдорд╕реНрдпрд╛ рдХрдзреАрдЪ рджрд┐рд╕рдд рдирд╛рд╣реА
- рджреЛрди columns рдиреЗ covering index рд▓рд╛ рд╕рдВрдкреВрд░реНрдг рдХрд╛рдо рдХрд░реВ рджрд┐рд▓реЗ рдЕрд╕рддреЗ рддрд┐рдереЗ
SELECT *
ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд
рдЦрд▒реНрдпрд╛ account рд╡рд░ тАФ PostgreSQL рдЪрд╛ EXPLAIN ANALYZE query рдЪрд╛рд▓рд╡рддреЛ рдЖрдгрд┐ plan рд╢реЗрдЬрд╛рд░реА рдЦрд▒реНрдпрд╛ rows
рдЖрдгрд┐ рд╡реЗрд│рд╛ рджрд╛рдЦрд╡рддреЛ (рдореНрд╣рдгреВрди UPDATE/DELETE рдмрд╛рдмрдд рдХрд╛рд│рдЬреА рдШреНрдпрд╛ тАФ рддреНрдпрд╛рдВрдирд╛ рдЕрд╢рд╛ transaction рдордзреНрдпреЗ рдЧреБрдВрдбрд╛рд│рд╛ рдЬреЛ
рддреБрдореНрд╣реА roll back рдХрд░рд╛рд▓):
EXPLAIN (ANALYZE, BUFFERS) SELECT lap_ms FROM laps WHERE runner_id = 42;
-- look for: Seq Scan vs Index Scan / Index Only Scan / Bitmap Heap Scan,
-- estimated rows vs actual rows, and "Buffers: shared hit=тАж read=тАж"
CREATE INDEX CONCURRENTLY idx_laps_runner ON laps (runner_id); -- builds without blocking writes
CREATE INDEX idx_runners_lower_name ON runners (lower(name)); -- an expression index
ANALYZE laps; -- refresh the planner's statistics
рдЖрдзреА рд╣рд│реВ queries рд╢реЛрдзрд╛. PostgreSQL рдПрдХрд╛ рдорд░реНрдпрд╛рджреЗрдкреЗрдХреНрд╖рд╛ рд╣рд│реВ рдЕрд╕рд▓реЗрд▓реА рдкреНрд░рддреНрдпреЗрдХ query log рдХрд░реВ рд╢рдХрддреЛ:
ALTER SYSTEM SET log_min_duration_statement = '200ms';
SELECT pg_reload_conf();
MySQL: EXPLAIN ANALYZE SELECT ... (8.0.18+) рдЖрдгрд┐ slow query log (long_query_time).
SQLite, command line рд╡рд░реВрди:
sqlite3 school.db "EXPLAIN QUERY PLAN SELECT lap_ms FROM laps WHERE runner_id = 42;"
ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: рддреБрдордЪреНрдпрд╛ рд╕рд░реНрд╡рд╛рдзрд┐рдХ рд╡реЗрд│рд╛ рдЪрд╛рд▓рдгрд╛рд▒реНрдпрд╛ рджрд╣рд╛ рдЖрдгрд┐ рд╕рд░реНрд╡рд╛рдд рд╣рд│реВ рджрд╣рд╛ queries рд╕рд╛рдареА, plan code рд╢реЗрдЬрд╛рд░реА рдареЗрд╡рд╛, рдЖрдгрд┐ table рджрд╣рд╛рдкрдЯ рд╡рд╛рдврд▓реНрдпрд╛рд╡рд░ рддреЛ рдкреБрдиреНрд╣рд╛ рдкрд╛рд╣рд╛.
тПня╕П рдкреБрдвреЗ
рдЖрддрд╛ server рд╡реЗрдЧрд╡рд╛рди рдЖрд╣реЗ. рдкрдг рдкрд╛рд▓рдХ рдирд┐рдХрд╛рд▓ рддреНрдпрд╛рдВрдЪреНрдпрд╛ phones рд╡рд░ рдкрд╛рд╣рдд рдЖрд╣реЗрдд. рдкреБрдвреЗ: web performance тАФ LCP, INP, CLS, payload, compression рдЖрдгрд┐ caching headers.
git checkout lesson-11-web-performance