MySQL面试题
此笔记为本人备考面试时整理,内容多源于网络搜集,仅作后续查阅之用,无盈利意图。若有侵权,请联系删除。
参考资料:
一、MySQL基础
1、数据库三大范式
1NF 要求字段值必须是不可再分的原子值。
反例:
用户信息表
中地址
字段存储 “北京市海淀区”,未拆分为省
、市
、区
,违反 1NF;
正例:拆分为province
、city
、district
,每个字段独立存储。2NF 1NF的基础上,要求非主属性完全依赖主键,避免部分依赖(如订单明细拆分)。
场景:
订单明细表
((订单号
+商品ID
为主键)+ 金额 ),若字段订单金额
仅依赖订单号
,则存在部分依赖(非主属性订单金额
不依赖商品ID
),违反 2NF;
优化:拆分为订单表
(订单号、金额)和订单明细表
(订单号、商品 ID、数量)。3NF 2NF的基础上,非主键字段之间不能有依赖关系,消除传递依赖,如学生表与班级表分离。
反例:
学生表
(学生 ID,姓名,班级 ID,班级地址)中,班级地址
依赖班级ID
,形成 “学生 ID→班级 ID→班级地址” 的传递依赖,违反 3NF;
优化:拆分为学生表
(学生 ID,姓名,班级 ID)和班级表
(班级 ID,班级地址)。
2、char 和 varchar 的区别是什么?
特性 | CHAR | VARCHAR |
---|---|---|
存储方式 | 固定长度 | 可变长度 |
空间占用 | 始终占用定义长度(可能浪费) | 仅占用实际数据长度 + 长度标识 |
存储开销 | 无额外开销 | 额外1~2字节存储长度 |
查询速度 | 更快(固定长度直接定位) | 稍慢(需计算位置) |
适用场景 | 长度固定的短字符串(如MD5) | 长度变化大的字符串(如地址) |
空格处理 | 存入时补足空格,查询时去除 | 原样存储和返回 |
CHAR
是定长分配,适合存储固定长度短字符串(如验证码),查询更快但可能浪费空间;VARCHAR
是变长存储,适合长度不确定的数据(如用户名),空间利用率高但需额外长度标识。
3、varchar (100)和 varchar (10)的区别是什么?
1. 存储机制
- 两者相同点:
- 都是可变长度字符串类型
- 实际存储空间 = 字符数 + 长度标识字节(1-2字节)
- 存储”Hello”时都占用 5 字节 + 1 字节开销 = 6 字节
存储相同的字符串,所占用磁盘的存储空间其实是一样的
- 差异:
- varchar (100) 最多存储100字符
- varchar (10) 最多存储10字符
2. 性能影响
- 内存分配:
- 排序操作时,数据库可能按最大长度分配内存
VARCHAR(100)
的列可能比VARCHAR(10)
多消耗 10 倍内存
- 索引效率:
- 索引大小:
VARCHAR(100)
索引大约是VARCHAR(10)
索引的 10 倍
- 索引大小:
- 适用场景:
varchar(10)
:短文本(如验证码、商品编码前几位);varchar(100)
:较长文本(如用户名、文章标题、地址片段)。
4、in和exists的区别?
5、怎么存储 emoji?
因为 emoji(😊)是 4 个字节的 UTF-8 字符,而 MySQL 的 utf8 字符集只支持最多 3 个字节的 UTF-8 字符,所以在 MySQL 中存储 emoji 时,需要使用 utf8mb4 字符集。
1 | ALTER TABLE mytable CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; |
MySQL 8.0 已经默认支持 utf8mb4 字符集,可以通过 SHOW VARIABLES WHERE Variable_name LIKE 'character\_set\_%' OR Variable_name LIKE 'collation%';
查看。
6、drop、delete 与 truncate 的区别?
DELETE 支持行级删除,可以带 WHERE 条件,可以回滚。
DROP 是物理删除,用来删除整张表,包括表结构,且不能回滚。
TRUNCATE 用于清空表中的所有数据,但会保留表结构,不能回滚。
7、UNION 与 UNION ALL 的区别?
UNION 会自动去除合并后结果集中的重复行。UNION ALL 不会去重,会将所有结果集合并起来。
8、count(1)、count(*) 与 count(列名) 的区别?
- count(*):
count(*)
会统计结果集中所有行的数量,包括所有列,不忽略任何行,即使某些列包含NULL值。- 它是一个标准的SQL函数,用于统计表中的总行数。
- count(1):
count(1)
中的1是一个常量值,表示对每一行进行计数。- 由于常量值永远不为NULL,因此
count(1)
实际上与count(*)
一样计算所有行。 - 在MySQL中,优化器通常会将
count(1)
优化为与count(*)
相同的执行计划。
- count(列名):
count(列名)
会统计结果集中指定列非NULL值的数量。- 如果列中的所有值都是非空的,那么
count(列名)
的结果与count(*)
的结果相同。 - 但是,如果列中有空值(NULL),那么
count(列名)
的结果会小于count(*)
的结果。
9、NULL 和 ‘ ‘ 的区别是什么?
1. 本质含义
NULL
:
表示 “值不存在”或“未知”,相当于 “无值”。它不是空字符串,也不是数字0
,而是一个特殊的标记。如果列允许
NULL
且未指定默认值,则插入时默认为NULL
。''
(空字符串):
表示 “空值”,是一个长度为 0 的有效字符串,明确表示 “这里有一个空字符串”。需要显式设置默认值为
''
(例如:ALTER TABLE tbl MODIFY col VARCHAR(10) DEFAULT '';
)。
2. 存储空间
NULL
:
通常需要额外 1 位(bit)来标记字段是否为NULL
(对于允许NULL
的列)。''
:取决于存储引擎和字符集。例如:
- 在 UTF-8 中,
''
占用 0 字节; - 在
varchar
中,可能需要 1~2 字节存储长度信息(即使长度为 0)。
- 在 UTF-8 中,
3. 查询与比较
**
NULL
**:不能用
=
直接比较,必须用IS NULL
或IS NOT NULL
。1
2WHERE column IS NULL; -- 正确
WHERE column = NULL; -- 错误!永远返回 false在聚合函数(如
COUNT()
、SUM()
)中会被忽略(除非用COUNT(*)
)。
**
''
**:可以用
=
直接比较。1
WHERE column = ''; -- 正确
在聚合函数中会被视为有效值。
关键判断标准:如果某字段必须有值(即使是空),用 ''
;如果可能无需赋值,用 NULL
。
10、mysql 的深度分页如何优化
深度分页是指当查询结果集很大时(如 LIMIT 1000000, 10),MySQL 需要扫描大量数据才能返回少量结果的性能问题场景。
方案1:游标分页
- 原理:记录上一页最后一条数据的 标识(如 ID),用
WHERE
代替OFFSET
。
1 | -- 第1页:初始查询 |
优点:
- 时间复杂度 O(1)
- 无性能衰减
- 支持高并发
限制:
- 只能顺序翻页,不能跳页,适合 APP 滚动加载。
- 需有序且唯一的列(如自增ID、时间戳)
方案2:覆盖索引 + 延迟关联
原理:利用索引先获取主键(减少扫描量),再回表取全字段。
1
2
3
4
5
6
7SELECT * FROM orders
INNER JOIN (
SELECT id FROM orders
ORDER BY create_time DESC
LIMIT 1000000, 10 -- 索引覆盖扫描
) AS tmp USING(id);
-- USING(id) 等价于 ON table1.id = table2.id优化效果:
- 索引扫描代替全表扫描
- 减少回表数据量
- 性能提升 5-10 倍
方案3:范围分页
原理:通过 WHERE 条件缩小扫描范围
1
2
3SELECT * FROM t
WHERE create_time BETWEEN '2025-01-01' AND '2025-01-31'
ORDER BY id LIMIT 10;适用场景:
- 时间范围明确的数据
- 分区表的分区键查询
11、你知道哪些方法来优化 SQL?
- 针对性建索引:为
WHERE
、JOIN
、GROUP BY
、ORDER BY
后的字段建索引(这些是查询的过滤、关联、排序核心)。 - 联合索引遵循 “最左前缀原则”:将区分度高的字段放在前面(如
INDEX (age, name)
可优化age=30 AND name='张三'
,但不能优化name='张三'
)。 - 避免索引失效:
- 索引列不参与函数 / 计算(如
SUBSTR(name,1,1)='张'
会失效,改用name LIKE '张%'
); - 避免
OR
连接非索引字段、字符串不加引号(如phone=13800138000
若phone
是字符串会隐式转换); - 控制索引数量(单表建议不超过 5-8 个,避免影响增删改性能)。
- 索引列不参与函数 / 计算(如
- 避免全表扫描:查询必须带有效过滤条件,分页用
LIMIT
并配合索引(如LIMIT 10000,10
需ORDER BY
字段有索引)。 - **不用
SELECT *
**:只查需要的字段,可能触发 “覆盖索引”(索引包含所有查询字段,无需回表)。 - **子查询改
JOIN
**:子查询(尤其相关子查询)可能重复执行,JOIN
更高效(如IN (子查询)
改JOIN
关联)。 - 优化
JOIN
逻辑:- 小表驱动大表(减少外层循环次数);
- 连接字段必须建索引(如
a.id = b.a_id
中b.a_id
需索引)。
- 排序 / 分组依赖索引:
ORDER BY
/GROUP BY
的字段有索引时,可避免额外排序(如Using filesort
)。 - 合理选择数据类型:用更小的类型(如
INT
代替BIGINT
)、日期用DATETIME
而非字符串,避免大字段(TEXT/BLOB
拆分到子表)。 - 分表分库:
- 水平分表:按时间 / 哈希拆分大表(如
order_2023
、order_2024
); - 垂直分表:拆分不常用字段(如
user
拆分为user_base
和user_detail
)。
- 水平分表:按时间 / 哈希拆分大表(如
二、数据库架构
1、说说 MySQL 的基础架构?
MySQL 采用分层架构,主要包括连接层、服务层、和存储引擎层。
下图是 MySQL 的一个简要架构图,从下图你可以很清晰的看到客户端的一条 SQL 语句在 MySQL 内部是如何执行的。
①、连接层主要负责客户端连接的管理,包括验证用户身份、权限校验、连接管理等。可以通过数据库连接池来提升连接的处理效率。
②、服务层是 MySQL 的核心,主要负责查询解析、优化、执行等操作。在这一层,SQL 语句会经过解析、优化器优化,然后转发到存储引擎执行,并返回结果。这一层包含查询解析器、优化器、执行计划生成器、日志模块等。
③、存储引擎层负责数据的实际存储和提取。MySQL 支持多种存储引擎,如 InnoDB、MyISAM、Memory 等。
binlog写入在哪一层?
binlog 在服务层,负责记录 SQL 语句的变化。它记录了所有对数据库进行更改的操作,用于数据恢复、主从复制等。
2、一条 SQL 查询语句在 MySQL 中的完整执行过程
案例 SQL
1 | SELECT name, age FROM employees WHERE department = 'Sales' AND salary > 5000 ORDER BY hire_date DESC LIMIT 10; |
执行过程详解
1. 连接阶段 (Connector)
- 作用:建立客户端与服务器的连接
- 案例过程:
- 应用程序(如Java程序)通过JDBC驱动连接到MySQL
- 验证用户名/密码(如:
jdbc:mysql://localhost:3306/company
) - 检查权限(确认用户有
employees
表的SELECT权限) - 建立连接线程(
show processlist
可查看)
2. 查询缓存 (Query Cache) [MySQL 8.0已移除]
MySQL 5.7中仍存在,但默认禁用
- 作用:缓存SELECT查询结果
- 案例过程:
- 生成查询缓存Key:
department='Sales', salary>5000, ...
- 检查缓存中是否有完全匹配的结果
- 若存在直接返回结果(本案例大概率不会命中,因包含动态条件)
- 生成查询缓存Key:
3. 解析器 (Parser)
- 作用:语法解析,生成解析树
- 案例过程:
- 词法分析:拆分关键词
1
2
3
4
5
6SELECT → 查询命令
name, age → 目标列
FROM employees → 数据源
WHERE → 条件开始
department = 'Sales' → 条件表达式
... - 语法分析:构建语法树
graph TD A[SELECT] --> B[ColumnList] A --> C[FROM] A --> D[WHERE] A --> E[ORDER BY] A --> F[LIMIT] B --> G[name] B --> H[age] C --> I[employees] D --> J[AND] J --> K[department='Sales'] J --> L[salary>5000] E --> M[hire_date DESC] F --> N[10]
- 词法分析:拆分关键词
4. 预处理器 (Preprocessor)
- 作用:语义检查,表/列解析
- 案例过程:
- 检查
employees
表是否存在 - 验证
name, age, department, salary, hire_date
列是否存在 - 解析
*
符号(本案例未使用) - 检查权限(再次确认SELECT权限)
- 检查
5. 优化器 (Optimizer)
作用:生成最优执行计划
案例过程:
统计信息分析:
- 表行数(
show table status like 'employees'
) - 索引分布(
department
索引?salary
索引?)
- 表行数(
成本估算:
- 全表扫描 vs 使用索引
- 假设存在
idx_dept_salary
(department, salary)索引
执行计划生成:
1
2
3
4
5
6
7
8
9EXPLAIN SELECT ...
-- 结果示例:
id: 1
select_type: SIMPLE
table: employees
type: ref
key: idx_dept_salary
rows: 100
Extra: Using where; Using filesort关键决策:
- 使用
idx_dept_salary
索引快速定位’Sales’部门 - 在索引结果中过滤
salary > 5000
- 对结果集进行文件排序(filesort)
- 应用LIMIT 10
- 使用
6. 执行器 (Executor)
- 作用:调用存储引擎执行计划
- 案例过程:
- 打开
employees
表 - 调用存储引擎接口:
1
2
3
4
5
6
7
8// 伪代码
index_read_first(idx_dept_salary, 'Sales')
while (record = index_read_next()) {
if (record.salary > 5000) {
result_set.add(record.name, record.age);
if (result_set.size() >= 10) break;
}
} - 处理排序:
- 收集所有符合条件的行
- 在内存或磁盘进行快速排序(
ORDER BY hire_date DESC
)
- 应用LIMIT:取前10条结果
- 打开
7. 存储引擎 (Storage Engine)
- 作用:实际数据存取(以InnoDB为例)
- 案例过程:
- 索引扫描:
- 使用B+树索引
idx_dept_salary
定位到第一个’Sales’部门记录 - 沿叶子节点链表扫描部门为’Sales’的记录
- 使用B+树索引
- 回表查询:
- 通过主键获取完整行数据(因SELECT包含非索引列
name, age, hire_date
)
- 通过主键获取完整行数据(因SELECT包含非索引列
- 过滤处理:
- 对每条记录检查
salary > 5000
条件
- 对每条记录检查
- 数据返回:
- 将符合条件的
name, age, hire_date
返回给执行器
- 将符合条件的
- 事务支持:
- 保证在READ COMMITTED隔离级别下看到一致的数据视图
- 索引扫描:
8. 结果返回
作用:将最终结果发送给客户端
案例过程:
- 排序后的结果集放入网络缓冲区
- 通过TCP连接逐步发送给客户端
- 客户端(如MySQL命令行)显示结果:
1
2
3
4
5
6+----------+-----+
| name | age |
+----------+-----+
| John Doe | 32 |
| Jane Smith| 28 |
...(共10行) - 清理临时资源(排序内存、游标等)
关键流程总结
sequenceDiagram
participant Client
participant Connector
participant Parser
participant Optimizer
participant Executor
participant InnoDB
Client->>Connector: 发送SQL查询
Connector->>Parser: 权限验证后传递
Parser->>Optimizer: 生成解析树
Optimizer->>Executor: 最优执行计划
Executor->>InnoDB: 调用索引扫描接口
InnoDB->>Executor: 返回数据页
Executor->>InnoDB: 请求回表查询
InnoDB->>Executor: 返回完整行数据
Executor->>Executor: 过滤/排序/LIMIT
Executor->>Client: 返回最终结果集
性能优化关键点
- 索引设计:创建复合索引
(department, salary)
可加速WHERE过滤 - 覆盖索引:若索引包含所有SELECT列(如
(department, salary, name, age, hire_date)
),可避免回表 - 排序优化:添加
hire_date
索引可消除filesort - LIMIT下推:在存储引擎层尽早应用行数限制
- 批量读取:顺序I/O读取多个数据页减少磁盘寻道
💡 实际建议:通过
EXPLAIN
分析执行计划,关注type
列(扫描方式)、Extra
列(排序/临时表)、rows
列(扫描行数)等关键指标进行优化。
三、存储引擎
四、索引
1、索引是什么?有什么好处?
索引类似于书籍的目录,可以减少扫描的数据量,提高查询效率。
如果查询的时候,没有用到索引就会全表扫描,这时候查询的时间复杂度是On
如果用到了索引,那么查询的时候,可以基于二分查找算法,通过索引快速定位到目标数据, mysql 索引的数据结构一般是 b+树,其搜索复杂度为O(logdN),其中 d 表示节点允许的最大子节点个数为 d 个。
2、索引都分类
- 从功能上分
- 主键索引
- 唯一索引
- 普通索引
- 联合索引
- 全文索引
- 空间索引
- 从存储结构上分
- 聚簇索引
- 二级索引(非聚簇索引)
3、索引失效的几种情况
- 查询条件使用函数或者计算
- 不符合最左前缀原则
- 隐式类型转换(类型不匹配)
- like通配符以“%” 开头
- 使用 or 时,两边有没有索引的字段
- 使用is null 或 is not null
- 使用not in
- 使用范围(> ,<)查询
- 使用 != 或者 <>
4、创建索引有哪些注意点?
- 针对于数据量较大,且查询比较频繁的表建立索引。
- 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引。
- 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。
- 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。
- 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间, 避免回表,提高查询效率。
- 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增 删改的效率。
- 如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含 NULL值时,它可以更好地确定哪个索引最有效地用于查询。
5、如果Explain用到的索引不正确的话,有什么办法干预吗?
可以使用 force index,强制走索引。
五、事务
1、MySQL事务的四大特性
- A(Atomicity) 原子性: 要求事务的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务中的操作不能只执行其中一部分。
- C(Consistency) 一致性:执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的。
- I(Isolation) 隔离性:并发执行的事务是彼此隔离的,多个事务之间不相互干扰。
- D(Durability) 持久性:一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
2、MySQL事务如何实现的
graph TD
A[事务ACID特性] --> B[实现机制]
B --> B1[原子性 - Undo Log]
B --> B2[持久性 - Redo Log]
B --> B3[隔离性 - MVCC+锁]
B --> B4[一致性 - 前3者保证]
B1 --> C1[回滚操作]
B2 --> C2[崩溃恢复]
B3 --> C3[多版本并发控制]
B3 --> C4[行锁/间隙锁]
Undo Log(回滚日志):
- 作用:实现事务的原子性
- 存储位置:共享表空间(ibdata1)或独立 Undo 表空间
- 工作原理:
- 更新数据前先记录修改前的值
- 事务回滚时恢复原始数据
- 支持 MVCC 的快照读
Redo Log(重做日志):
- 作用:保证事务的持久性
- 存储形式:循环写的日志文件(ib_logfile0, ib_logfile1)
- 工作原理:
- 采用 WAL(Write-Ahead Logging)机制
- 先写日志后写数据页
- 崩溃恢复时重放日志
MVCC(多版本并发控制)
- 作用:实现隔离性(非锁定读)
- 核心元素:
- DB_TRX_ID:6字节,最近修改的事务ID
- DB_ROLL_PTR:7字节,回滚指针指向Undo Log
- DB_ROW_ID:6字节,隐藏自增ID(无主键时)
锁机制
行级锁类型:
- Record Lock:记录锁(锁定单行)
- Gap Lock:间隙锁(锁定区间)
- Next-Key Lock:记录锁+间隙锁(解决幻读)
锁兼容矩阵:
请求\持有 S(共享) X(排他) S(共享) ✓ ✗ X(排他) ✗ ✗
3、mysql可能出现什么和并发相关问题
1. 脏读(Dirty Read)
定义:一个事务读取了另一个未提交事务修改的数据。
产生原因:事务隔离级别设置为 读未提交(READ UNCOMMITTED),允许读取未提交的数据。
影响:若未提交事务回滚,读取的数据是无效的,可能导致决策错误。
示例:
1
2
3
4
5
6
7
8
9-- 事务 A
BEGIN;
UPDATE users SET balance = 200 WHERE id = 1; -- 未提交
-- 事务 B
SELECT balance FROM users WHERE id = 1; -- 读到 200(脏数据)
-- 事务 A 回滚
ROLLBACK;解决方案:提高隔离级别至 读已提交(READ COMMITTED) 或更高。
2. 不可重复读(Non-Repeatable Read)
定义:同一事务中多次读取同一数据,结果不一致。
产生原因:事务隔离级别为 读已提交(READ COMMITTED),每次查询生成新的快照,允许读取其他事务已提交的修改。
影响:事务内的查询结果不一致,可能导致逻辑错误。
示例:
1
2
3
4
5
6
7
8
9
10
11-- 事务 A
BEGIN;
SELECT balance FROM users WHERE id = 1; -- 读到 100
-- 事务 B
BEGIN;
UPDATE users SET balance = 200 WHERE id = 1;
COMMIT;
-- 事务 A 再次查询
SELECT balance FROM users WHERE id = 1; -- 读到 200(与第一次不同)解决方案:使用 可重复读(REPEATABLE READ) 隔离级别(MySQL 默认),确保事务内快照一致。或使用锁定读
SELECT balance FROM FROM users WHERE id = 1 FOR UPDATE;
。
3. 幻读(Phantom Read)
定义:同一事务中,两次相同条件的查询返回不同行数。
产生原因:其他事务插入或删除数据,且提交后被当前事务读到。
影响:事务内的查询结果不一致,可能导致逻辑错误(如重复插入唯一键)。
示例:
1
2
3
4
5
6
7
8
9
10
11-- 事务 A
BEGIN;
SELECT * FROM users WHERE age > 30; -- 返回 10 条记录
-- 事务 B
BEGIN;
INSERT INTO users (name, age) VALUES ('Alice', 35);
COMMIT;
-- 事务 A 再次查询
SELECT * FROM users WHERE age > 30; -- 返回 11 条记录(幻读)解决方案:
- 使用
串行化(SERIALIZABLE)
隔离级别,但性能较低。 - 使用间隙锁(Next-Key Locking)
SELECT * FROM users WHERE age > 30 FOR UPDATE;
。
- 使用
4、不可重复读和幻读有什么区别?
不可重复读 | 幻读 |
---|---|
同一事务内多次读取同一行数据,结果不一致。 | 同一事务内多次执行相同条件的查询,返回的 行数 不一致。 |
原因:其他事务对该行数据进行了 修改(Update/Delete) 并提交。 | 原因:其他事务在查询范围内 插入(Insert) 或 删除(Delete) 了新数据并提交。 |
关注点:数据的 内容变化。 | 关注点:数据的 行数变化。 |
5、事务的隔离级别有哪些?
隔离级别 | 脏读可能性 | 不可重复读可能性 | 幻读可能性 | 加锁情况 |
---|---|---|---|---|
读未提交(Read Uncommitted) | ✅ | ✅ | ✅ | 无特殊锁 |
读已提交(Read Committed) | ❌ | ✅ | ✅ | 行锁(仅在查询时加锁) |
可重复读(Repeatable Read) | ❌ | ❌ | ✅ | 行锁 + 间隙锁(MySQL 默认) |
串行化(Serializable) | ❌ | ❌ | ❌ | 表锁(强制事务串行执行) |
六、MySQL 锁
七、日志文件
1、讲一下binlog
二进制日志(BINLOG)记录了所有的 DDL(数据定义语言)语句和 DML(数据操纵语言)语句,但不包括数据查询(SELECT、SHOW)语句。
作用:
①. 灾难时的数据恢复。
②. MySQL的主从复制。
MySQL 在完成一条更新操作后,Server 层还会生成一条 binlog,等之后事务提交的时候,会将该事物执行过程中产生的所有 binlog 统一写 入 binlog 文件,binlog 是 MySQL 的 Server 层实现的日志,所有存储引擎都可以使用。
binlog 是追加写,写满一个文件,就创建一个新的文件继续写,不会覆盖以前的日志,保存的是全量的日志,用于备份恢复、主从复制;
2、RedoLog日志是什么?
重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性。
该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log file),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都存到该日志文件中,用于在刷新脏页到磁盘,发生错误时,进行数据恢复使用。
3、Write-ahead logging(WAL)是什么?
Write-ahead logging(WAL):因为在业务操作中,我们操作数据一般都是随机读写磁盘的,而不是顺序读写磁盘。 而redo log在往磁盘文件中写入数据时,由于是日志文件,所以都是顺序写的。顺序写的效率,要远大于随机写。
4、binlog 和 redo log 有什么区别?
binlog 由 MySQL 的 Server 层实现,与存储引擎无关;redo log 由 InnoDB 存储引擎实现。
binlog 会记录整个 SQL 或行变化;redo log 是为了恢复“已提交但未刷盘”的数据,undo log 是为了撤销未提交的事务。
binlog 是追加写入的,文件写满后会新建文件继续写入,不会覆盖历史日志,保存的是全量操作记录;redo log 是循环写入的,空间是固定的,写满后会覆盖旧的日志,仅保存未刷盘的脏页日志,已持久化的数据会被清除。
5、为什么要两阶段提交呢?
事务提交后,redo log 和 binlog 都要持久化到磁盘,但是这两个是独立的逻辑,可能出现半成功的状态,这样就造成两份日志之间的逻辑不一致。
在 MySQL 的 InnoDB 存储引擎中,开启 binlog 的情况下,MySQL 会同时维护 binlog 日志与 InnoDB 的 redo log,为了保证这两个日志的一致性,MySQL 使用了内部 XA 事务,内部 XA 事务由 binlog 作为协调者,存储引擎是参与者。
6、UndoLog日志的作用是什么?
回滚日志,用于记录数据被修改前的信息 , 作用包含两个 : 提供回滚(保证事务的原子性) 和 MVCC(多版本并发控制) ;在事务没提交之前,MySQL 会先记录更新前的数据到 undo log 日志文件里面,当事务回滚时,可以利用 undo log 来进行回滚。
八、面试常问
1、一个慢查询日志显示某SQL执行缓慢,你会如何分析和优化?
一、先明确问题:收集关键信息
首先会获取分析所需的基础信息,避免盲目优化:
- 获取完整 SQL:包括查询类型(SELECT/UPDATE/DELETE)、涉及的表、过滤条件(WHERE)、排序(ORDER BY)、分组(GROUP BY)、关联(JOIN)等细节。
- 了解表结构与索引:通过
show create table
查看表的字段类型、主键、索引;通过show index
确认现有索引是否覆盖查询条件。 - 掌握数据规模与分布:表的总行数、过滤字段的分布(如某状态值占比)、是否有数据倾斜(如热点数据集中)。
- 确认执行环境:数据库版本(优化器行为可能不同)、当前负载(是否有锁竞争、其他慢查询)。
二、核心分析:通过执行计划定位瓶颈
使用EXPLAIN
(或EXPLAIN ANALYZE
)查看 SQL 执行计划,重点关注 4 个关键指标,快速定位问题:
- type 字段(访问类型):
- 若为
ALL
(全表扫描)或index
(全索引扫描),说明未有效利用索引,是主要瓶颈(尤其数据量大时)。 - 目标是优化到
range
(范围扫描)及以上(如ref
、eq_ref
)。
- 若为
- key 字段(实际使用的索引):
- 若
key=NULL
,说明索引未被使用,需分析索引失效原因(如隐式转换、函数操作、反向模糊查询等)。 - 若
key
与预期不符,可能是优化器依赖的统计信息过时,需更新统计信息(analyze table
)。
- 若
- rows 字段(估算扫描行数):
- 若
rows
远大于实际需要的行数,说明索引过滤性差(如用非唯一索引查询高频值),需优化索引粒度。
- 若
- Extra 字段(额外执行信息):
- 出现
Using filesort
(无法用索引排序,需磁盘排序)、Using temporary
(创建临时表存储中间结果)时,性能会急剧下降,必须优化。 - 理想状态是
Using index
(覆盖索引,无需回表)。
- 出现
三、针对性优化:分场景解决
1. 索引问题(最常见)
- 缺少索引:为
WHERE
、JOIN ON
、ORDER BY
、GROUP BY
涉及的字段添加合适索引(如联合索引需遵循 “最左前缀原则”)。
例:SELECT * FROM user WHERE age > 30 ORDER BY create_time
,可添加(age, create_time)
联合索引。 - 索引失效:修正导致索引失效的写法:
- 避免隐式类型转换(如
WHERE phone = '123'
改为WHERE phone = 123
,假设 phone 是 int 类型)。 - 避免对索引字段用函数(如
SUBSTR(name,1,1) = '张'
改为name LIKE '张%'
)。 - 替换反向模糊查询(
LIKE '%张'
)为全文索引或应用层处理。
- 避免隐式类型转换(如
2. SQL 逻辑不合理
- **避免
SELECT *
**:只查询需要的字段,可能触发覆盖索引(Extra: Using index
),减少 IO。 - 优化子查询:复杂子查询(如
IN
子查询)改为JOIN
(优化器对JOIN
支持更好);联表查询最好要以小表驱动大表,并且被驱动表的字段要有索引,当然最好通过冗余字段的设计,避免联表查询。 - 优化大偏移量分页:
LIMIT 100000, 10
改为 “书签分页”(WHERE id > 100000 LIMIT 10
)或延迟关联(先查主键再关联表)。
3. 表结构与数据问题
- 数据量过大:单表超千万行时,用表分区(如按时间
RANGE
分区)或分库分表(Sharding-JDBC)拆分数据。 - 数据倾斜:对高频值字段(如
status=0
占 90%),添加更细粒度索引(如(status, create_time)
),结合时间过滤减少扫描行数。
4. 环境与配置优化
- 调整数据库参数:增大
innodb_buffer_pool_size
(建议为内存的 50%-70%),减少磁盘 IO。 - 减少锁竞争:优化长事务(缩短事务时间),避免读写冲突(如
show processlist
排查锁等待)。 - 缓存热点数据:用 Redis 缓存高频查询结果,减少数据库访问。
2、






- 1. MySQL面试题
- 1.1. 一、MySQL基础
- 1.1.1. 1、数据库三大范式
- 1.1.2. 2、char 和 varchar 的区别是什么?
- 1.1.3. 3、varchar (100)和 varchar (10)的区别是什么?
- 1.1.4. 4、in和exists的区别?
- 1.1.5. 5、怎么存储 emoji?
- 1.1.6. 6、drop、delete 与 truncate 的区别?
- 1.1.7. 7、UNION 与 UNION ALL 的区别?
- 1.1.8. 8、count(1)、count(*) 与 count(列名) 的区别?
- 1.1.9. 9、NULL 和 ‘ ‘ 的区别是什么?
- 1.1.10. 10、mysql 的深度分页如何优化
- 1.1.11. 11、你知道哪些方法来优化 SQL?
- 1.2. 二、数据库架构
- 1.3. 三、存储引擎
- 1.4. 四、索引
- 1.5. 五、事务
- 1.6. 六、MySQL 锁
- 1.7. 七、日志文件
- 1.8. 八、面试常问
- 1.1. 一、MySQL基础