ЁЯЧВя╕П рдзрдбрд╛ 06 тАФ Indexes: рдХрд╛рд░реНрдб рдХреЕрдЯрд▓реЙрдЧ
ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 18 рдкреИрдХреА рдзрдбрд╛ 06 ┬╖ рдорд╛рдЧреЗ: lesson-05-data-modelling ┬╖ рдкреБрдвреЗ: lesson-07-concurrency
ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ
рдзрдбреЗ 01тАУ05, рдЖрдгрд┐ рдХрд╛рд╣реА рдкреНрд░рд╢реНрдирд╛рдВрдирд╛ milliseconds рдХрд╛ рд▓рд╛рдЧрддрд╛рдд рдЖрдгрд┐ рдХрд╛рд╣реАрдВрдирд╛ рдорд┐рдирд┐рдЯреЗ
рдХрд╛: indexes (рдХрд╛рд░реНрдб рдХреЕрдЯрд▓реЙрдЧ), B-trees, EXPLAIN QUERY PLAN, рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ рдХреЕрдЯрд▓реЙрдЧ рдкреНрд░рддреНрдпреЗрдХ write рд╡рд░ рдЖрдХрд╛рд░рддреЛ рддреА рдХрд┐рдВрдордд.
ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ
рд╣рдЬреЗрд░реАрдЪреНрдпрд╛ рдиреЛрдВрджрд╡рд╣реАрдд 200,000 рдУрд│реА рдЖрд╣реЗрдд. "student 4242 рдХрд┐рддреА рджрд┐рд╡рд╕ рд╣рдЬрд░ рд╣реЛрддреА?" тАФ рджрдкреНрддрд░рджрд╛рд░ рдкреНрд░рддреНрдпреЗрдХ рдУрд│ рд╡рд╛рдЪрддреЛ. рдкреНрд░рддреНрдпреЗрдХ рд╡реЗрд│реА. рд╣рд╛ SCAN: 5 рдУрд│реАрдВрд╡рд░ рдареАрдХ, 200,000 рд╡рд░ рдЪрд╣рд╛рдЪрд╛ рдмреНрд░реЗрдХ.
рдореНрд╣рдгреВрди рджрдкреНрддрд░рджрд╛рд░ рдПрдХ рдХрд╛рд░реНрдб рдХреЕрдЯрд▓реЙрдЧ ЁЯЧВя╕П рдмрдирд╡рддреЛ: student number рдиреБрд╕рд╛рд░ рд▓рд╛рд╡рд▓реЗрд▓реНрдпрд╛ cards рдЪрд╛ рдбреНрд░реЙрд╡рд░, рдкреНрд░рддреНрдпреЗрдХ card рд╕рд╛рдВрдЧрддреЗ рдХреА рддреА рд╡рд┐рджреНрдпрд╛рд░реНрдерд┐рдиреА рдХреЛрдгрддреНрдпрд╛ рдУрд│реАрдВрд╡рд░ рдЖрд╣реЗ. рдЖрддрд╛ "student 4242" рдореНрд╣рдгрдЬреЗ рдереЗрдЯ рдПрдХрд╛ card рдкрд░реНрдпрдВрдд рдЪрд╛рд▓рдгреЗ тАФ рдПрдХ SEARCH. рддреЛрдЪ рдкреНрд░рд╢реНрди, 2.4 ms рд╡рд░реВрди рдореЛрдЬрддрд╛рд╣реА рди рдпреЗрдгрд╛рд▒реНрдпрд╛ рд╡реЗрд│реЗрд╡рд░.
рдХреЕрдЯрд▓реЙрдЧ рдлреБрдХрдЯ рдирд╛рд╣реА: рдкреНрд░рддреНрдпреЗрдХ рд╡реЗрд│реА рдУрд│ рдЬреЛрдбрд▓реА рдХреА рддрд┐рдЪреЗ card рд╕реБрджреНрдзрд╛ рд▓рд╛рд╡рд╛рд╡реЗ рд▓рд╛рдЧрддреЗ.
рджрд╣рд╛ рдХреЕрдЯрд▓реЙрдЧ рдЕрд╕рд▓реЗрд▓реА рдиреЛрдВрджрд╡рд╣реА рдкреНрд░рддреНрдпреЗрдХ рдУрд│реАрд╕рд╛рдареА рджрд╣рд╛ cards рд▓рд┐рд╣рд┐рддреЗ. рдЖрдгрд┐ 5 рдУрд│реАрдВрдЪреНрдпрд╛
рдиреЛрдВрджрд╡рд╣реАрд╕рд╛рдареАрдЪрд╛ рдХреЕрдЯрд▓реЙрдЧ рддреНрдпрд╛ 5 рдУрд│реА рд╡рд╛рдЪрдгреНрдпрд╛рдкреЗрдХреНрд╖рд╛ рд╣рд│реВ рдЕрд╕рддреЛ. рдореНрд╣рдгреВрди: рдЬреНрдпрд╛ columns
рд╡рд░ рддреБрдореНрд╣реА filter, join рдЖрдгрд┐ sort рдХрд░рддрд╛ рддреНрдпрд╛рдВрдирд╛рдЪ index рдХрд░рд╛, рдЖрдгрд┐ рдХрд╛рд╣реАрд╣реА
рдмрдирд╡рдгреНрдпрд╛рдЖрдзреА рджрдкреНрддрд░рджрд╛рд░рд╛рд▓рд╛ рдорд╛рд░реНрдЧ рд╡рд┐рдЪрд╛рд░рд╛ тАФ EXPLAIN QUERY PLAN.
ЁЯЧ║я╕П рдЖрдХреГрддреА
flowchart LR
q["тЭУ SELECT COUNT(*) FROM attendance<br/>WHERE student_id = 4242"]
scan["ЁЯЪ╢ SCAN attendance<br/>read 200,000 lines тЖТ 2.4 ms"]
idx["ЁЯЧВя╕П CREATE INDEX idx_attendance_student<br/>ON attendance(student_id) тАФ a B-tree"]
search["ЁЯФН SEARCH тАж USING INDEX<br/>straight to the card тЖТ 0.0 ms"]
q -->|"1 before"| scan
q -->|"2 after"| idx --> search
cost["тЪЦя╕П every INSERT files a card ┬╖ composite order matters ┬╖ tiny tables: no catalogue"]
idx -.->|"3"| cost
тЭУ рдХрд╛рдп
- Index = рдПрдХ рдХрд┐рдВрд╡рд╛ рдЕрдзрд┐рдХ columns рд╡рд░рдЪреА рдХреНрд░рдорд╛рдиреЗ рд▓рд╛рд╡рд▓реЗрд▓реА рд░рдЪрдирд╛ (рдмрд╣реБрдзрд╛
B-tree), rows рдХрдбреЗ рдкрд░рдд рдмреЛрдЯ рджрд╛рдЦрд╡рдгрд╛рд░реА. Primary keys рдЖрдгрд┐
UNIQUEconstraints рдирд╛ рддреА рдЖрдкреЛрдЖрдк рдорд┐рд│рддреЗ. EXPLAIN QUERY PLAN <query>(SQLite; Postgres рдордзреНрдпреЗEXPLAIN/EXPLAIN ANALYZE)SCANрд╡рд┐рд░реБрджреНрдзSEARCH тАж USING INDEXрджрд╛рдЦрд╡рддреЛ тАФ рдЪрд╛рд▓рдгреНрдпрд╛рдЖрдзреАрдЪрд╛ рдорд╛рд░реНрдЧ.- рдХрд╛рдп index рдХрд░рд╛рдпрдЪреЗ:
WHERE,JOIN тАж ON,ORDER BYрдордзреАрд▓ columns; foreign keys ("рдЕрдиреЗрдХ" рдмрд╛рдЬреВрд▓рд╛ index рдирд╕рд▓реЗрд▓рд╛ join scan рдХрд░рддреЛ). - Composite indexes
(class_id, name)рд╣реЗWHERE class_id = ? ORDER BY nameрд╕рд╛рдареА рдЙрдкрдпреЛрдЧреА рдкрдбрддрд╛рдд тАФ рдкрдг рдлрдХреНрддWHERE name = ?рд╕рд╛рдареА рдирд╛рд╣реА: рдХреНрд░рдо рдорд╣рддреНрддреНрд╡рд╛рдЪрд╛, рд╕рд░реНрд╡рд╛рдд рдбрд╛рд╡рд╛ рдЖрдзреА. - рдХрд┐рдВрдордд: рд╣рд│реВ writes, рдЬрд╛рд╕реНрдд рдЬрд╛рдЧрд╛, рдЖрдгрд┐ table рдЦреВрдк рд▓рд╣рд╛рди рдЕрд╕реЗрд▓ рдХрд┐рдВрд╡рд╛ filter рдмрд╣реБрддреЗрдХ rows рд╢реА рдЬреБрд│рдд рдЕрд╕реЗрд▓ рддрд░ planner рддрд░реАрд╣реА scan рдирд┐рд╡рдбреВ рд╢рдХрддреЛ.
- Covering index: query рд▓рд╛ рд▓рд╛рдЧрдгрд╛рд░рд╛ рдкреНрд░рддреНрдпреЗрдХ column index рдордзреНрдпреЗрдЪ рдЕрд╕реЗрд▓, рддрд░
рдиреЛрдВрджрд╡рд╣реА рдЙрдШрдбрд▓реАрдЪ рдЬрд╛рдд рдирд╛рд╣реА (
USING COVERING INDEX).
ЁЯдФ рдХрд╛
рдХрд╛рд░рдг "рд╡рд╛рдврдд рдЬрд╛рдИрд▓ рддрд╕реЗ рд╣рд│реВ рд╣реЛрдгрд╛рд░реЗ" app рдЖрдгрд┐ рди рд╣реЛрдгрд╛рд░реЗ app рдпрд╛рдВрддреАрд▓ рдлрд░рдХ рдЬрд╡рд│рдЬрд╡рд│ рдкреВрд░реНрдгрдкрдгреЗ рдХреЛрдгрддреНрдпрд╛ рдкреНрд░рд╢реНрдирд╛рдВрдирд╛ рдХрд╛рд░реНрдб рдХреЕрдЯрд▓реЙрдЧ рдЖрд╣реЗ рдпрд╛рд╡рд░ рдЕрд╡рд▓рдВрдмреВрди рдЕрд╕рддреЛ. рдЖрдгрд┐ index рдирд╕рд▓реЗрд▓реЗ writes рд╕реНрд╡рд╕реНрдд рдЕрд╕рддрд╛рдд рдкрдг index рдирд╕рд▓реЗрд▓реЗ reads рдЪрдХреНрд░рд╡рд╛рдвреАрдиреЗ рд╡рд╛рдврддрд╛рдд: рдЬреНрдпрд╛ рджрд┐рд╡рд╢реА table рджрд╣рд╛ рд▓рд╛рдЦ rows рдУрд▓рд╛рдВрдбрддреЗ, рддреНрдпрд╛ рджрд┐рд╡рд╢реА рд╣рд░рд╡рд▓реЗрд▓рд╛ index outage рдмрдирддреЛ.
ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)
db/demo.py рдордзреАрд▓ index() 200,000 rows рдЪрд╛ attendance
table рдмрдирд╡рддреЗ, index рд╢рд┐рд╡рд╛рдп рдкреНрд░рд╢реНрдирд╛рдЪреА рд╡реЗрд│ рдореЛрдЬрддреЗ, index рдмрдирд╡рддреЗ, рдЖрдгрд┐ рдкреБрдиреНрд╣рд╛ рд╡реЗрд│ рдореЛрдЬрддреЗ тАФ
рджреЛрдиреНрд╣реА рд╡реЗрд│рд╛ EXPLAIN QUERY PLAN рдЫрд╛рдкрдд. schema.sql рдЖрдзреАрдЪ рджреЛрди foreign keys рдирд╛
index рдХрд░рддреЗ.
ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛
python3 db/demo.py index
python3 - <<'EOF'
import sqlite3, time; c = sqlite3.connect("db/school.db")
for q in ["SELECT * FROM grades WHERE subject = 'maths'", "SELECT * FROM students WHERE class_id = 1 ORDER BY name"]:
print(c.execute("EXPLAIN QUERY PLAN " + q).fetchall()[0][3], "тЖР", q)
c.execute("CREATE INDEX IF NOT EXISTS idx_grades_subject ON grades(subject)")
print(c.execute("EXPLAIN QUERY PLAN SELECT * FROM grades WHERE subject = 'maths'").fetchall()[0][3], "тЖР after the index")
# the cost side: time 20,000 inserts into attendance with the index present
t=time.perf_counter(); c.executemany("INSERT INTO attendance (student_id, day, present) VALUES (?,?,?)", [(i, '2026-01-01', 1) for i in range(20000)]); c.commit(); print(f"20,000 inserts with an index: {(time.perf_counter()-t)*1000:.0f} ms")
EOF
тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ
index рдЖрдзреА millisecond рд╡реЗрд│реЗрд╕рд╣ SCAN attendance рдЫрд╛рдкрддреЛ, рдордЧ ~0 ms рд╕рд╣ SEARCH attendance USING COVERING INDEX. рддреБрдордЪреА рджреБрд╕рд░реА query рдЖрдзреАрдЪ SEARCH тАж USING INDEX idx_students_class рджрд╛рдЦрд╡рддреЗ (schema.sql рдордзреАрд▓ foreign key index); рддреБрдореНрд╣реА index рдмрдирд╡рд▓реНрдпрд╛рдирдВрддрд░ grades.subject query SCAN рд╡рд░реВрди SEARCH рд╡рд░ рдЬрд╛рддреЗ; рд╡реЗрд│ рдореЛрдЬрд▓реЗрд▓реЗ inserts рджрд╛рдЦрд╡рддрд╛рдд рдХреА write рдЪреА рдХрд┐рдВрдордд рдЦрд░реА рдЖрд╣реЗ рдкрдг рд▓рд╣рд╛рди.
ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ
рддреБрдореНрд╣реА 200,000 рдУрд│реАрдВрдЪреЗ рдкреВрд░реНрдг рд╡рд╛рдЪрди рдПрдХрд╛ card lookup рдордзреНрдпреЗ рдмрджрд▓рд▓реЗ, planner рдЪрд╛ рдорд╛рд░реНрдЧ рдмрджрд▓рддрд╛рдирд╛ рдкрд╛рд╣рд┐рд▓рд╛, рдЖрдгрд┐ рдХреЕрдЯрд▓реЙрдЧ writes рд╡рд░ рдХрд┐рддреА рдХрд┐рдВрдордд рдШреЗрддреЛ рддреЗ рдореЛрдЬрд▓реЗ.
тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛
- "рдХрджрд╛рдЪрд┐рдд рд▓рд╛рдЧреЗрд▓ рдореНрд╣рдгреВрди" рдкреНрд░рддреНрдпреЗрдХ column рд▓рд╛ index рдХрд░рдгреЗ тАФ writes рд░реЗрдВрдЧрд╛рд│рддрд╛рдд рдЖрдгрд┐ planner рддреНрдпрд╛рддрд▓реЗ рдмрд╣реБрддреЗрдХ рджреБрд░реНрд▓рдХреНрд╖рд┐рдд рдХрд░рддреЛ
- foreign-key indexes рд╡рд┐рд╕рд░рдгреЗ тАФ "рдЕрдиреЗрдХ" рдмрд╛рдЬреВрд╡рд░рдЪрд╛ рдкреНрд░рддреНрдпреЗрдХ JOIN scan рдХрд░рддреЛ
- рдЪреБрдХреАрдЪреНрдпрд╛ рдХреНрд░рдорд╛рддреАрд▓ composite index (
WHERE class_id = ?рд╕рд╛рдареА(name, class_id)) emailрд╡рд░рдЪреНрдпрд╛ index рд╡рд░WHERE LOWER(email) = ?тАФ function column рд▓рдкрд╡рддреЗ (expression index рд╡рд╛рдкрд░рд╛)- 5 rows рд╡рд░ рдореЛрдЬреВрди ship рдХрд░рдгреЗ тАФ рдореЛрдареНрдпрд╛ рдкреНрд░рдорд╛рдгрд╛рд╡рд░ planner рд╡реЗрдЧрд│рд╛ рд╡рд╛рдЧрддреЛ
ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: "index рдЬреЛрдбрд╛" рд╣рд╛ 30 seconds рдЪреНрдпрд╛ page рд▓рд╛ 30 milliseconds рдордзреНрдпреЗ рдмрджрд▓рдгрд╛рд░рд╛ рд╕рд░реНрд╡рд╛рдд рд╕рд╛рдорд╛рдиреНрдп рдПрдХрд╛ рдУрд│реАрдЪрд╛ fix рдЖрд╣реЗ тАФ рдЖрдгрд┐ write throughput рдЧреБрдкрдЪреВрдк рдЕрд░реНрдзрд╛ рдХрд░рдгрд╛рд░рд╛ рд╕рд░реНрд╡рд╛рдд рд╕рд╛рдорд╛рдиреНрдп рдПрдХрд╛ рдУрд│реАрдЪрд╛ рдмрджрд▓рд╣реА. рдкреНрд░рддреНрдпреЗрдХ рд╡реЗрд│реА рдЖрдзреА рдЖрдгрд┐ рдирдВрддрд░
EXPLAIN.
тПня╕П рдкреБрдвреЗ
рднрд╛рдЧ 2 рд╕реБрд░реВ рд╣реЛрддреЛ: рд░реЗрдХреЙрд░реНрдб рд░реВрдо рдЦрд▒реНрдпрд╛ рдЬрдЧрд╛рдд рдЪрд╛рд▓рд╡рдгреЗ. рдЖрдзреА, рджреЛрди clerks рдЖрдгрд┐ рдПрдХ рдиреЛрдВрджрд╡рд╣реА тАФ concurrency рдЖрдгрд┐ isolation.
git checkout lesson-07-concurrency