MySQL 多表查询与体系结构(完整版)

---

第一部分:多表查询

---

一、表与表之间的关系

在SQL语句中,数据表与数据表之间,如果存在关系,一般一共有3种情况:

1.1 一对一关系

比如有A、B两张表,A表中的每一条数据,在B表中有一条唯一的数据与之对应。

用户表 tb_user

user_id(用户编号)账号username密码password
001adminadmin888
002itheima123456

用户详情表 tb_user_items

user_id(用户编号)真实姓名年龄联系方式
001张三1610086
002李四1810010

其中,user_id是一一对应的,我们把用户表与用户详情表之间的关系就称之为一对一关系

1.2 一对多关系

比如有A、B两张表,A表中的每一条数据,在B表中都有多条数据与之对应,我们把这种关系就称之为一对多关系

产品分类表

分类id编号分类名称
1手机
2电脑

产品信息表

产品id编号产品名称产品价格所属分类id编号
1Apple iPhone 136799.001
2Redmi Note 93499.001

我们把产品分类表与产品表之间的(分类id的对应)关系就称之为一对多关系

1.3 多对多关系

用户表

用户编号登录账号登录密码
1adminadmin888
2itheima123456

权限表

权限id编号权限名称
1增加
2删除
3修改
4查询

用户与权限之间是多对多关系,需要通过中间表来关联。

---

二、连接查询介绍

连接查询可以实现多个表的查询,当查询的字段数据来自不同的表就可以使用连接查询来完成。

连接查询可以分为:

2.1 准备数据集

-- 1. 准备数据集
-- 创建数据库并切换
create database db_itheima;    -- 创建数据库db_itheima
use db_itheima;                -- 切换到db_itheima数据库

-- 创建班级表classes,拥有两个字段,cls_id代表班级编号,cls_name代表班级名称
drop table if exists classes;  -- 如果classes表存在则删除
create table classes(          -- 创建classes表
    cls_id int,                -- 班级编号字段
    cls_name varchar(20)       -- 班级名称字段
);

-- 插入数据,ui、java、python、shell
insert into classes values
    (1, 'UI'),                 -- 班级1:UI
    (2, 'Java'),               -- 班级2:Java
    (3, 'Python'),             -- 班级3:Python
    (4, 'Shell');              -- 班级4:Shell

-- 创建学生表students,拥有id、name、age、gender值使用male和female、score、cls_id
drop table if exists students; -- 如果students表存在则删除
create table students(         -- 创建students表
    id int,                    -- 学生编号字段
    name varchar(20),          -- 学生姓名字段
    age int,                   -- 学生年龄字段
    gender enum('male', 'female'),  -- 性别字段,只能是male或female
    score decimal(11,2),       -- 分数字段
    cls_id int                 -- 班级编号字段
);

-- 插入数据,刘备属于Java,貂蝉属于UI,赵云属于Python,关羽属于Python,大乔属于UI
insert into students values
    (1, '刘备', 18, 'male', 100.00, 2),    -- 刘备:Java班,100分
    (2, '貂蝉', 18, 'female', 99.00, 1),   -- 貂蝉:UI班,99分
    (3, '赵云', 18, 'male', 98.00, 3),     -- 赵云:Python班,98分
    (4, '关羽', 18, 'male', 96.00, 3),     -- 关羽:Python班,96分
    (5, '大乔', 18, 'female', 97.00, 1),   -- 大乔:UI班,97分
    (6, '小乔', 18, 'female', 97.00, 5);   -- 小乔:cls_id=5不存在

-- 创建学生详情表students_detail
drop table if exists students_detail;  -- 如果students_detail表存在则删除
create table students_detail(          -- 创建students_detail表
    id      int primary key auto_increment,  -- 主键,自动增长
    stu_id  int,                       -- 学生编号
    address varchar(200)               -- 地址
);

-- 插入数据
insert into students_detail (stu_id, address)
values (1,'aaaaaa'),           -- 学生1的地址
       (2,'bbbbbb');           -- 学生2的地址

2.2 交叉连接(笛卡尔积)

-- 交叉连接也称之为笛卡尔积连接,一旦出现,就会出现数据库性能问题
select * from students cross join classes;  -- 使用cross join关键字
-- 或
select * from students, classes;           -- 使用逗号分隔

笛卡尔积连接,没有意义,但是它是所有连接的基础。 其功能就是将表1和表2中的每一条数据进行连接。

结果:

idnameagegenderscorecls_idcls_idcls_name
1刘备18male100.0021UI
1刘备18male100.0022Java
1刘备18male100.0023Python
1刘备18male100.0024Shell
2貂蝉18female99.0011UI
........................
共 6 × 4 = 24 条记录

---

三、内连接(INNER JOIN)

说明:查询两个表中符合条件的共有记录。

语法格式

select 字段,... from 表1 inner join 表2 on 表1.字段1 = 表2.字段2;

说明:

案例1:查询学生及其班级(所有字段)

-- 案例:求每个学生所属的班级信息(学生所有字段 + 班级名称)
select * from students inner join classes on students.cls_id = classes.cls_id;

-- 给表起别名,在后续使用
select * from students as stu inner join classes as cls on stu.cls_id = cls.cls_id;

-- 要students表中所有字段,但只要classes中的cls_name字段
select stu.*,cls.cls_name from students as stu inner join classes as cls on stu.cls_id = cls.cls_id;

-- inner关键字可以不写,默认就是,所以直接写一个join就可以了
select stu.*,cls.cls_name from students as stu join classes as cls on stu.cls_id = cls.cls_id;

执行结果:

idnameagegenderscorecls_idcls_idcls_name
1刘备18male100.0022Java
2貂蝉18female99.0011UI
3赵云18male98.0033Python
4关羽18male96.0033Python
5大乔18female97.0011UI
小乔(cls_id=5)不匹配,不会出现。

案例2:指定字段 + WHERE 条件

-- 要students中指定的字段和classes表中指定的字段
select stu.id,stu.name,cls.cls_name from students as stu inner join classes as cls on stu.cls_id = cls.cls_id;

-- 要求student中分数在97及以上,要得到它的分类,还要指定字段
select stu.id,stu.name,stu.score,cls.cls_name from students as stu inner join classes as cls
on stu.cls_id = cls.cls_id
where stu.score>=97;

执行结果:

idnamescorecls_name
1刘备100.00Java
2貂蝉99.00UI
3赵云98.00Python
5大乔97.00UI

内连接小结

---

四、左外连接(LEFT JOIN)

说明:有一个主表概念,默认情况下,关联后会保留主表的所有记录。以左表为主根据条件查询右表数据,如果根据条件查询右表数据不存在,使用null值填充。

语法格式

select 字段 from 表1 left join 表2 on 表1.字段1 = 表2.字段2;

说明:

左外连接示意图

商品分类表

编号分类名称
1手机
2电脑

商品表

编号商品名称商品分类编号
1Vivo手机1
2Xiaomi手机1

左外连接查询select * from 分类表(主表) left join 商品表 on 分类表.编号 = 商品表.分类编号;

编号分类名称编号商品名称商品分类编号
1手机1Vivo手机1
1手机2Xiaomi手机1
2电脑nullnullnull
左外连接默认会保留左表,然后与右边的表进行匹配;如果匹配到,则显示右表对应的数据;匹配不到,也要显示。只不过右表的所有字段使用null进行填充!

案例:查询所有班级及其学生

-- 左外连接  主表和从表,取主表的全部和从表的交集部份,如果主表中的数据在从表中不存在,则使用null
select stu.id,stu.name,stu.score,cls.cls_name from classes as cls left join students as stu
on stu.cls_id = cls.cls_id;

执行结果:

idnamescorecls_name
5大乔97.00UI
2貂蝉99.00UI
1刘备100.00Java
4关羽96.00Python
3赵云98.00Python
NULLNULLNULLShell
Shell班没有学生,但左连接保证左表(classes)全部显示。

左连接后过滤

-- 左连接后再进行where过滤
select stu.id,stu.name,stu.score,cls.cls_name from classes as cls left join students as stu
on stu.cls_id = cls.cls_id where id is not null;

执行结果:

idnamescorecls_name
5大乔97.00UI
2貂蝉99.00UI
1刘备100.00Java
4关羽96.00Python
3赵云98.00Python

左外连接小结

---

五、右外连接(RIGHT JOIN)

说明:以右表为主根据条件查询左表数据,如果根据条件查询左表数据不存在则使用null值填充。

语法格式

select 字段 from 表1 right join 表2(主表) on 表1.字段1 = 表2.字段2;

说明:

案例:查询所有学生及其班级

-- 查看班级中对应的学生信息,如果某个班级没有对应的学生也要显示。(既可以用左外连接也可以用右外连接)
select stu.id,stu.name,stu.score,cls.cls_name from classes as cls right join students as stu
on stu.cls_id = cls.cls_id;

执行结果:

idnamescorecls_name
1刘备100.00Java
2貂蝉99.00UI
3赵云98.00Python
4关羽96.00Python
5大乔97.00UI
6小乔97.00NULL
小乔的cls_id=5没有对应班级,右连接保证右表(students)全部显示。

右外连接小结

---

六、子查询(扩展)

6.1 子查询介绍

在一个 select 语句中,嵌入了另外一个 select 语句,那么被嵌入的 select 语句称之为子查询语句。外部那个select语句则称为主查询语句

select * from (select * from xxx) as t;

作用:子查询比较适合复杂查询以及多层级查询结构。

主查询和子查询的关系:

应用场景:在我们需求的基础上,如果这个需求需要通过多条SQL语句分步查询的情况,一般都需要基于子查询。

注意:子查询会全表扫描两次,大数据量下需谨慎使用。所以我们说了解就行。

案例1:WHERE 中的子查询

-- 取出大于平均分的人信息  子查询   位置 where条件处
select * from tb_score where math > (select avg(math) from tb_score);

执行过程:子查询先算出平均分 84.6,主查询再过滤 math > 84.6 的记录。

idnamemathenglish
1刘备9085
4赵云9588
5貂蝉8892

案例2:SELECT 列表中的子查询

-- 在from取字段处
select name,math,(select max(math) as aa from tb_score) from tb_score;

执行结果:

namemathmax_math
刘备9095
关羽8095
张飞7095
赵云9595
貂蝉8895

案例3:自关联查询

-- 自关联,一张表自己和自己关联起来
select a.*,b.title from areas a join areas b on a.pid=b.id where a.id=110106;

---

七、连接查询对比表

特性内连接 (INNER JOIN)左外连接 (LEFT JOIN)右外连接 (RIGHT JOIN)
关键字inner join / joinleft joinright join
结果集两表交集左表全部 + 右表匹配右表全部 + 左表匹配
NULL 填充不填充右表无匹配填 NULL左表无匹配填 NULL
语法on 关联条件on 关联条件on 关联条件
使用场景查共有数据左表全量展示右表全量展示

---

第二部分:MySQL 体系结构

---

一、MySQL四层架构简介

面试题:MySQL底层,一条SQL语句的执行流程/原理?

:经过四层,分别是连接层(连接池)、服务层(查询缓存、分析器、优化器、执行器)、引擎层、存储层。

层级名称主要功能
第一层连接层管理连接、身份认证、连接池
第二层服务层SQL解析、优化、缓存
第三层引擎层数据存储和提取的实现
第四层存储层与操作系统交互,数据持久化

---

二、连接层

2.1 客户端(连接者)

MySQL的客户端可以是:

-- 查看当前的连接者
show processlist;

2.2 连接层核心功能

主要作用:管理和缓冲用户连接,为客户端请求做连接处理;身份认证等。

面试题:进程和线程?

概念特点适用场景
进程一个应用软件,启动后往往会产生1个甚至多个进程,进程需要消耗一定的计算机资源(CPU、内存、磁盘、网络),进程之间的数据是不共享的CPU密集型应用(大量的计算程序,需要消耗资源)
线程一个进程可以产生多个线程,线程不能单独存在,必须依赖进程。所有线程共享进程资源,线程启动、停止快,资源开销小IO密集型应用(文件操作、网络爬虫、数据库连接)

总结:进程是资源分配的基本单位,而线程是操作系统进行CPU调度和执行的最小单位。

2.3 连接池机制

连接层中的缓存池,就是为了优化数据库连接而设置的机制,专门用来缓存和复用已建立的连接。

主要作用:

工作流程:创建连接 → 用完放回池中 → 再次复用,保持高效循环。

2.4 连接池配置参数

-- 模糊查看wait_timeout
show variables like 'wait_timeout';

执行结果:

Variable_nameValue
wait_timeout28800
-- 查看当前mysql中系统变量
-- mysql中变量调用用 @@变量名
select @@wait_timeout;

执行结果:

@@wait_timeout
28800

配置文件 /etc/my.cnf

[mysqld]
max_connections=100          -- 最大连接数
thread_cache_size=4          -- 线程缓存池大小
wait_timeout=300             -- 连接闲置等待时间
interactive_timeout=300      -- 交互超时时间

注意事项:

三、服务层

主要作用:接受用户的SQL请求,查询分析,权限处理,优化,结果缓存等。

---

四、引擎层(重要)

4.1 什么是存储引擎?

1. 存储引擎说白了就是如何管理操作数据(存储数据、如何更新、查询数据等)的一种方法和机制 2. 在MySql数据库中提供了多种存储引擎,各个存储引擎的优势各不一样,现在mysql中推荐使用Innodb 3. 用户可以根据不同需求为数据表选择不同的存储引擎,也可以根据自己需要编写自己的存储引擎 4. 甚至一个库中不同的表使用不同的存储引擎,这些都是允许的

-- 查看当前mysql实例中支持的存储引擎种类
show engines;

-- 查看当前默认的存储引擎
select @@default_storage_engine;

执行结果:

@@default_storage_engine
InnoDB

4.2 MyISAM与InnoDB引擎对比表

特性MyISAMInnoDB
事务支持不支持支持(ACID特性)
外键支持不支持支持
锁机制表级锁行级锁
适用场景读多写少的应用,查询性能好高并发写操作的应用
崩溃恢复数据损坏风险高,无法自动恢复自动恢复,数据更安全,双写(dw double write)
存储结构每个表有单独的文件表和索引存储在表空间中
性能查询性能较好,但不支持事务处理支持事务处理,写操作性能较高

4.3 面试题:你用过哪些数据库引擎?各自特点?

:早期MySQL5.5版本使用过MyISAM引擎,后期MySQL5.7、MySQL8.0等等都是使用InnoDB引擎,偶尔也了解MEMORY引擎。

① MyISAM引擎

② InnoDB引擎

③ Memory引擎

---

五、InnoDB事务处理

5.1 应用场景:银行转账(最典型)

我的银行卡:1.00
itwu银行卡:2000.00

发生一系列操作:
① itwu发起转账,扣款1000,余额-1000
② 银行接收任务,处理(ATM)
③ 我的银行卡接收到1000,余额+1000
update bank set money=money-1000 where name = "itwu"
-- 交易系统停电了
update bank set money=money+1000 where name = "我"

5.2 事务操作步骤

操作步骤:事务处理配合Python/Java程序一起使用,就是把所有要执行的SQL语句当做一个整体,要么全部成功,要么全部失败。

要想完成事务操作,你的创建表时所用的存储引擎,必须且一定要为 innodb

-- 查看表的存储引擎
show create table 表名;

事务三步骤:

-- ① 开启事务处理功能
start transaction;

-- ② 执行一系列的SQL语句(多条)
update bank set money=money-1000 where name = "itwu";
update bank set money=money+1000 where name = "我";

-- ③ 判断SQL语句是否全部执行成功,如果成功则提交事务 => commit; 失败,则回滚事务 => rollback;
commit;

5.3 事务示例

-- 创建InnoDB引擎的表
create table bank (
    id int primary key auto_increment,  -- 主键,自动增长
    name varchar(20),                   -- 姓名
    money int not null default 1        -- 余额,默认为1
);

-- 查看表结构
show create table bank;

-- 插入测试数据
insert into bank (name,money) values ('itwu',2000),('我',1);

-- 代码示例:
-- 开始一个事务
start transaction;

-- 扣款
update bank set money=money-1000 where name = "itwu";

-- 收款
update bank set money=money+1000 where name = "我";

-- 如果事务中没有问题,则提交commit;如果发现有问题则可以rollback
commit;

执行结果:

idnamemoney
1itwu1000
21001

---

六、存储层(物理层)

核心作用:物理层负责与底层的操作系统交互,将数据存储到磁盘上,并确保数据的物理安全。

默认存储位置/usr/local/mysql/data 数据目录下

工作方式:

---

第三部分:数据引擎详解

---

一、MyISAM引擎

-- 永久修改默认存储引擎方案
-- vi /etc/my.cnf
-- [mysqld]
-- default_storage_engine=innodb

-- 在创建表时来指定存储引擎  engine
create database test5;
use test5;

-- 创建一个表并指定它的存储引擎为myisam
create table user1(id int) engine=myisam;

MyISAM引擎文件结构:

文件说明
*.sdi序列化字典信息(Serialized Dictionary Information)表的元数据信息,主要用于数据字典管理;数据表结构、字段、类型等等
*.MYIINDEX索引,主要用于存放索引文件
*.MYDData数据文件,主要用于存储数据文件

注意:早期MySQL5.7及以前版本,没有*.sdi文件,只有*.frm文件。早期MySQL5.7及以前版本,我们可以通过cp *.frm、*.MYI、*.MYD这三个文件来实现MyISAM引擎表的备份,MySQL8.0以后引入更多复杂的功能,导致没有办法直接copy,只能通过物理备份或逻辑备份!

---

二、InnoDB引擎

-- 创建一个表并指定它的存储引擎为innodb
-- 如果默认mysql它的引擎就为innodb,所以你可以不指定
create table user2(id int) engine=innodb;

-- 查看一下表空间大小
select @@innodb_data_file_path;

执行结果:

@@innodb_data_file_path
ibdata1:12M:autoextend

InnoDB引擎文件结构:

文件说明
.ibd每个表都会有一个独立的.ibd文件,存储该表的表数据和索引
ibdata1用于存储全局的表空间、数据字典和事务日志等

表空间类型:

表空间:MySQL的InnoDB存储引擎采用表空间来管理数据,可以把它看作是数据库内容的物理存储仓库。用这样的方式为了更好的去扩容。

-- 配置文件 /etc/my.cnf
-- innodb_file_per_table=1
-- 建议最多不要超过3个
-- innodb_data_file_path=ibdata1:12M;ibdata2:100M:autoextend

redo log日志文件(ib_logfile0, ib_logfile1等):用于存储事务日志,确保数据一致性和恢复能力。

redo log配置:

# /etc/my.cnf
[mysqld]
# 每个重做日志文件的大小
innodb_log_file_size = 50M
innodb_redo_log_capacity=100M
# 重做日志组中日志文件的个数 2(默认值)
innodb_log_files_in_group = 2
# redo log 文件存放路径
innodb_log_group_home_dir = /usr/local/mysql/data

注意事项:

第四部分:慢查询日志与索引优化

---

一、慢查询日志配置

1.1 启动慢查询日志(重点)

SQL语句:执行过程中,有快有慢,找出慢查询SQL。

作用:慢查询日志记录了所有执行时间超过设定阈值的查询(300ms、1s、5s根据不同业务场景而定),帮助你发现需要优化的慢查询SQL语句,辅助优化。

如何启用

# /etc/my.cnf
[mysqld]
# 开启慢查询日志
slow_query_log=1
# 设置超过 1 秒的查询被记录
long_query_time=1
# 指定慢查询日志文件存放路径
slow_query_log_file=/usr/local/mysql/logs/mysql-slow.log

# 记录未使用索引的查询(可选)
log_queries_not_using_indexes=1

# 通用日志  开启会他们记录所有的sql操作,建议不要在生产环境中去开启,只要测试环境中去开启,方便调试
general_log=1
general_log_file=/usr/local/mysql/data/general.log

注意事项:

1.2 日志内容示例

# Time: 2026-05-26T10:00:38.070872Z
# User@Host: root[root] @ localhost []  Id:     8
# 执行的多久
# Query_time: 0.419292  Lock_time: 0.000002  Rows_sent: 655360  Rows_examined: 655360
use test5;
SET timestamp=1779789637;
# 语句
select * from user2;

日志字段说明:

字段含义
Query_time查询耗时(秒)
Lock_time锁等待时间
Rows_sent返回行数
Rows_examined扫描行数(越少越好)

分析日志:慢查询日志可以显示查询的执行时间、锁等待时间、以及查询的详细内容。分析日志后,优化这些慢查询是提升性能的首要任务。

1.3 案例设计 - 慢查询

添加200万条数据到数据表中,做测试(不要求掌握以下,只是为了做测试)

use db_itheima;

-- 创建测试表
drop table if exists tb_people;
create table tb_people (
    id   int auto_increment primary key,  -- 主键,自动增长
    name varchar(50),                     -- 姓名
    age  int                              -- 年龄
);

-- 创建存储过程
drop procedure if exists insertdata;
delimiter //
create procedure insertdata()
begin
    declare i int default 1;              -- 声明变量i,默认值为1
    start transaction;                    -- 开启事务
    while i <= 2000000                    -- 循环200万次
        do
            insert into tb_people (name, age)
                values (concat('user_', i), floor(18 + (rand() * 42)));
            set i = i + 1;               -- i自增
        end while;
    commit;                               -- 提交事务
end
// delimiter ;

-- delimiter用于临时更改语句结束符(默认;),常用于定义存储过程、触发器等包含分号的复合语句。

-- 调用存储过程
call insertdata();

-- 移除自动增长,因为有自动增长的情况,主键无法移除的,因为自动增长所在字段要求必须是一个key索引
alter table tb_people
    change id id int,      -- 先移除自动增长
    drop primary key;      -- 移除主键

-- 检查数据生成结果
select count(*) from tb_people;

执行结果:

count(*)
2000000
-- 做一个查询,然后查看效果
select * from tb_people where id = 1990000;

-- 查看性能分析
EXPLAIN select * from tb_people where id = 1990000;

---

二、EXPLAIN执行计划

2.1 简介

简单来说:explain/desc执行计划就是用于分析一个SQL语句如何执行的,核心作用:帮助我们提升SQL查询效率,加快查询速度。

2.2 索引概念

Index索引

学不认识的汉字,会去词典当中寻找

2.3 EXPLAIN命令

简单来说,EXPLAIN是用来查看数据库如何执行一个SQL查询的工具。它能告诉你数据库在执行查询时,选择了什么样的操作步骤(比如是扫描整个表还是使用索引等),并且通过这些信息你可以判断查询是否高效。

作用:假设你写了一个SQL查询来查询数据,如果查询效率低下(比如很慢),你可能想知道数据库是怎么执行的,找出瓶颈在哪。EXPLAIN就是用来帮你"拆解"查询,了解数据库是如何处理每一步的。

如何使用

EXPLAIN SELECT * FROM orders WHERE user_id = 123;

2.4 分析结果

当你在查询前加上EXPLAIN时,它会显示一个执行计划的表格,这个表格给出了执行查询时的每一个细节。

核心字段说明:

字段说明重要程度
type连接类型,反映查询效率最重要
key实际使用的索引(NULL=没用索引)很重要
rows估算扫描行数(越少越好)很重要
possible_keys可能使用的索引重要
Extra额外信息重要

详细字段说明:

字段说明
id这个数字表示查询的执行顺序。对于复杂的查询,可能会有多个查询步骤,id就帮助你理解它们的顺序
select_type表示查询的类型,是普通查询还是复杂查询(比如涉及子查询等)
table代表查询是从哪张表中获取数据
type查询是如何连接数据的。这个字段非常重要,它告诉你查询效率的高低
possible_keys查询可能会使用到哪些索引。索引就像数据的"目录",使用索引可以加速数据查找
key实际使用了哪个索引
key_len使用的索引的长度(表示数据库选择了多长的索引)
ref显示与哪个列进行匹配来获取数据
rows数据库估算的需要扫描的行数。越少的行数,意味着查询越高效
Extra额外信息,比如是否使用了临时表或者文件排序等

2.5 type效率对比表(从高到低)

type速度说明
const最快主键/唯一索引等值查询
ref普通索引等值查询
range较快索引范围查询(BETWEEN、>、<)
index较慢全扫描索引树
ALL最慢全表扫描(必须优化!)

---

三、索引类型简介

3.1 查看索引

-- 查看索引
SHOW INDEX FROM 表名;  -- 详细显示所有索引
-- 等价于
show keys from 表名;

-- 第二种:直接展示
desc student;             -- 简略显示,PRI/UNI/MUL 标记索引类型

3.2 唯一索引 unique

-- unique 唯一索引
alter table 表名 add unique (字段);

3.3 普通索引 key/index

在MySQL当中,Index的中文是索引。

-- key/index 普通索引,这里以 name 举例
alter table student add index 索引名称 (字段);
-- 或者
create index 索引名称 on 表名 (字段);

3.4 联合索引

联合索引,其实是普通索引的扩展。通常适用于加快查询多个字段的结果。

-- 联合索引:可以针对多个字段建立索引 on 数据表(字段1,字段2)
alter table tb_student add index index_name_age (name, age);
-- 或者
create index index_name_age on tb_student (name, age);

---

四、加索引前后对比案例

4.1 未加索引

-- 没加索引情况
explain select * from tb_people where id = 1990000;
+----+------+------+------+---------+----------+-------------+
| id | type | key  | rows | filtered | Extra                  |
+----+------+------+------+---------+----------+-------------+
|  1 | ALL  | NULL | 2M   | 100.00  | Using where |
+----+------+------+------+---------+----------+-------------+

4.2 添加主键索引

-- 添加主键索引
alter table tb_people add primary key(id);

-- 查看表结构
desc tb_people;
show create table tb_people;
show index from tb_people;

4.3 加索引后

-- 添加索引的情况
explain select * from tb_people where id = 1990000;
+----+-------+---------+------+----------+-------+
| id | type  | key     | rows | filtered | Extra |
+----+-------+---------+------+----------+-------+
|  1 | const | PRIMARY |    1 | 100.00  |       |
+----+-------+---------+------+----------+-------+

对比: type从ALL → const,rows从200万 → 1,性能提升约200万倍。

4.4 执行计划详细说明

字段说明
id1表示这是查询的第一步
select_typeSIMPLE表示这是一个简单查询,没有复杂的连接操作
tablesimple_table表示查询的数据表
typeconst表示使用常量值(如WHERE id = 1990000中的1990000)来匹配到了结果
possible_keysPrimary表示数据库考虑过使用的Key
keyPrimary表示数据库实际使用的主键约束来查的
key_len4表示数据库使用了该索引,占了4Bytes
refconst表示查询近似于使用了常量时间(即查询复杂度低)
rows100表示数据库预计需要扫描100行数据
filtered100表示过滤(where id = 1990000)出来的数据,100%命中了结果
Extra没有额外的信息-

---

五、key_len长度说明

数据类型占用字节
INT类型4字节
VARCHAR(n)类型n字节(最大长度)
DATE类型3字节
CHAR(n)类型n字节

---

六、面试题

问:在运维环境中,发现MySQL运行缓慢,如何去解决?

答:

从系统层面,检查系统资源使用情况:

从日志层面,开启慢查询日志,把查询缓慢的SQL写入到慢查询日志中,进行捕获

从SQL语句层面,使用explain执行计划分析SQL执行过程,是否有走缓慢等等,查看具体慢的原因。如果是全表扫描,可以考虑引入索引(如主键索引、唯一索引、普通索引)等等实现优化操作

---

全文完