一、基础知识
1、select 语句的查询流程是怎样的
- 客户端发送这个语句到mysql服务器
- mysql服务器的连接器开始处理这个需求,并和客户端建立连接
- 解析器对sql语句进行解析(检查语句有没有语法错误,引用的数据库表都存在,权限正确)
- 优化器找到最高效的执行计划(索引)
- 执行器调用存储引擎(InnoDB支持事务,MyISAM不支持事务)的api来进行数据读写
- 客户端收到查询结果,完成查询请求
sql的执行顺序
select - from - where - group by - having - order by - limit
2、常用命令
sql
# 创建数据库
create database database_name;
# 删除数据库
drop database database_name;
# 选择数据库
use database_name;
# 创建表
create table table_name (
column1 datatype,
column2 datatype,
...
);
# 删除表
drop table table_name;
# 显示所有表
show tables;
# 查看表结构
describe table_name;
# 修改表(添加列)
alter table table_name add column_name datatype;
# 插入数据
insert into table_name (column1, column2, ...) values (value1, value2, ...);
# 查询数据
selet column_name from table_name where condition;
# 更新数据
update table_name set column1 = value1, column2 = value2 where condition;
# 删除数据
delete from table_name where conditon;
# 创建索引
create index index_name on table_name(column_name);
# 添加主键约束
alter table_name add primary key (column_name);
# 添加外键约束
alter table table_name add constraint fk_name foreign key (column_name) references parent_table (parent_column_name);
# 创建用户
create user 'username'@'host' identified by 'password';
# 授予权限
grant all privileges on database_name.table_name to 'username'@'host';
# 删除权限
revoke all privileges on database_name.table_name from 'username'@'host';
# 删除用户
drop user'username'@'host';
# 事务
# 开始事务
start transaction;
# 提交事务
commit;
# 回滚事务
rollback;
3、存储引擎
Innodb 支持事务,行级锁(高并发写入性能更高),外键
MyISAM不支持事务和外键,支持表级锁(查询速度快,只适合只读的静态报表)
4、日志
-
错误日志
-
慢查询日志:记录超时的 sql 语句(设置 long query time)
-
一般查询日志:记录 mysql 服务器的连接信息以及 sql 语句
-
二进制日志(bin log):记录所有修改数据库状态的 sql 语句(insert, update, delete)
主从复制用到的就是二进制日志
两个 Innodb 存储引擎的日志文件:
-
重做日志(redo log):记录 Innodb 表的每个写操作,主要用于崩溃修复(物理日志)。当事务进行写操作时,Innodb 会首先写入 redo log 并不会立刻修改数据文件,这种写入方式被称为 write ahead logging(先写日志),后续 Innodb 将这些更改异步更新到数据文件中,从而这种方式能够预防系统崩溃导致的数据未写入,确保数据的持久性。
-
回滚日志(undo log):记录数据被修改前的值,用于事务的回滚
通过回滚日志,可以实现 MVCC(多版本并发控制)
bin log 和 redo log 的区别:
-
bin log 记录所有与数据库相关的日志记录(包括Innodb和MyISAM等存储引擎的日志);而redo log 只记录 Innodb 存储引擎的日志
-
bin log 记录关于一个事务的具体操作语句(为逻辑日志);redo log 记录的是每页内容具体更改的情况(物理日志)
-
写入时间不同:bin log 在事务提交前提交;在事务进行时,不断有 redo log 写入
-
写入方式不同:bin log 为循环写入和删除;redo log 为追加写入,不覆盖已有文件
redo log 什么时候刷入磁盘:
-
log buffer 空间不足
-
事务提交时
-
后台线程输入:通过后台线程设置过一段时间刷新 log buffer 到磁盘中
-
正常关闭服务器
-
触发 checkpoint 规则:循环覆盖写入
5、索引(性能优化)
mysql 的数据结构:b+ 树
索引为什么会加快查询:
索引相当于给数据加目录,避免全表扫描
索引的分类(按功能):
-
主键索引:唯一 + 非空
-
唯一索引:唯一
-
普通索引
-
全文索引:只用于文本数据
索引的分类(按数据结构):
-
b+ 树索引:索引对应数值存在二叉树中,每次查询从根节点开始,遍历叶子节点查询效率为 O(logN)
-
哈希索引:只适合 = 和 in 查询,不适合范围查询,查询效率为 O(1)
索引的分类(按存储位置):
-
聚簇索引:索引的叶子节点保存一行的所有列的信息
-
非聚簇索引:叶子节点只包含一个主键值,通过非聚簇索引先找到主键,再通过主键找到聚簇索引得到对应记录行内容(这整个过程被称为回表)
选择什么列作为索引列?
- 经常作为查询条件,排序条件,分组条件的列(where 子句,order by 子句,group by 子句)
什么列最好不作为索引列?
-
频繁更新的列
-
列中的唯一值较少的列(性别)
要避免过多的索引
- 因为每个索引会占用额外的磁盘空间,同时维护索引也需要成本
前缀索引能减少索引的大小
什么情况下索引会失效(范围查询 & 函数)
-
使用函数表达式的列无法添加索引
-
使用 <> 或 not 操作符(因为这两个操作会扫描全表,导致索引失效)
-
使用 or 操作符
Innodb 选择 b+ 树的原因
b+ 树 和 b 树 的区别:
-
b+ 树所有值存在叶子节点,并且叶子节点通过指针连接,形成一个有序的链表。因此有更高的查询效率
-
b 树每个节点(根节点,中间节点)都存数据;而 b+ 树非叶子节点不存储数据,比 b 树分叉更多,导致树的高度较低,降低查询过程中对磁盘IO的占用
-
同时查询效率更加稳定,查询速度快
总结:b+ 树形状 "矮胖",且适合范围查询
聚簇索引和非聚簇索引的区别
-
每个表只有一个聚簇索引,Innodb 中逐渐就是聚簇索引
-
聚簇索引中,表中的行按照索引顺序存储
-
非聚簇索引中,索引和数据分开存储
什么是回表
- 使用非聚簇索引查找数据时,数据库先找索引位置,再根据索引位置定位数据行
最左前缀匹配原则(联合索引)
- 在使用联合索引时,查询条件从索引的最左列开始并不跳过中间的列
什么是覆盖索引
- 如果查询的所有字段都在索引中,无需回表查询
6、事务
事务的四大特性:
-
原子性:事务中所有操作要么全部提交成功,要么全部失败(通过undo log保证)
-
一致性:事务确保数据库状态从一个一致性状态变为另一个一致性状态(通过其他三个特性来确保)
-
隔离性:多个并发事务相互隔离(通过MVCC保证)
-
持久性:一旦事务提交,修改将永久保留在数据库中。即使系统崩溃数据也不会丢失(通过redo log保证)
隔离级别:
-
读未提交:事务可读取未被其他事务提交的数据,会出现脏读、不可重复读、幻读的问题
-
读已提交:事务可读取已经被其他事务提交的数据,可避免脏读,但不可重复读和幻读仍然存在
-
可重复读:确保同一事物读取相同记录的结果为一致的,即使其他事务对这条记录进行修改,可避免脏读和不可重复读,大程度减少幻读
-
串行化:最高的隔离级别,强制事务串行执行,但会导致超时和锁竞争
读已提交&可重复读可以通过 MVCC 中的ReadView 来实现
-
读已提交在每次读取数据前都生成一个readview,保证每次读取操作为最新的数据
-
可重复读只在第一次读操作时生成一个readview,后续都使用这个readview,从而保证一致
串行化的实现
-
事务在读操作时,必须先加表级共享锁,直到事务提交后才释放
-
事务在写操作时,必须先加表级排他锁,直到事务结束才释放
并发的三大问题:
-
脏读:事务A,B并发执行,事务A读到B的未提交的数据
-
不可重复读:一个事物范围内,两个相同查询,读取同一条记录,却返回了不同的数据
-
幻读:事务A查询结果集,并发事务B往这个结果集中进行插入和删除数据,并提交,事务A再次查询相同结果集却得到了不同的数据结果
MVCC(多版本并发控制)
-
在支持 MVCC 的数据库中,当多个用户同时访问数据库时,每个用户都可以看到再某一个时间点之前的数据库快照。从而保证多个用户之间不会相互干扰,同时能无阻塞的执行查询和修改操作
-
实现读写操作并行进行
ReadView(读视图)
-
主要用来处理可重复读和读已提交
-
在事务刚开始执行时,创建ReadView,该ReadView会包含以下信息:1、已开始但是未提交的事务ID列表;2、所有活跃事务的最小事务ID;3、活跃事务的最大ID+1;4、创建该 ReadView 的事务ID
7、锁
锁的种类:
-
表锁
-
行锁
-
页锁
-
共享锁(= 读锁)
-
排他锁(= 写锁)
-
乐观锁:通过数据表中使用版本号或时间戳来实现。每次读取记录时,同时获取版本号或时间戳,更新时检查这两个是否发生变化
-
悲观锁:适合锁冲突常见的情况。直接用表锁、行锁来锁定被访问的数据
如:select for update 语句加排他锁
Innodb 行锁的实现
-
记录锁:直接锁定某行数据。如当使用唯一性索引进行查询时会将查到的记录锁定
-
间隙锁:锁定两个记录之间的间隙。如使用范围查询时,如果没有命中任何记录,此时就会将对应的间隙区间锁定(为一个左开右开的区间)
-
临键锁 = 间隙锁(左开右闭区间)= 又记住记录行也记住间隙。如查到一条记录,临键锁=记录锁;没查到记录,则变为间隙锁
意向锁
-
意向锁为表级锁,目的就是去判断表里面有没有行锁
-
事务A锁住了某一行,事务B想要锁这整张表。如果没有意向锁,B需要遍历表中每行检查是否已经加了行锁。而有了意向锁后,他会在A锁住行之前先给整张表添加一个表级的意向锁,从而直观地告诉B
排查死锁的步骤
-
查看死锁的日志 show engine innodb status
-
找出死锁 sql
-
分析 sql
-
分析死锁日志
-
分析死锁结果
8、sql 优化
定位慢 sql(慢查询日志)
- 找到对应慢 sql 后,使用explain查看sql语句
避免不必要的列:
-
避免 select *
-
分页优化两种方法:1、延迟关联(通过先检索索引,再根据索引id关联行内容);2、书签(通过记住上次查询返回的最后一行的值,下次查询直接从这个值开始)
索引优化:
-
覆盖索引,避免回表
-
避免使用 != 或 or 操作符
-
避免对列上使用函数
-
联合索引
join 优化:
-
优化子查询
-
join 时,用小表来连大表
-
适当增加冗余字段
-
避免join太多表
排序优化:
- 设计索引时考虑排序的需求
Union 优化:
- 用 Union all 代替 Union,Union all 能去重
学会看 explain(select 语句中添加 explain 关键词)(重要)
| 列名 | 核心作用 | 🔥 面试高频考点 & 避坑指南 |
|---|---|---|
| id | 查询的执行顺序 | id 越大越先执行 ;id 相同则从上到下 执行。若 id 为 NULL,代表是 UNION 合并后的结果集,最后执行。 |
| select_type | 查询的复杂类型 | ① SIMPLE (简单查询,无子查询/UNION) ② PRIMARY (最外层主查询) ③ SUBQUERY (子查询) ④ DERIVED (派生表,即 FROM 子句里的子查询) ⑤ UNION (UNION 后面的查询) |
| type ⭐ | 访问性能(最重要) | 性能从高到低排序 : system > const > eq_ref > ref > range > index > ALL 优化目标至少要达到 range 级别,理想是 ref 或 const 。看到 ALL(全表扫描)必须优化。 |
| possible_keys | 可能用到的候选索引 | 有值不代表一定用,只是 MySQL 的备选名单。 |
| key ⭐ | 实际选用的索引 | 如果 possible_keys 有值,但 key 为 NULL,说明索引失效(如用了 LIKE '%xx'、隐式类型转换、函数操作)。 |
| key_len ⭐ | 索引使用的字节长度 | 例如 (name, age) 索引,key_len 显示只用了 name 的长度,说明 age 没走索引(可能被范围查询截断了)。 公式 :varchar(n) ≈ 3n+2 字节,int ≈ 4 字节,null 额外 +1 字节。 |
| rows | 预估扫描的行数 | 越小越好。这是优化器估算的值,虽然不精确,但突然从百级跳到百万级,说明 SQL 有问题。 |
| Extra ⭐⭐⭐ | 额外信息 | ✅ Using index ------ 覆盖索引 !数据直接从索引取,不回表,性能优秀 (加分项)。 ✅ Using index condition ------ 索引下推(ICP) ,5.6 后的优化,减少回表次数。 ⚠️ Using where ------ 存储引擎层返回数据后,Server 层再过滤,通常意味着索引利用不充分。 ❌ Using filesort ------ 文件排序 (内存/磁盘),代表 ORDER BY 没走索引,必须优化 。 ❌ Using temporary ------ 临时表 ,常见于 GROUP BY 或 DISTINCT 不走索引,性能极差,必须重构 SQL。 |
9、主从复制
主服务器上,所有修改数据的语句(insert, update, delete)被记录到二进制日志中;主服务器上的一个线程(二进制日志转储线程)负责读取二进制日志的内容并发送给从服务器。从服务器接收到该数据,将这些改动写入自己的中继日志(relay log)中,从服务器上有一个sql线程会读取中继日志并将这些修改异步应用到从服务器中
10、分库分表
分表(大表拆分为小表,减轻单表的压力)
- 垂直分字段,水平分数据
分表策略:
-
范围路由:根据某个字段的值的范围进行分表
-
哈希路由:通过对分片进行哈希计算,取模来确认数据存储的表。优点:数据均匀分布
-
配置路由:通过新建一个配置表来决定数据划分
分库(把数据分散到多台机器上)
-
垂直分库:按业务模块划分
-
水平分表:按一定策略将一个表中的数据拆分到多个库中
分库分表的代价
-
分布式ID的问题,不能依赖自增主键了
-
跨库查询
二、真题练习
找出连续三天及以上活跃的用户

提示:row_number()
代码:
sql
select
uid
from (
select
uid,
dt,
row_number() over (partition by uid order by dt) rn,
date_sub(dt,row_number() over (partition by uid order by dt)) ds
from
useractive
) tmp
group by
uid,ds
having
count(*) >= 3;
其中临时表长这样:
