MySQL面试题

此笔记为本人备考面试时整理,内容多源于网络搜集,仅作后续查阅之用,无盈利意图。若有侵权,请联系删除。

参考资料:

https://javaguide.cn/

https://www.xiaolincoding.com/

https://pdai.tech

https://javabetter.cn/

一、MySQL基础

1、数据库三大范式

  • 1NF 要求字段值必须是不可再分的原子值

    反例:用户信息表地址字段存储 “北京市海淀区”,未拆分为,违反 1NF;
    正例:拆分为provincecitydistrict,每个字段独立存储。

  • 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 倍
  1. 适用场景
    • varchar(10):短文本(如验证码、商品编码前几位);
    • varchar(100):较长文本(如用户名、文章标题、地址片段)。

4、in和exists的区别?

IN 运算符

先执行子查询,将结果缓存到临时表,然后检查主查询的值是否在这个临时表中。

exists 运算符

对于主查询的每一行,都会执行一次子查询检查是否存在匹配记录

区别

特性 IN EXISTS
工作原理 检查值是否在结果集中 检查子查询是否返回任何行
执行顺序 先执行子查询,再执行主查询 主查询的每一行都执行子查询
性能特点 子查询结果集小时高效 主查询结果集小时高效
NULL 处理 NULL IN (结果集) 总是返回 UNKNOWN 不受子查询中 NULL 值影响
适用场景 静态值列表或小型结果集 关联子查询或大型结果集
可读性 更直观,易于理解 对初学者稍复杂

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(列名) 的区别?

  1. count(*):
  • count(*)会统计结果集中所有行的数量,包括所有列,不忽略任何行,即使某些列包含NULL值。
  • 它是一个标准的SQL函数,用于统计表中的总行数。
  1. count(1):
  • count(1)中的1是一个常量值,表示对每一行进行计数。
  • 由于常量值永远不为NULL,因此count(1)实际上与count(*)一样计算所有行。
  • 在MySQL中,优化器通常会将count(1)优化为与count(*)相同的执行计划。
  1. 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)。

3. 查询与比较

  • **NULL**:

    • 不能用 = 直接比较,必须用 IS NULLIS NOT NULL

      1
      2
      WHERE 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
2
3
4
5
-- 第1页:初始查询
SELECT * FROM t ORDER BY id LIMIT 10; -- 记最后一条 ID 为 last_id
-- 第2页:基于游标
SELECT * FROM t WHERE id < last_id ORDER BY id DESC LIMIT 10;

  • 优点

    • 时间复杂度 O(1)
    • 无性能衰减
    • 支持高并发
  • 限制

    • 只能顺序翻页,不能跳页,适合 APP 滚动加载。
    • 需有序且唯一的列(如自增ID、时间戳)

方案2:覆盖索引 + 延迟关联

  • 原理:利用索引先获取主键(减少扫描量),再回表取全字段。

    1
    2
    3
    4
    5
    6
    7
    SELECT * 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
    3
    SELECT * FROM t 
    WHERE create_time BETWEEN '2025-01-01' AND '2025-01-31'
    ORDER BY id LIMIT 10;
  • 适用场景

    • 时间范围明确的数据
    • 分区表的分区键查询

11、你知道哪些方法来优化 SQL?

  1. 针对性建索引:为 WHEREJOINGROUP BYORDER BY 后的字段建索引(这些是查询的过滤、关联、排序核心)。
  2. 联合索引遵循 “最左前缀原则”:将区分度高的字段放在前面(如 INDEX (age, name) 可优化 age=30 AND name='张三',但不能优化 name='张三')。
  3. 避免索引失效:
    • 索引列不参与函数 / 计算(如 SUBSTR(name,1,1)='张' 会失效,改用 name LIKE '张%');
    • 避免 OR 连接非索引字段、字符串不加引号(如 phone=13800138000phone 是字符串会隐式转换);
    • 控制索引数量(单表建议不超过 5-8 个,避免影响增删改性能)。
  4. 避免全表扫描:查询必须带有效过滤条件,分页用 LIMIT 并配合索引(如 LIMIT 10000,10ORDER BY 字段有索引)。
  5. **不用 SELECT ***:只查需要的字段,可能触发 “覆盖索引”(索引包含所有查询字段,无需回表)。
  6. **子查询改 JOIN**:子查询(尤其相关子查询)可能重复执行,JOIN 更高效(如 IN (子查询)JOIN 关联)。
  7. 优化 JOIN 逻辑:
    • 小表驱动大表(减少外层循环次数);
    • 连接字段必须建索引(如 a.id = b.a_idb.a_id 需索引)。
  8. 排序 / 分组依赖索引ORDER BY/GROUP BY 的字段有索引时,可避免额外排序(如 Using filesort)。
  9. 合理选择数据类型:用更小的类型(如 INT 代替 BIGINT)、日期用 DATETIME 而非字符串,避免大字段(TEXT/BLOB 拆分到子表)。
  10. 分表分库:
    • 水平分表:按时间 / 哈希拆分大表(如 order_2023order_2024);
    • 垂直分表:拆分不常用字段(如 user 拆分为 user_baseuser_detail)。

二、数据库架构

1、说说 MySQL 的基础架构?

MySQL 采用分层架构,主要包括连接层、服务层、和存储引擎层。

下图是 MySQL 的一个简要架构图,从下图你可以很清晰的看到客户端的一条 SQL 语句在 MySQL 内部是如何执行的。

image-20250625173507865

①、连接层主要负责客户端连接的管理,包括验证用户身份、权限校验、连接管理等。可以通过数据库连接池来提升连接的处理效率。

②、服务层是 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, ...
    • 检查缓存中是否有完全匹配的结果
    • 若存在直接返回结果(本案例大概率不会命中,因包含动态条件)
3. 解析器 (Parser)
  • 作用:语法解析,生成解析树
  • 案例过程
    • 词法分析:拆分关键词
      1
      2
      3
      4
      5
      6
      SELECT → 查询命令
      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
      9
      EXPLAIN SELECT ...
      -- 结果示例:
      id: 1
      select_type: SIMPLE
      table: employees
      type: ref
      key: idx_dept_salary
      rows: 100
      Extra: Using where; Using filesort
    • 关键决策

      1. 使用idx_dept_salary索引快速定位’Sales’部门
      2. 在索引结果中过滤salary > 5000
      3. 对结果集进行文件排序(filesort)
      4. 应用LIMIT 10
6. 执行器 (Executor)
  • 作用:调用存储引擎执行计划
  • 案例过程
    1. 打开employees
    2. 调用存储引擎接口:
      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;
      }
      }
    3. 处理排序:
      • 收集所有符合条件的行
      • 在内存或磁盘进行快速排序(ORDER BY hire_date DESC
    4. 应用LIMIT:取前10条结果
7. 存储引擎 (Storage Engine)
  • 作用:实际数据存取(以InnoDB为例)
  • 案例过程
    • 索引扫描
      • 使用B+树索引idx_dept_salary定位到第一个’Sales’部门记录
      • 沿叶子节点链表扫描部门为’Sales’的记录
    • 回表查询
      • 通过主键获取完整行数据(因SELECT包含非索引列name, age, hire_date
    • 过滤处理
      • 对每条记录检查salary > 5000条件
    • 数据返回
      • 将符合条件的name, age, hire_date返回给执行器
    • 事务支持
      • 保证在READ COMMITTED隔离级别下看到一致的数据视图
8. 结果返回
  • 作用:将最终结果发送给客户端

  • 案例过程

    1. 排序后的结果集放入网络缓冲区
    2. 通过TCP连接逐步发送给客户端
    3. 客户端(如MySQL命令行)显示结果:
      1
      2
      3
      4
      5
      6
      +----------+-----+
      | name | age |
      +----------+-----+
      | John Doe | 32 |
      | Jane Smith| 28 |
      ...(共10行)
    4. 清理临时资源(排序内存、游标等)

关键流程总结

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: 返回最终结果集

性能优化关键点

  1. 索引设计:创建复合索引(department, salary)可加速WHERE过滤
  2. 覆盖索引:若索引包含所有SELECT列(如(department, salary, name, age, hire_date)),可避免回表
  3. 排序优化:添加hire_date索引可消除filesort
  4. LIMIT下推:在存储引擎层尽早应用行数限制
  5. 批量读取:顺序I/O读取多个数据页减少磁盘寻道

💡 实际建议:通过EXPLAIN分析执行计划,关注type列(扫描方式)、Extra列(排序/临时表)、rows列(扫描行数)等关键指标进行优化。

三、存储引擎

1、讲一讲mysql的引擎吧,你有什么了解?

  • InnoDB:
    • InnoDB是MySQL的默认存储引擎。
    • 具有ACID事务支持、行级锁、外键约束等特性。
    • 它适用于高并发的读写操作,支持较好的数据完整性和并发控制。
  • MyISAM:
    • MyISAM是MySQL的另一种常见的存储引擎。
    • 具有较低的存储空间和内存消耗,适用于大量读操作的场景。
    • MyISAM不支持事务、行级锁和外键约束,因此在并发写入和数据完整性方面有一定的限制。
  • Memory:
    • Memory引擎将数据存储在内存中,适用于对性能要求较高的读操作,但是在服务器重启或崩溃时数据会丢失。
    • 不支持事务、行级锁和外键约束。

四、索引

1、索引是什么?有什么好处?

  • 索引类似于书籍的目录,可以减少扫描的数据量,提高查询效率。

  • 如果查询的时候,没有用到索引就会全表扫描,这时候查询的时间复杂度是On

  • 如果用到了索引,那么查询的时候,可以基于二分查找算法,通过索引快速定位到目标数据, mysql 索引的数据结构一般是 b+树,其搜索复杂度为O(logdN),其中 d 表示节点允许的最大子节点个数为 d 个。

2、索引都分类

  • 从功能上分
    • 主键索引
    • 唯一索引
    • 普通索引
    • 联合索引
    • 全文索引
    • 空间索引
  • 从存储结构上分
    • 聚簇索引
    • 二级索引(非聚簇索引)

3、索引失效的几种情况

  • 查询条件使用函数或者计算
  • 不符合最左前缀原则
  • 隐式类型转换(类型不匹配)
  • like通配符以“%” 开头
  • 使用 or 时,两边有没有索引的字段
  • 使用is null 或 is not null
  • 使用not in
  • 使用范围(> ,<)查询
  • 使用 != 或者 <>

4、创建索引有哪些注意点?

  1. 针对于数据量较大,且查询比较频繁的表建立索引。
  2. 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引。
  3. 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。
  4. 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。
  5. 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间, 避免回表,提高查询效率。
  6. 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增 删改的效率。
  7. 如果索引列不能存储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执行缓慢,你会如何分析和优化?

一、先明确问题:收集关键信息

首先会获取分析所需的基础信息,避免盲目优化:

  1. 获取完整 SQL:包括查询类型(SELECT/UPDATE/DELETE)、涉及的表、过滤条件(WHERE)、排序(ORDER BY)、分组(GROUP BY)、关联(JOIN)等细节。
  2. 了解表结构与索引:通过show create table查看表的字段类型、主键、索引;通过show index确认现有索引是否覆盖查询条件。
  3. 掌握数据规模与分布:表的总行数、过滤字段的分布(如某状态值占比)、是否有数据倾斜(如热点数据集中)。
  4. 确认执行环境:数据库版本(优化器行为可能不同)、当前负载(是否有锁竞争、其他慢查询)。

二、核心分析:通过执行计划定位瓶颈

使用EXPLAIN(或EXPLAIN ANALYZE)查看 SQL 执行计划,重点关注 4 个关键指标,快速定位问题:

  1. type 字段(访问类型)
    • 若为ALL(全表扫描)或index(全索引扫描),说明未有效利用索引,是主要瓶颈(尤其数据量大时)。
    • 目标是优化到range(范围扫描)及以上(如refeq_ref)。
  2. key 字段(实际使用的索引)
    • key=NULL,说明索引未被使用,需分析索引失效原因(如隐式转换、函数操作、反向模糊查询等)。
    • key与预期不符,可能是优化器依赖的统计信息过时,需更新统计信息(analyze table)。
  3. rows 字段(估算扫描行数)
    • rows远大于实际需要的行数,说明索引过滤性差(如用非唯一索引查询高频值),需优化索引粒度。
  4. Extra 字段(额外执行信息)
    • 出现Using filesort(无法用索引排序,需磁盘排序)、Using temporary(创建临时表存储中间结果)时,性能会急剧下降,必须优化。
    • 理想状态是Using index(覆盖索引,无需回表)。

三、针对性优化:分场景解决

1. 索引问题(最常见)
  • 缺少索引:为WHEREJOIN ONORDER BYGROUP 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、