2025年 Java 面试八股文(20w字)_java面试八股文-优快云博客
四、MySQL
1.Select 语句完整的执行顺序
SQL Select 语句完整的执行顺序:
(1)from 子句组装来自不同数据源的数据;
(2)where 子句基于指定的条件对记录行进行筛选;
(3)group by 子句将数据划分为多个分组;
(4)使用聚集函数进行计算;
(5)使用 having 子句筛选分组;
(6)计算所有的表达式;
(7)select 的字段;
(8)使用order by 对结果集进行排序。
2.MySQL事务
事务的基本要素(ACID)
1.原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位
2.一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。
3.隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。
4.持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。
MySQL事务隔离级别:
事务的并发问题
脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致
幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
如何解决脏读、幻读、不可重复读
脏读: 隔离级别为 读提交、可重复读、串行化可以解决脏读
不可重复读:隔离级别为可重复读、串行化可以解决不可重复读
幻读:隔离级别为串行化可以解决幻读、通过MVCC + 区间锁可以解决幻读
小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
3.MyISAM和InnoDB的区别
索引结构
-
InnoDB:主键索引为聚集索引,数据直接存储在索引的叶子节点,主键查询极快。
-
MyISAM:所有索引均为非聚集索引,索引与数据分离,需通过地址二次查找。
适用场景
场景 | 推荐引擎 | 理由 |
---|---|---|
高并发写入、事务需求 | InnoDB | 行级锁、事务支持 |
复杂查询(OLTP) | InnoDB | 缓冲池优化、索引效率高 |
只读或读多写少 | MyISAM | 简单结构、全表扫描快 |
日志记录、数据仓库 | MyISAM | 无事务需求,写入后很少修改 |
总结
-
InnoDB 是现代应用的首选,尤其在需要事务、高并发或数据可靠性时。
-
MyISAM 适用于简单查询、只读场景,但已逐渐被 InnoDB 取代。
-
MySQL 5.5+ 默认使用 InnoDB,建议优先选择。
4.悲观锁和乐观锁的怎么实现
悲观锁和乐观锁是两种常见的并发控制机制,用于解决多线程或分布式环境下数据竞争问题。它们的核心区别在于对并发冲突的预期以及实现方式。
悲观锁:
核心思想:默认并发操作会频繁发生冲突,因此在访问数据时直接加锁,确保独占操作。
数据库层面
1.行级锁(InnoDB)
使用 SELECT ... FOR UPDATE
显式锁定目标行,其他事务需等待锁释放
特点:
-
锁在事务提交或回滚后释放。
-
需确保事务尽量短,避免长事务导致性能问题。
2.表级锁(MyISAM)
MyISAM 引擎默认在写操作时自动加表级锁
适用场景:
-
写操作频繁,冲突概率高(如账户扣款)。
-
需要强一致性保证。
乐观锁:
核心思想:默认并发冲突较少,只在提交数据时检查是否发生冲突,若冲突则重试或放弃。
数据库层面
-
版本号机制
为表添加一个版本号字段(如version
),更新时校验版本号 -
时间戳机制
类似版本号,但使用时间戳字段update_time
作为校验依据
对比总结
特性 | 悲观锁 | 乐观锁 |
---|---|---|
冲突预期 | 默认高概率冲突,提前加锁 | 默认低概率冲突,提交时校验 |
实现复杂度 | 简单(直接加锁) | 复杂(需处理冲突重试逻辑) |
性能 | 高并发下可能阻塞,吞吐量低 | 无锁竞争,吞吐量高 |
适用场景 | 强一致性、短事务、写多读少 | 最终一致性、长事务、读多写少 |
典型应用 | 银行转账、订单支付 | 商品库存、评论点赞 |
选择建议
-
若系统并发冲突频繁,或要求强一致性(如金融系统),优先选择悲观锁。
-
若系统读多写少,或能容忍短暂不一致(如电商库存),优先选择乐观锁。
-
分布式场景中,乐观锁更易扩展(如结合 Redis 或 ZooKeeper)。
5.聚簇索引与非聚簇索引区别
都是B+树的数据结构
聚簇索引:将数据存储与索引放到了一块、并且是按照一定的顺序组织的,找到索引也就找到了数据,数据的物理存放顺序与索引顺序是一致的,即:只要索引是相邻的,那么对应的数据一定也是相邻地存放在磁盘上的
非聚簇索引叶子节点不存储数据、存储的是数据行地址,也就是说根据索引查找到数据行的位置再取磁盘查找数据,这个就有点类似一本书的目录,比如我们要找第三章第一节,那我们先在这个目录里面找,找到对应的页码后再去对应的页码看文章。
优势:
1、查询通过聚簇索引可以直接获取数据,相比非聚簇索引需要第二次查询(非覆盖索引的情况下)效率要高
2、聚簇索引对于范围查询的效率很高,因为其数据是按照大小排列的
3、聚簇索引适合用在排序的场合,非聚簇索引不适合
劣势;
1、维护索引很昂贵,特别是插入新行或者主键被更新导至要分页(pagesplit)的时候。建议在大量插入新行后,选在负载较低的时间段,通过OPTIMIZETABLE优化表,因为必须被移动的行数据可能造成碎片。使用独享表空间可以弱化碎片
2、表因为使用uuId(随机ID)作为主键,使数据存储稀疏,这就会出现聚簇索引有可能有比全表扫面更慢,所以建议使用int的auto_increment作为主键
3、如果主键比较大的话,那辅助索引将会变的更大,因为辅助索引的叶子存储的是主键值,过长的主键值,会导致非叶子节点占用占用更多的物理空间
6.什么情况下mysql会索引失效
MySQL 索引失效会导致查询性能急剧下降,甚至触发全表扫描。以下是常见的索引失效场景及其原因和优化方法:
失效条件:
- where 后面使用函数
- 使用or条件
- 模糊查询 %放在前边
- 类型转换
- 组合索引 (最佳左前缀匹配原则)
1. 对索引列使用函数或计算
失效原因:索引存储的是列的原始值,对列进行函数操作或计算后,MySQL 无法直接匹配索引结构。
示例:
-- 索引失效(对 date 字段使用函数) SELECT * FROM orders WHERE YEAR(create_time) = 2023; -- 索引失效(对 price 进行计算) SELECT * FROM products WHERE price * 0.9 > 100;
优化方法:
-
将函数或计算移至表达式另一侧:
-- 优化后(直接使用时间范围) SELECT * FROM orders WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31'; -- 优化后(避免对列计算) SELECT * FROM products WHERE price > 100 / 0.9;
2. 隐式类型转换
失效原因:索引列与查询值的类型不一致,触发隐式类型转换(如字符串转数字)。
示例:
-- user_id 是 VARCHAR 类型,但查询使用数字(导致类型转换) SELECT * FROM users WHERE user_id = 10086;
优化方法:
-
保持类型一致:
SELECT * FROM users WHERE user_id = '10086';
3. 使用 LIKE
前导通配符
失效原因:以 %
或 _
开头的模糊查询无法利用索引的有序性。
示例:
-- 索引失效(前导通配符) SELECT * FROM articles WHERE title LIKE '%数据库%';
优化方法:
-
避免前导通配符,或使用全文索引(如
MATCH ... AGAINST
):-- 仅后缀模糊匹配(可能走索引) SELECT * FROM articles WHERE title LIKE '数据库%';
4. 组合索引未遵循最左前缀原则
失效原因:组合索引按最左列优先排序,若查询未包含最左列,索引失效。
示例:
-- 组合索引为 (a, b, c) SELECT * FROM table WHERE b = 2 AND c = 3; -- 未使用 a 列,索引失效
优化方法:
-
调整查询条件顺序,确保包含最左列:
SELECT * FROM table WHERE a = 1 AND b = 2 AND c = 3;
5. 使用 OR
连接非索引列
失效原因:若 OR
的某一侧字段无索引,优化器可能放弃使用索引。
示例:
-- name 有索引,age 无索引 SELECT * FROM users WHERE name = '张三' OR age = 25; -- 索引失效
优化方法:
-
为
OR
两侧字段都添加索引,或改用UNION
:SELECT * FROM users WHERE name = '张三' UNION SELECT * FROM users WHERE age = 25;
6. 索引列参与 !=
、NOT
、IS NULL
判断
失效原因:非等值查询(如 !=
、NOT IN
)可能导致优化器选择全表扫描。
示例:
-- 索引失效 SELECT * FROM orders WHERE status != 'paid'; SELECT * FROM users WHERE email IS NULL;
优化方法:
-
避免高频使用非等值查询,或结合覆盖索引优化:
-- 覆盖索引(仅查索引列) SELECT id FROM users WHERE email IS NULL;
7. 数据量过小或索引选择性低
失效原因:
-
表数据量过小(如几百行),优化器认为全表扫描更快。
-
索引选择性低(如性别字段只有
男/女
),索引效率不如全表扫描。
示例:
-- 性别字段索引选择性低 SELECT * FROM users WHERE gender = '男';
优化方法:
-
选择性低的字段避免单独建索引,可结合其他字段建组合索引。
8. 全表扫描成本低于索引查询
失效原因:当查询需要访问大部分数据时,优化器可能直接选择全表扫描。
示例:
-- 查询表中 90% 的数据 SELECT * FROM logs WHERE create_time > '2000-01-01';
优化方法:
-
限制查询范围或强制使用索引(需谨慎):
SELECT * FROM logs USE INDEX(idx_time) WHERE create_time > '2023-01-01';
9. 索引列使用 IN
或 BETWEEN
的范围过大
失效原因:IN
列表过长或 BETWEEN
范围过广,优化器可能放弃索引。
示例:
-- IN 列表包含数千个值 SELECT * FROM products WHERE id IN (1, 2, 3, ..., 10000);
优化方法:
-
分批次查询或改用临时表关联。
7.B+tree 与 B-tree区别
1. 数据结构设计
特性 | B树(B-Tree) | B+树(B+ Tree) |
---|---|---|
节点存储内容 | 每个节点存储键(Key)和对应的数据(Value) | 非叶子节点仅存储键,叶子节点存储键和数据 |
叶子节点链接 | 叶子节点独立,无链表连接 | 叶子节点通过指针形成有序双向链表 |
数据分布 | 数据可能分布在所有节点 | 数据仅存储在叶子节点 |
2. 查询性能
场景 | B树 | B+树 |
---|---|---|
单值查询 | 可能在非叶子节点命中数据(更快) | 必须遍历到叶子节点才能获取数据 |
范围查询 | 需中序遍历,效率较低(无链表支持) | 通过叶子节点链表直接遍历,效率极高 |
查询稳定性 | 查询时间波动较大(数据分布不均) | 查询时间稳定(所有查询必须到叶子节点) |
3. 存储与I/O效率
特性 | B树 | B+树 |
---|---|---|
节点容量 | 节点存储键+数据,单节点键数量较少 | 非叶子节点仅存键,单节点可容纳更多键 |
树高度 | 相对较高(相同数据量下) | 更矮胖(键密度高,减少磁盘I/O次数) |
空间利用率 | 非叶子节点存储数据,空间利用率较低 | 非叶子节点仅存键,空间利用率更高 |
4. 插入与删除
操作 | B树 | B+树 |
---|---|---|
分裂与合并 | 频繁(数据分布在所有节点) | 主要在叶子节点操作,非叶子节点仅调整键 |
复杂度 | 较高(需处理数据和键的重新分配) | 较低(数据仅存于叶子节点) |
5. 适用场景
场景 | B树 | B+树 |
---|---|---|
数据库索引 | 较少使用(如MongoDB的某些存储引擎) | 主流选择(如MySQL InnoDB、Oracle) |
文件系统 | 适用(如早期文件系统) | 更高效(如NTFS、ReiserFS) |
内存数据库 | 可能更优(减少指针跳转) | 适用但需权衡链表维护成本 |
总结
维度 | B树 | B+树 |
---|---|---|
设计目标 | 快速随机访问 | 高效范围查询和顺序扫描 |
优势 | 单点查询可能更快 | 高I/O效率、适合大规模数据存储 |
劣势 | 范围查询性能差、树高度较高 | 单点查询需访问叶子节点 |
典型应用 | 内存受限或随机访问密集场景 | 数据库、文件系统等磁盘存储场景 |
为什么数据库(如MySQL)选择B+树?
-
范围查询优化:叶子节点的链表结构天然支持高效范围查询(如
WHERE id > 100
)。 -
更低的树高度:减少磁盘I/O次数(适合海量数据)。
-
稳定的查询性能:所有查询最终落到叶子节点,时间波动小。
-
更适合磁盘预读:连续存储的叶子节点匹配磁盘块读取特性。
8.以MySQL为例Linux下如何排查问题
类似提问方式:如果线上环境出现问题比如网站卡顿重则瘫痪 如何是好?
一、快速应急(止血)
临时重启服务(仅限极端情况)
# 强制终止 MySQL 进程(慎用!可能丢失数据) kill -9 $(pidof mysqld) # 安全重启 MySQL systemctl restart mysql
限制流量入口
# 通过 iptables 临时屏蔽外部请求(示例屏蔽 80 端口) iptables -A INPUT -p tcp --dport 80 -j DROP
紧急杀会话
-- 登录 MySQL,终止长时间运行的查询 SHOW PROCESSLIST; KILL <query_id>;
二、系统资源排查
CPU 负载
# 查看 CPU 使用率及负载(重点看 %us 用户态、%sy 内核态) top -c htop # 按进程查看 CPU 占用(-p 指定进程) pidstat -p <mysql_pid> 1 5
内存使用
# 检查内存和 Swap 使用情况 free -h vmstat 1 5 # 查看 MySQL 内存分配 mysql> SHOW VARIABLES LIKE '%buffer%';
磁盘 I/O
# 查看磁盘 I/O 压力(关注 %util、await) iostat -x 1 5 # 定位高 I/O 进程(按进程统计) iotop
网络流量
# 检查网络连接和带宽(排查 DDoS 或异常连接) iftop -P netstat -antp | grep ESTABLISHED
三、MySQL 内部诊断
慢查询分析
-- 查看是否开启慢查询日志 SHOW VARIABLES LIKE 'slow_query_log'; -- 临时开启慢查询日志(立即生效) SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 1; -- 阈值设为 1 秒 -- 使用工具分析慢日志(示例) pt-query-digest /var/lib/mysql/slow.log
锁与事务
-- 查看当前锁等待 SELECT * FROM information_schema.INNODB_LOCK_WAITS; -- 检查长事务 SELECT * FROM information_schema.INNODB_TRX\G -- 查看表级锁(MyISAM) SHOW OPEN TABLES WHERE In_use > 0;
连接数暴增
-- 查看最大连接数限制 SHOW VARIABLES LIKE 'max_connections'; -- 检查当前连接数与来源 SHOW STATUS LIKE 'Threads_connected'; SHOW PROCESSLIST; -- 临时调整连接数(立即生效) SET GLOBAL max_connections = 1000;
缓存命中率
-- 检查 InnoDB 缓冲池命中率(低于 99% 需警惕) SHOW STATUS LIKE 'Innodb_buffer_pool_read%'; -- 计算公式: -- 命中率 = (1 - Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests) * 100%
四、进阶工具
性能剖析
# 使用 perf 分析 CPU 热点(需 root) perf top -p <mysql_pid>
内存泄漏检测
# 查看 MySQL 内存分配(valgrind 需重启)
valgrind --tool=memcheck --leak-check=full mysqld
死锁分析
-- 开启 InnoDB 监控(记录到错误日志) SET GLOBAL innodb_print_all_deadlocks = ON;
五、故障复盘与优化
日志归档
备份 /var/log/mysql/error.log
和慢查询日志。
配置调优
调整 innodb_buffer_pool_size
(通常设为物理内存的 70%~80%)。
架构改进
引入读写分离、分库分表、缓存(Redis)等。
监控告警
部署 Prometheus + Grafana 监控 MySQL 核心指标。
排查流程图
网站卡顿/瘫痪
│
├─ 应急处理:重启服务、限流、杀会话
│
├─ 系统资源检查:CPU、内存、磁盘、网络
│
├─ MySQL 内部分析:慢查询、锁、连接数、缓存
│
└─ 根因定位:日志分析、工具诊断
│
├─ 优化配置
├─ 架构升级
└─ 监控加固
9.如何处理慢查询
一、定位慢查询
1. 开启慢查询日志
2. 分析慢日志
二、分析执行计划
1. 使用 EXPLAIN
关键字段解读:
-
type:访问类型(
ALL
=全表扫描,index
=全索引扫描,range
=范围扫描) -
key:实际使用的索引
-
rows:预估扫描行数
-
Extra:
-
Using filesort
:需额外排序 -
Using temporary
:使用临时表 -
Using where
:需回表查询
-
2. EXPLAIN ANALYZE(MySQL 8.0+)
三、优化策略
1. 索引优化
-
添加缺失索引
-
避免索引失效
-
使用覆盖索引
2. SQL 重写
-
分页优化
-
分解复杂查询
3. 业务逻辑优化
-
避免全量数据拉取
-
缓存热点数据
使用 Redis 缓存高频查询结果(如用户基本信息)。
四、数据库调参
1. 内存优化
# 调整 InnoDB 缓冲池(通常设为物理内存的 70%~80%)
innodb_buffer_pool_size = 16G
2. 并发控制
# 调整连接池(根据硬件资源设置)
max_connections = 500
innodb_thread_concurrency = 16 -- CPU 核心数 × 2
五、架构级优化
场景 | 解决方案 |
---|---|
单表数据量过大(亿级) | 分库分表(Sharding) |
高频复杂查询 | 读写分离 + 缓存(Redis) |
实时聚合分析 | 同步至 OLAP 系统(ClickHouse) |
六、预防机制
1.自动化监控:部署 Prometheus + Grafana 监控慢查询数量、QPS、索引命中率等。
2.定期审计
3.开发规范
-
SQL 上线前必须经过 EXPLAIN 审核
-
禁止全表更新(如
UPDATE users SET status = 1
)
10.MySQL优化
1)尽量选择较小的列
(2)将where中用的比较频繁的字段建立索引
(3)select子句中避免使用‘*’
(4)避免在索引列上使用计算、not in 和<>等操作
(5)当只需要一行数据的时候使用limit 1
(6)保证单表数据不超过200W,适时分割表。针对查询较慢的语句,可以使用explain 来分析该语句具体的执行情况。
(7)避免改变索引列的类型。
(8)选择最有效的表名顺序,from字句中写在最后的表是基础表,将被最先处理,在from子句中包含多个表的情况下,你必须选择记录条数最少的表作为基础表。
(9)避免在索引列上面进行计算。
(10)尽量缩小子查询的结果
11.SQL语句优化案例
例1:where 子句中可以对字段进行 null 值判断吗?
可以,比如 select id from t where num is null 这样的 sql 也是可以的。但是最好不要给数据库留NULL,尽可能的使用 NOT NULL 填充数据库。不要以为 NULL 不需要空间,比如:char(100) 型,在字段建立时,空间就固定了, 不管是否插入值(NULL 也包含在内),都是占用 100 个字符的空间的,如果是 varchar 这样的变长字段,null 不占用空间。可以在 num 上设置默认值 0,确保表中 num 列没有 null 值,然后这样查询:select id from t where num= 0。
例2:如何优化?下面的语句?
select * from admin left join log on admin.admin_id = log.admin_id where log.admin_id>10
优化为:select * from (select * from admin where admin_id>10) T1 lef join log on T1.admin_id = log.admin_id。
使用 JOIN 时候,应该用小的结果驱动大的结果(left join 左边表结果尽量小如果有条件应该放到左边先处理, right join 同理反向),同时尽量把牵涉到多表联合的查询拆分多个 query(多个连表查询效率低,容易到之后锁表和阻塞)。
例3:limit 的基数比较大时使用 between
例如:select * from admin order by admin_id limit 100000,10
优化为:select * from admin where admin_id between 100000 and 100010 order by admin_id。
例4:尽量避免在列上做运算,这样导致索引失效
例如:select * from admin where year(admin_time)>2014
优化为: select * from admin where admin_time> '2014-01-01′
12.有没有设计过数据表?你是如何设计的
13.SQL编写
例1:
用一条SQL语句查询出每门课都大于80分的学生姓名
name kecheng fenshu
张三 语文 81
张三 数学 75
李四 语文 76
李四 数学 90
王五 语文 81
王五 数学 100
王五 英语 90
例2:
学生表 如下:
自动编号 学号 姓名 课程编号 课程名称 分数
1 2005001 张三 0001 数学 69
2 2005002 李四 0001 数学 89
3 2005001 张三 0001 数学 69
删除除了自动编号不同,其他都相同的学生冗余信息
例3:
一个叫team的表,里面只有一个字段name,一共有4条纪录,分别是a,b,c,d,对应四个球队,现在四个球队进行比赛,用一条sql语句显示所有可能的比赛组合.
例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
例5:
说明:复制表(只复制结构,源表名:a新表名:b)
例6:
原表:
courseid coursename score
1 java 70
2 oracle 90
3 xml 40
4 jsp 30
5 servlet 80
为了便于阅读,查询此表后的结果显式如下(及格分数为60):
courseid coursename score mark
1 java 70 pass
2 oracle 90 pass
3 xml 40 fail
4 jsp 30 fail
5 servlet 80 pass
写出此查询语句
例7:
表名:购物信息
购物人 商品名称 数量
A 甲 2
B 乙 4
C 丙 1
A 丁 2
B 丙 5
给出所有购入商品为两种或两种以上的购物人记录
例8:
info 表
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
如果要生成下列结果, 该如何写sql语句?
date win lose
2005-05-09 2 2
2005-05-10 1 2
例9 mysql 创建了一个联合索引(a,b,c) 以下 索引生效 的是(1,2,4)
1、where a = 1 and b = 1 and c =1
2、where a = 1 and c = 1
3、where b = 1 and c = 1,
4、where b = 1 and a =1 and c = 1