中學考試成績統計系統,四張核心表:
class:班級表 (id, class_name, master_id, is_key)student:學生表 (id, school_id, name, class_name...)course:課程表 (id, course_name, grade, class_name...)score:成績表 (id, course_id, school_id, score)
新增班級 303 暫無班長,WHERE/INNER JOIN 將丟失該行:
-- WHERE / INNER JOIN 結果 (6 行,缺 303) SELECT cl.class_name, st.name FROM class cl, student st WHERE cl.master_id = st.school_id; -- LEFT JOIN 結果 (7 行,含 303 NULL) SELECT cl.class_name, st.name FROM class cl LEFT JOIN student st ON cl.master_id = st.school_id;
核心原則:業務需要「主表全量保留」時,必須使用 LEFT/RIGHT JOIN;WHERE 子句等價於 INNER JOIN,會過濾掉無匹配行。
EXPLAIN SELECT cl.class_name, st.name FROM class cl LEFT JOIN student st ON cl.master_id = st.school_id;
+----+-------------+-------+------+---------------+------+---------+------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+-------+ | 1 | SIMPLE | cl | ALL | NULL | NULL | NULL | NULL | 7 | | | 1 | SIMPLE | st | ALL | NULL | NULL | NULL | NULL | 301 | | +----+-------------+-------+------+---------------+------+---------+------+------+-------+
問題:被驅動表 student 類型為 ALL (全表掃描),rows=301。
ALTER TABLE student ADD INDEX idx_school_id (school_id);
+----+-------------+-------+------+-----------------+-----------------+---------+---------------------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+-----------------+-----------------+---------+---------------------+------+-------+ | 1 | SIMPLE | cl | ALL | NULL | NULL | NULL | NULL | 7 | | | 1 | SIMPLE | st | ref | idx_school_id | idx_school_id | 4 | school.cl.master_id | 1 | | +----+-------------+-------+------+-----------------+-----------------+---------+---------------------+------+-------+
效果:
type:ALL → ref (索引查找)rows:301 → 1 (單行精確定位)Extra:消失 Using join buffer,無需臨時表MySQL 僅支援 Nested Loop Join:
優化核心:被驅動表 JOIN 條件列必須有索引,將內層循環從全表掃描降為索引查找 O(log n)。
EXPLAIN SELECT st.name, co.course_name, sc.score FROM student st JOIN score sc ON sc.school_id = st.school_id JOIN course co ON co.id = sc.course_id WHERE st.school_id = 100005;
+----+-------------+-------+--------+---------------------------------------+--------------------+---------+---------------------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+--------+---------------------------------------+--------------------+---------+---------------------+------+-------+ | 1 | SIMPLE | st | ref | idx_school_id | idx_school_id | 4 | const | 1 | | | 1 | SIMPLE | sc | ref | idx_school_id_sc,idx_course_id_sc | idx_school_id_sc | 4 | const | 3 | | | 1 | SIMPLE | co | eq_ref | PRIMARY | PRIMARY | 4 | school.sc.course_id | 1 | | +----+-------------+-------+--------+---------------------------------------+--------------------+---------+---------------------+------+-------+
優化亮點:
student 通過 WHERE school_id=const 直接定位 1 行score 表複合索引支援 school_id 精確查找course 走主鍵 eq_ref,單行精確匹配CREATE DATABASE school;
USE school;
CREATE TABLE student (
id INT NOT NULL AUTO_INCREMENT,
school_id INT(11) NOT NULL,
name VARCHAR(30) NOT NULL,
sex INT NOT NULL,
age INT NOT NULL,
class_name INT NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE class (
id INT NOT NULL AUTO_INCREMENT,
class_name INT NOT NULL,
master_id INT,
is_key INT NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE course (
id INT NOT NULL AUTO_INCREMENT,
course_name VARCHAR(10) NOT NULL,
grade INT NOT NULL,
president_id INT,
is_neces INT NOT NULL,
credit INT NOT NULL,
class_name INT,
PRIMARY KEY (id)
);
CREATE TABLE score (
id INT NOT NULL AUTO_INCREMENT,
course_id INT NOT NULL,
school_id INT NOT NULL,
score INT,
PRIMARY KEY (id)
);
-- 核心索引
ALTER TABLE student ADD INDEX idx_school_id (school_id);
ALTER TABLE score ADD INDEX idx_school_id_sc (school_id);
ALTER TABLE score ADD INDEX idx_course_id_sc (course_id);
ALTER TABLE class ADD INDEX idx_master_id (master_id);