Java面试-06-数据库MySql

MySQL数据库面试题(完整版)

目录

  • [1. 三大范式](#1. 三大范式)
  • [2. 数据存储类型](#2. 数据存储类型)
    • [2.1 varchar与char的区别](#2.1 varchar与char的区别)
    • [2.2 blob和text有什么区别?](#2.2 blob和text有什么区别?)
    • [2.3 DATETIME和TIMESTAMP的异同](#2.3 DATETIME和TIMESTAMP的异同)
    • [2.4 IN和EXISTS区别](#2.4 IN和EXISTS区别)
  • [3. Select语句完整的执行顺序](#3. Select语句完整的执行顺序)
  • [4. MySQL事务](#4. MySQL事务)
    • [4.1 事务的基本要素ACID](#4.1 事务的基本要素ACID)
    • [4.2 ACID靠什么保证](#4.2 ACID靠什么保证)
    • [4.3 事务的并发问题](#4.3 事务的并发问题)
    • [4.4 MySQL事务隔离级别](#4.4 MySQL事务隔离级别)
    • [4.5 MVCC](#4.5 MVCC)
      • [4.5.1 MVCC是什么](#4.5.1 MVCC是什么)
      • [4.5.2 MVCC组成](#4.5.2 MVCC组成)
      • [4.5.3 MVCC工作原理](#4.5.3 MVCC工作原理)
      • [4.5.4 MVCC解决什么问题](#4.5.4 MVCC解决什么问题)
  • [5. MySQL索引](#5. MySQL索引)
    • [5.1 索引的定义、类型及使用](#5.1 索引的定义、类型及使用)
    • [5.2 创建索引的注意点](#5.2 创建索引的注意点)
    • [5.3 索引失效的情况](#5.3 索引失效的情况)
    • [5.4 建立索引的时机](#5.4 建立索引的时机)
    • [5.5 MySQL索引数据结构](#5.5 MySQL索引数据结构)
      • [5.5.1 聚簇索引和非聚簇索引](#5.5.1 聚簇索引和非聚簇索引)
    • [5.6 B树和B+树的区别](#5.6 B树和B+树的区别)
    • [5.7 为什么MySQL索引常用B+树](#5.7 为什么MySQL索引常用B+树)
    • [5.8 索引数据结构选择依据](#5.8 索引数据结构选择依据)
    • [5.9 MyISAM和InnoDB的区别](#5.9 MyISAM和InnoDB的区别)
    • [5.10 最左前缀原则/最左匹配原则](#5.10 最左前缀原则/最左匹配原则)
  • [6. MySQL锁](#6. MySQL锁)
    • [6.1 乐观锁和悲观锁](#6.1 乐观锁和悲观锁)
    • [6.2 死锁问题及排查](#6.2 死锁问题及排查)
    • [6.3 死锁解决办法](#6.3 死锁解决办法)
  • [7. MySQL日志](#7. MySQL日志)
  • [8. MySQL高可用/高性能](#8. MySQL高可用/高性能)
    • [8.1 MySQL高可用方案](#8.1 MySQL高可用方案)
    • [8.2 主从同步延迟处理](#8.2 主从同步延迟处理)
  • [9. SQL优化](#9. SQL优化)
    • [9.1 慢SQL定位与排查](#9.1 慢SQL定位与排查)
    • [9.2 MySQL优化原则](#9.2 MySQL优化原则)
  • [10. 实际工作问题](#10. 实际工作问题)
    • [10.1 百万级数据删除](#10.1 百万级数据删除)
    • [10.2 大表添加字段](#10.2 大表添加字段)
    • [10.3 MySQL CPU飙升处理](#10.3 MySQL CPU飙升处理)
  • [11. 常见面试SQL](#11. 常见面试SQL)

1. 三大范式

  • 第一范式:数据表中的每一列(每个字段)都不可以再拆分。
  • 第二范式:在第一范式的基础上,非主键列完全依赖于主键,而不能依赖于主键的一部分。例如订单表里存储商品信息时,需将商品ID和订单ID作为联合主键。
  • 第三范式:在满足第二范式的基础上,表中的非主键只依赖于主键,而不依赖于其他非主键。例如订单表不能存储用户姓名、地址等用户信息。

2. 数据存储类型

2.1 varchar与char的区别

类型 长度 特性 存储方式 性能特点 空间利用 最大字符数
char 定长 插入数据长度小于设定长度时用空格填充 - 存取速度快,比varchar快约50% 可能占多余空间,空间换时间 255
varchar 可变长 - 按插入数据实际长度存储 存取速度慢 不占多余空间,时间换空间 65532

2.2 blob和text有什么区别?

对比维度 BLOB TEXT
用途 存储二进制数据(图片、音频、视频二进制编码) 存储大字符串数据(文章内容、长描述)
字符集 无字符集,按字节存储,无编码问题 有字符集,需进行字符编码处理
排序/比较依据 基于二进制字节值 根据字符集校对规则

2.3 DATETIME和TIMESTAMP的异同

对比维度 DATETIME TIMESTAMP
表现格式 YYYY-MM-DD HH:MM:SS YYYY-MM-DD HH:MM:SS
数据组成 包含日期+时间 包含日期+时间
微秒支持 支持秒后6位小数秒 支持秒后6位小数秒
存储范围 1000-01-01 00:00:00 至 9999-12-31 23:59:59 1970-01-01 00:00:01 UTC 至 2038-01-19 03:14:07 UTC
存储空间 8字节 4字节(无微秒)/5-8字节(有微秒)
时区影响 不受时区影响,存储读取值不变 受时区影响,存储转UTC,读取转当前时区
默认值 无默认值 默认当前时间(CURRENT_TIMESTAMP)

2.4 IN和EXISTS区别

对比项 IN EXISTS
实现方式 外表和内表作hash连接 外表loop循环,每次循环查询内表
效率比较 两表相当:差别不大;子查询表小:用IN 两表相当:差别不大;子查询表大:用EXISTS
NOT IN/NOT EXISTS NOT IN全表扫描,不使用索引 NOT EXISTS能用到索引,通常比NOT IN快

3. Select语句完整的执行顺序

SELECT语句完整执行顺序:

FROM -> JOIN -> WHERE -> GROUP BY -> HAVING -> SELECT -> DISTINCT -> ORDER BY -> LIMIT/OFFSET

分步说明:

  1. FROM:确定查询数据来源表
  2. JOIN:多表时按条件连接表
  3. WHERE:筛选符合条件的行
  4. GROUP BY:按指定列分组
  5. HAVING:筛选分组后的结果
  6. SELECT:指定返回的列
  7. DISTINCT:去除重复行
  8. ORDER BY:对结果集排序
  9. LIMIT/OFFSET:限制返回行数和偏移量

4. MySQL事务

4.1 事务的基本要素ACID

  • 原子性(Atomicity):事务操作要么全部成功,要么全部失败回滚
  • 一致性(Consistency):事务执行前后,数据库数据保持一致状态
  • 隔离性(Isolation):多个事务并发执行时,互不干扰
  • 持久性(Durability):事务提交后,数据修改永久生效

4.2 ACID靠什么保证

  • A(原子性):undo log日志,记录回滚信息,事务回滚时撤销已执行的SQL
  • C(一致性):由其他三大特性+业务代码保证
  • I(隔离性):锁机制(共享锁、排他锁)+ MVCC多版本并发控制
  • D(持久性):内存+redo log,修改数据时同时记录到内存和redo log,宕机可恢复

4.3 事务的并发问题

问题 描述 解决方式
脏读 读取到另一个未提交事务修改的数据 1. 隔离级别设为读已提交+;2. 读数据加共享锁;3. MVCC读取已提交版本
不可重复读 同一事务两次读取同一数据结果不一致 1. 隔离级别提升到可重复读;2. MVCC保证事务内数据一致性;3. 共享锁至事务结束
幻读 同一条件查询,结果集因其他事务增删记录而变化 1. 隔离级别设为串行化;2. InnoDB可重复读级别用间隙锁;3. 应用层乐观锁
丢失更新 两个事务同时更新,一个覆盖另一个结果 1. 更新前加排他锁;2. 乐观锁(版本号/时间戳)检查后更新

4.4 MySQL事务隔离级别

隔离级别 含义 解决的并发问题 仍存在的问题
读未提交 可读取未提交事务的数据 脏读、不可重复读、幻读
读已提交 只能读取已提交事务的数据 脏读 不可重复读、幻读
可重复读 同一事务多次读取数据结果一致 脏读、不可重复读 幻读(InnoDB间隙锁缓解)
串行化 事务依次顺序执行 脏读、不可重复读、幻读 性能差,锁竞争多

4.5 MVCC

4.5.1 MVCC是什么

MVCC(多版本并发控制)是数据库的版本控制机制,通过维护数据多个版本,支持不同事务并发执行,解决脏读、幻读、不可重复读问题。

4.5.2 MVCC组成
  • 数据版本:新版本、旧版本
  • 事务ID:每个事务唯一ID
  • 回滚段:存储数据旧版本
4.5.3 MVCC工作原理
  • 读未提交:MVCC无作用
  • 读已提交:事务开始创建一致性视图,查询取最新已提交版本,避免脏读,但存在不可重复读/幻读
  • 可重复读:事务开始创建视图且全程不变,解决脏读、不可重复读;幻读通过MVCC+间隙锁缓解
  • 串行化:MVCC无作用
4.5.4 MVCC解决什么问题
  • 解决脏读、不可重复读、幻读
  • 读写可同时进行,提高并发量,减少锁竞争
  • 读不加锁,读写不冲突,保证事务隔离性

5. MySQL索引

5.1 索引的定义、类型及使用

定义 :索引是提高查询速度的数据结构

类型

  1. 主键索引:唯一,非空,每个表仅一个
  2. 唯一索引:值唯一,可含NULL,表可多个
  3. 普通索引:无限制,基础索引类型
  4. 全文索引:用于文本数据全文搜索
  5. 组合索引:多列组合,遵循最左前缀原则

5.2 创建索引的注意点

  1. 建在查询频繁的字段上
  2. 索引数量适量(占用空间,更新需维护)
  3. 频繁更新的值不做主键/索引
  4. 小表不建索引
  5. 离散度低的字段(如性别)不建索引

5.3 索引失效的情况

  1. 查询条件含or、like、内置函数、运算(+/-/*/)、!=/<>=、not in、is null/is not null
  2. 字符串字段查询未加引号(隐式类型转换)
  3. 联合索引不遵循最左前缀原则
  4. 左右连接时关联字段编码不一致

5.4 建立索引的时机

  1. 表数据量大,查询慢
  2. 多表关联的字段
  3. GROUP BY/ORDER BY的字段

5.5 MySQL索引数据结构

  • B树
  • B+树(InnoDB/MyISAM默认)
  • 哈希(Hash)(Memory引擎)
5.5.1 聚簇索引和非聚簇索引
  • 聚簇索引(InnoDB):B+树叶子节点包含完整数据行
  • 非聚簇索引(MyISAM):B+树叶子节点存储索引键值+主键值,查询需回表

5.6 B树和B+树的区别

  • B树:平衡多路搜索树,键值分布在整棵树,非叶子节点可能找到目标
  • B+树:B树变形,数据仅存于叶子节点,非叶子节点仅索引;叶子节点通过指针形成有序链表

5.7 为什么MySQL索引常用B+树

  1. 磁盘I/O少:非叶子节点仅存索引,节点存储更多索引,树高度低
  2. 范围查询优:叶子节点链表相连,便于范围查询
  3. 查询稳定:每次查询都到叶子节点,效率稳定
  4. 红黑树劣势:数据量大时树高,磁盘I/O多,且有变色旋转开销

5.8 索引数据结构选择依据

  1. 精确查找为主:哈希索引(存储引擎支持时)
  2. 范围查询/排序多:B+树索引
  3. 通用场景:B+树(MySQL默认)

5.9 MyISAM和InnoDB的区别

对比维度 MyISAM InnoDB
事务支持 不支持 支持ACID
外键支持 不支持 支持
索引存储 索引与数据分离,索引指向物理位置 聚簇索引存数据,辅助索引指向主键
锁机制 表级锁,并发写差 行级锁+表级锁,并发好
崩溃恢复 恢复困难,易丢数据 日志+崩溃恢复机制,自动恢复
存储统计 保存总行数,查询快 不精确,需实时计算
适用场景 读多写少,无事务/外键需求 写多,需数据完整性和高并发

5.10 最左前缀原则/最左匹配原则

InnoDB联合索引中,需匹配前一个/左边的值才能匹配下一个。

如创建组合索引(a1,a2,a3),等价于创建:

  • (a1)
  • (a1,a2)
  • (a1,a2,a3)

6. MySQL锁

6.1 乐观锁和悲观锁

悲观锁
  • 认为数据随时会被修改,事务拿到锁后,其他事务无法修改,只能等待
  • 数据库行锁、表锁、读锁、写锁均为悲观锁
乐观锁
  • 认为数据变动不频繁,通过版本号(version)或时间戳(timestamp)实现
  • 版本号为最常用方式

6.2 死锁问题及排查

行锁争用导致的死锁

原因 :事务持有锁时间过长,行锁争用激烈

解决思路:减少事务持有锁时间,移出不必要操作,缩短执行时间

示例

sql 复制代码
-- 事务1
START TRANSACTION;
UPDATE users SET age = 30 WHERE id = 1; -- 对id=1加排他锁

-- 事务2
START TRANSACTION;
SELECT * FROM users WHERE id = 2 FOR UPDATE; -- 对id=2加排他锁
UPDATE users SET age = 25 WHERE id = 1; -- 等待事务1释放锁

-- 事务1
UPDATE users SET name = 'John' WHERE id = 2; -- 等待事务2释放锁,形成死锁
事务顺序不一致导致的死锁

原因 :不同事务操作表的顺序不一致

解决思路:统一事务操作表的顺序

示例

sql 复制代码
-- 事务1
START TRANSACTION;
UPDATE table1 SET name1 = 'A' WHERE id1 = 1;
UPDATE table2 SET name2 = 'X' WHERE id2 = 1;

-- 事务2
START TRANSACTION;
UPDATE table2 SET name2 = 'Y' WHERE id2 = 1;
UPDATE table1 SET name1 = 'B' WHERE id1 = 1; -- 顺序相反,易死锁
死锁排查步骤
  1. 查看死锁日志:show engine innodb status;
  2. 找出死锁SQL
  3. 分析SQL加锁情况
  4. 模拟死锁场景
  5. 分析死锁日志
  6. 得出解决方案

6.3 死锁解决办法

  1. 统一操作顺序:所有事务对表的操作顺序一致(如先操作questions→answers→feedback)
  2. 拆分事务:大事务拆分为小事务,减少锁持有时间
  3. 设置超时时间 :通过innodb_lock_wait_timeout参数设置锁等待阈值,超时自动回滚

7. MySQL日志

  1. 错误日志(error log):记录启动、运行、关闭过程,定位MySQL问题
  2. 慢查询日志(slow query log) :记录执行时间超过long_query_time的SQL,用于优化
  3. 一般查询日志(general log):记录所有数据库请求,无论是否执行成功
  4. 二进制日志(bin log):记录DDL/DML语句(不含select/show),事件形式存储
  5. InnoDB特有日志
    • 重做日志(redo log):记录事务日志,保证持久性
    • 回滚日志(undo log):记录数据回滚信息,保证原子性

8. MySQL高可用/高性能

8.1 MySQL高可用方案

读写分离

原理:将读写操作分散到不同节点

  • 搭建主从集群(一主一从/一主多从)
  • 主机:负责读写操作
  • 从机:仅负责读操作
  • 主机通过复制同步数据到从机
  • 业务端:写操作发主机,读操作发从机

实现方式

  • 程序代码封装:抽象数据访问层,实现读写分离和连接管理
分库分表
类型 说明
垂直分库 按业务归属,将不同表拆分到不同库
水平分库 按字段策略(hash/range),将一个库数据拆分到多个库
垂直分表 按字段活跃性,将字段拆到主表/扩展表
水平分表 按字段策略(hash/range),将一个表数据拆分到多个表

水平分表路由方式

  1. 范围路由:按有序列(整形/时间戳)分段
  2. Hash路由:字段Hash运算后分散

常用中间件

  • sharding-jdbc
  • Mycat

分库分表问题

  1. 事务问题:需分布式事务
  2. 跨库JOIN:业务代码中关联查询
主从复制过程
  1. master写入数据,更新binlog
  2. master创建dump线程向slave推送binlog
  3. slave创建IO线程接收binlog,写入relay log
  4. slave创建SQL线程读取relay log并执行,完成同步
  5. slave记录自身binlog

8.2 主从同步延迟处理

原因:从库仅一个线程读取binlog,主库大并发更新导致SQL积压

解决办法

  1. 写操作后的读操作指定主库
  2. 读从机失败后重试主机
  3. 关键业务读写走主机,非关键业务读写分离

9. SQL优化

9.1 慢SQL定位与排查

发现慢SQL
  1. 开启慢查询日志:

    ini 复制代码
    # my.cnf/my.ini配置
    slow_query_log = 1
    slow_query_log_file = /var/log/mysql/mysql-slow.log
    long_query_time = 2 # 阈值2秒

    重启MySQL生效

  2. SHOW PROCESSLIST;:查看Time列,值大的为慢SQL

分析慢SQL
  1. EXPLAIN分析执行计划:关注type(ALL全表扫描差)、possible_keys、key

  2. 索引检查:验证过滤/排序/连接字段是否有索引,示例:

    sql 复制代码
    CREATE INDEX idx_age ON users(age);
  3. 锁等待查看:查询information_schema.innodb_trx/innodb_locks

  4. SQL优化:减少复杂子查询/连接,添加过滤条件

工具辅助
  • pt-query-digest分析慢查询日志:

    bash 复制代码
    pt-query-digest /var/log/mysql/mysql-slow.log

9.2 MySQL优化原则

  1. 分析表,适当建立索引
  2. 单表数据不超200W,适时分表;用explain分析慢查询
  3. FROM子句后表为基础表,选记录少的表
  4. 尽量缩小子查询结果

10. 实际工作问题

10.1 百万级数据删除

  1. 删除索引
  2. 删除无用数据
  3. 重新创建索引

10.2 大表添加字段

方案1:中间表转换
  1. 创建临时表,复制旧表结构
  2. 添加新字段
  3. 复制旧表数据到临时表
  4. 删除旧表,临时表重命名为旧表名
  5. 缺点:可能丢失部分数据
方案2:主从切换
  1. 先在从库添加字段
  2. 主从切换
  3. 其他节点添加字段

10.3 MySQL CPU飙升处理

排查步骤
  1. top命令确认是否mysqld导致
  2. show processlist查看消耗资源的SQL
  3. 分析SQL执行计划、索引、数据量
处理方式
  1. kill消耗高的线程,观察CPU是否下降
  2. 调整(加索引、改SQL、调内存参数)
  3. 重新执行SQL

11. 常见面试SQL

例1:查询每门课都大于80分的学生姓名

数据表

name kecheng fenshu
张三 语文 81
张三 数学 75
李四 语文 76
李四 数学 90
王五 语文 81
王五 数学 100
王五 英语 90

解法1

sql 复制代码
SELECT DISTINCT name 
FROM table 
WHERE name NOT IN (SELECT DISTINCT name FROM table WHERE fenshu <= 80);

解法2

sql 复制代码
SELECT name 
FROM table 
GROUP BY name 
HAVING MIN(fenshu) > 80;

例2:删除学生冗余信息

数据表

自动编号 学号 姓名 课程编号 课程名称 分数
1 2005001 张三 0001 数学 69
2 2005002 李四 0001 数学 89
3 2005001 张三 0001 数学 61

SQL

sql 复制代码
DELETE tablename 
WHERE 自动编号 NOT IN (
    SELECT MIN(自动编号) 
    FROM tablename 
    GROUP BY 学号, 姓名, 课程编号, 课程名称, 分数
);

例3:查询球队所有比赛组合

数据表:team(name字段:a,b,c,d)

SQL

sql 复制代码
SELECT a.name, b.name 
FROM team a, team b 
WHERE a.name < b.name;

例4:行转列查询

原表

year month amount
1991 1 1.1
1991 2 1.2
1991 3 1.3
1991 4 1.4
1992 1 2.1
1992 2 2.2
1992 3 2.3
1992 4 2.4

目标结果

year m1 m2 m3 m4
1991 1.1 1.2 1.3 1.4
1992 2.1 2.2 2.3 2.4

SQL

sql 复制代码
SELECT year,
(SELECT amount FROM aaa m WHERE month=1 AND m.year=aaa.year) AS m1,
(SELECT amount FROM aaa m WHERE month=2 AND m.year=aaa.year) AS m2,
(SELECT amount FROM aaa m WHERE month=3 AND m.year=aaa.year) AS m3,
(SELECT amount FROM aaa m WHERE month=4 AND m.year=aaa.year) AS m4
FROM aaa 
GROUP BY year;

例5:复制表结构(源表a,新表b)

MySQL

sql 复制代码
CREATE TABLE b AS SELECT * FROM a WHERE 1=2;

SQL Server

sql 复制代码
SELECT * INTO b FROM a WHERE 1<>1; -- 1=1复制结构+数据

例6:添加及格标记

原表

courseid coursename score
1 java 70
2 oracle 90
3 xml 40
4 jsp 30
5 servlet 80

目标结果

courseid coursename score mark
1 java 70 pass
2 oracle 90 pass
3 xml 40 fail
4 jsp 30 fail
5 servlet 80 pass

SQL

sql 复制代码
SELECT courseid, coursename, score, 
IF(score >= 60, "pass", "fail") AS mark 
FROM course;

例7:查询购入2种以上商品的购物人

数据表

购物人 商品名称 数量
A 2
B 4
C 1
A 2
B 5

SQL

sql 复制代码
SELECT * FROM 购物信息 
WHERE 购物人 IN (
    SELECT 购物人 FROM 购物信息 
    GROUP BY 购物人 
    HAVING COUNT(*) >= 2
);

例8:统计每日胜负次数

数据表

date result
2005-05-09 win
2005-05-09 lose
2005-05-09 lose
2005-05-09 lose
2005-05-10 win
2005-05-10 lose
2005-05-10 lose

目标结果

date win lose
2005-05-09 1 3
2005-05-10 1 2

解法1

sql 复制代码
SELECT date,
SUM(CASE WHEN result = "win" THEN 1 ELSE 0 END) AS "win",
SUM(CASE WHEN result = "lose" THEN 1 ELSE 0 END) AS "lose"
FROM info 
GROUP BY date;

解法2

sql 复制代码
SELECT a.date, a.result AS win, b.result AS lose
FROM (
    SELECT date, COUNT(result) AS result 
    FROM info 
    WHERE result = "win" 
    GROUP BY date
) AS a
JOIN (
    SELECT date, COUNT(result) AS result 
    FROM info 
    WHERE result = "lose" 
    GROUP BY date
) AS b
ON a.date = b.date;

例9:联合索引生效判断

联合索引:(a,b,c)

  1. WHERE a=1 AND b=1 AND c=1 → 生效

  2. WHERE a=1 AND c=1 → 生效(a匹配最左前缀)

  3. WHERE b=1 AND c=1 → 失效(无a)

  4. WHERE b=1 AND a=1 AND c=1 → 生效(顺序不影响)

    总结

    1. 文档完整整合了所有MySQL面试题内容,修正了原始格式错误(如表格排版、代码缩进、标点符号),补充了缺失的逻辑内容;
    2. 按照目录层级结构化梳理,每个知识点独立成节,关键内容用表格/代码块/列表呈现,可读性大幅提升;
    3. 保留了所有核心考点(三大范式、事务、索引、锁、优化、面试SQL等),并补充了完整的SQL示例和配置示例,可直接用于复习和面试准备。