一、索引核心认知:原理 + 类型(面试基础必问)
1.1 索引的本质与设计目的
- 本质:索引是数据库中为加速数据查询而构建的「有序数据结构」(类比书籍目录),避免全表扫描;
- 核心价值:降低 IO 次数(机械硬盘 IO 是性能瓶颈)、减少数据比较次数,将查询复杂度从 O (n) 降至 O (log n);
- 存储代价:索引需额外占用存储空间(约为数据量的 10%-30%),且会降低 INSERT/UPDATE/DELETE 效率(需同步维护索引)。
1.2 常见索引类型及适用场景(面试高频)
|-------------------|---------------|--------------------------|------------------------------|--------------------------------|
| 索引类型 | 底层结构 | 核心特点 | 适用场景 | 面试关联问题 |
| 主键索引(PRIMARY KEY) | B + 树(InnoDB) | 唯一且非空,叶子节点存储完整数据(聚簇索引) | 表主键(如 user_id) | 1. 主键索引与普通索引的区别?2. 聚簇索引的优势? |
| 普通索引(INDEX) | B + 树 | 非唯一,叶子节点存储主键值(二级索引) | 频繁查询的字段(如 username) | 二级索引查询为何需要回表? |
| 唯一索引(UNIQUE) | B + 树 | 唯一(允许 NULL),叶子节点存储主键值 | 需保证唯一性的字段(如 phone) | 唯一索引与主键索引的区别? |
| 联合索引(复合索引) | B + 树 | 多字段组合,按「最左前缀原则」匹配 | 多字段联合查询(如 where a=? and b=?) | 1. 最左前缀原则的具体含义?. 联合索引字段顺序如何设计? |
| 全文索引(FULLTEXT) | 倒排索引 | 支持模糊匹配(关键词检索),不支持前缀 % 匹配 | 文本内容检索(如文章内容) | 全文索引与 like % xxx% 的区别? |
| 哈希索引(HASH) | 哈希表 | 等值查询极快,不支持范围查询、排序 | 仅等值查询场景(如 Redis) | InnoDB 为何不推荐用哈希索引? |
1.3 索引底层原理(InnoDB B + 树核心)
- 结构特点:
-
- 非叶子节点仅存储索引键 + 子节点指针,叶子节点存储完整数据(聚簇索引)或主键值(二级索引);
-
- 叶子节点通过双向链表连接,支持范围查询和排序;
-
- 树高通常为 3-4 层(百万级数据),一次查询仅需 3-4 次 IO。
- 面试必问:聚簇索引与非聚簇索引的区别?
-
- 聚簇索引(主键):叶子节点 = 数据,查询无需回表;
-
- 非聚簇索引(普通 / 唯一):叶子节点 = 主键值,查询需通过主键回表查询完整数据(覆盖索引可避免回表)。
二、索引优化:避坑 + 最佳实践(面试核心)
2.1 索引失效的 8 种常见场景(高频考点)
- 使用函数或表达式操作索引字段
-
- 错误:where DATE(create_time) = '2024-01-01'(索引失效,全表扫描);
-
- 正确:where create_time between '2024-01-01 00:00:00' and '2024-01-01 23:59:59';
-
- 原理:函数操作破坏索引的有序性,无法触发索引查找。
- 隐式类型转换
-
- 错误:where phone = 13800138000(phone 为 varchar 类型,索引失效);
-
- 正确:where phone = '13800138000';
-
- 原理:MySQL 会将字符串转为数字(cast(phone as signed)),触发函数操作。
- 模糊查询前缀含 %
-
- 错误:where username like '%张三'(索引失效);
-
- 正确:where username like '张三%'(前缀无 %,索引有效);
-
- 替代方案:前缀 % 场景用全文索引。
- 联合索引不满足最左前缀原则
-
- 索引:idx_a_b_c(a,b,c);
-
- 有效查询:where a=?、where a=? and b=?、where a=? and b=? and c=?;
-
- 失效查询:where b=?、where c=?、where a=? and c=?(仅 a 字段索引有效)。
- 使用 OR 连接非索引字段
-
- 错误:where index_col=? or non_index_col=?(索引失效,全表扫描);
-
- 正确:要么给 non_index_col 加索引,要么拆分为两个查询(union all)。
- 使用!=、、not in、is not null
-
- 场景:where status != 1(索引失效,全表扫描);
-
- 替代方案:where status in (0,2,3)(索引有效);
-
- 例外:is null 在 MySQL 8.0 + 中可能触发索引(需结合数据分布)。
- 查询结果占比过大(超过 20%)
-
- 场景:where age > 18(表中 90% 数据满足);
-
- 原理:MySQL 优化器认为全表扫描比索引查询更快(减少回表开销)。
- 使用 order by rand ()
-
- 错误:select * from user order by rand() limit 10(索引失效,且性能极差);
-
- 正确:用主键随机取值(where id > (select floor(rand()*max(id)) from user) limit 10)。
2.2 索引设计最佳实践(实战 + 面试)
- 优先创建聚簇索引(主键)
-
- 建议:用自增 INT/BIGINT 作为主键(避免 UUID,UUID 无序会导致 B + 树频繁分裂);
-
- 理由:自增主键保证数据有序插入,减少索引维护开销。
- 联合索引字段顺序设计:高选择性字段在前
-
- 定义:选择性 = 唯一值数量 / 总记录数(选择性越高,过滤效果越好);
-
- 示例:查询where status=1 and username='张三',username 选择性高于 status,索引应设为idx_username_status(username, status)。
- 覆盖索引:避免回表
-
- 定义:查询字段均可从索引中获取(无需回表查询聚簇索引);
-
- 示例:索引idx_id_name(id, name),查询select id, name from user where id=?(索引覆盖,无需回表);
-
- 面试点:如何判断是否使用覆盖索引?执行计划中Extra字段显示「Using index」。
- 避免过度索引
-
- 问题:过多索引会导致 INSERT/UPDATE 变慢(需同步维护所有索引);
-
- 原则:一个表索引数量不超过 5 个,联合索引可替代多个单列索引(如 idx_a_b 替代 idx_a 和 idx_b)。
- 区分度低的字段不建索引
-
- 示例:性别(男 / 女)、状态(0/1)等字段,选择性极低,建索引反而增加开销。
2.3 索引优化工具:执行计划(EXPLAIN)
- 核心作用:分析 SQL 是否使用索引、索引类型、查询方式(全表扫描 / 索引扫描);
- 关键字段解读(面试必问):
-
- type:索引使用类型(从优到差:system > const > eq_ref > ref > range > index > ALL);
-
-
- 重点:ALL表示全表扫描,需优化;range表示范围查询(索引有效);ref表示非唯一索引等值查询。
-
-
- key:实际使用的索引名称(NULL 表示未使用索引);
-
- key_len:索引使用的长度(越长表示使用的索引字段越多);
-
- Extra:额外信息(Using index:覆盖索引;Using filesort:文件排序(需优化);Using temporary:临时表(需优化))。
- 示例:explain select * from user where username like '张三%';
-
- 若type=range、key=idx_username,说明索引有效;
-
- 若type=ALL、key=NULL,说明索引失效。
三、SQL 优化:核心技巧 + 实战案例(面试高频)
3.1 SQL 优化通用原则
- ** 只查需要的字段,避免 select * **- 错误:select * from user where id=1(查询所有字段,可能触发回表);
-
- 正确:select id, username from user where id=1(覆盖索引,性能更优);
-
- 理由:减少数据传输量,避免不必要的回表操作。
- 优化 join 查询
-
- 原则 1:小表驱动大表(left join时,小表作为左表;inner join时,MySQL 自动优化);
-
- 原则 2:join 字段加索引(避免笛卡尔积,减少连接开销);
- 优化子查询:用 join 替代
-
- 错误:select * from user where dept_id in (select dept_id from dept where status=1)(子查询效率低);
-
- 正确:select u.* from user u join dept d on u.dept_id = d.dept_id where d.status=1(join 效率更高,避免子查询重复执行)。
- 优化排序:利用索引排序
-
- 原则:排序字段与索引字段一致,避免Using filesort;
-
- 示例:索引idx_a_b(a,b),查询select * from user where a=? order by b(索引排序,无文件排序);
-
- 错误:select * from user where a=? order by c(触发Using filesort,需优化)。
- 优化分页查询(limit 大偏移量)
-
- 错误:select * from user order by id limit 10000, 10(偏移量 10000,需扫描 10010 条数据);
-
- 正确:用主键过滤(select * from user where id > 10000 limit 10),利用索引快速定位;
-
- 适用场景:主键自增,连续无断层。
3.2 高频 SQL 场景优化案例
- 场景 1:统计总数(count 优化)
-
- 问题:select count(*) from user(InnoDB 全表扫描,百万级数据慢);
-
- 优化方案:
-
-
- 用count(1)或count(主键)替代count(*)(效率略高,避免字段判断);
-
-
-
- 缓存统计结果(如 Redis 存储总数,定时更新);
-
-
-
- 分表场景:用汇总表记录各分表总数。
-
- 场景 2:模糊查询(前缀 %)
-
- 问题:select * from article where content like '%Java编程%'(索引失效,全表扫描);
-
- 优化方案:
-
-
- 用全文索引(alter table article add fulltext index idx_content(content));
-
-
-
- 查询:select * from article where match(content) against('Java编程')(全文索引效率远高于 like);
-
-
-
- 复杂场景:用 Elasticsearch 替代数据库检索。
-
- 场景 3:批量操作优化
-
- 错误:循环执行insert into user(name) values(?)(1000 次 IO,效率低);
-
- 正确:批量插入(insert into user(name) values(?), (?), ... (?)(1 次 IO,效率提升 10 倍);
-
- 注意:MySQL 默认允许批量插入的最大值由max_allowed_packet控制(建议设为 16M)。
- 场景 4:避免重复查询(exists 替代 in)
-
- 场景:判断用户是否存在(where id in (1,2,3));
-
- 优化:exists比in更高效(exists只要找到一条匹配就返回,in需全部匹配);
-
- 示例:select * from user u where exists (select 1 from order o where o.user_id = u.id)。
3.3 事务与锁优化(关联面试题)
- 事务隔离级别与性能
-
- 隔离级别(从低到高):读未提交(Read Uncommitted)→ 读已提交(Read Committed)→ 可重复读(Repeatable Read)→ 串行化(Serializable);
-
- 性能:级别越低,性能越高(锁竞争越少);
-
- 建议:默认用 InnoDB 的「可重复读」(RR),兼顾一致性和性能;读多写少场景可用「读已提交」(RC),减少锁开销。
- 避免死锁的 4 个原则
-
-
- 统一事务中锁的获取顺序(如先锁表 A,再锁表 B);
-
-
-
- 减少锁持有时间(事务中避免长耗时操作,如 IO、睡眠);
-
-
-
- 避免批量更新(分批更新,减少锁范围);
-
-
-
- 用行级锁替代表级锁(避免update user set status=1(全表锁),加 where 条件where id=?(行锁))。
-
四、面试高频问题与标准答案
- 索引的优缺点是什么?
-
- 优点:1. 加速查询(等值、范围、排序);2. 减少 IO 次数;3. 优化连接查询;
-
- 缺点:1. 占用额外存储空间;2. 降低写操作(INSERT/UPDATE/DELETE)效率;3. 索引维护开销。
- 聚簇索引与非聚簇索引的区别?
-
- 聚簇索引:1. 主键索引;2. 叶子节点存储完整数据;3. 一张表仅一个;4. 查询无需回表;
-
- 非聚簇索引:1. 普通 / 唯一 / 联合索引;2. 叶子节点存储主键值;3. 一张表可多个;4. 查询需回表(覆盖索引除外)。
- 如何判断 SQL 是否需要优化?
-
-
- 执行时间长(超过 1 秒);2. 执行计划中type=ALL(全表扫描);3. Extra字段出现Using filesort/Using temporary;4. 慢查询日志中记录的 SQL。
-
- limit 10000,10 为什么慢?如何优化?
-
- 原因:MySQL 会扫描前 10010 条数据,丢弃前 10000 条,仅返回后 10 条,偏移量越大越慢;
-
- 优化:1. 用主键过滤(where id > 10000 limit 10);2. 用游标分页;3. 分表场景下按分表键分页。
- 联合索引的最左前缀原则是什么?
-
- 答:联合索引idx_a_b_c(a,b,c)的索引顺序是先按 a 排序,再按 b 排序,最后按 c 排序;查询时需从左到右匹配字段,中间不能跳过。例如:
-
-
- 有效:where a=?、where a=? and b=?、where a=? and b=? and c=?;
-
-
-
- 无效:where b=?、where a=? and c=?(仅 a 字段生效)。
-
- SQL 优化的一般步骤是什么?
-
-
- 用 EXPLAIN 分析执行计划,定位问题(如全表扫描、索引失效);
-
-
-
- 优化索引(添加缺失索引、调整联合索引顺序、删除冗余索引);
-
-
-
- 重构 SQL(避免索引失效场景、用 join 替代子查询、优化排序分页);
-
-
-
- 调整数据库参数(如连接数、缓存大小);
-
-
-
- 分库分表(数据量超千万时)。
-
五、总结:核心要点速记
- 索引优化:记住「8 种失效场景 + 5 大设计原则」,用 EXPLAIN 验证效果;
- SQL 优化:遵循「只查必要字段、利用索引排序、小表驱动大表、避免全表扫描」;
- 面试重点:索引底层原理、聚簇与非聚簇索引区别、执行计划解读、慢 SQL 优化案例;
- 实战建议:开发时先写执行计划验证索引有效性,上线前做压力测试,监控慢查询日志。
掌握以上知识点,可轻松应对数据库面试中的索引优化、SQL 优化等核心问题,同时提升实际开发中的数据库性能调优能力。