一、索引相关的问题
1、MYSQL的索引有哪些?
MySQL 中常见的索引类型主要按逻辑功能划分,底层结构通常是 B+Tree (除非指定为 HASH):
主键索引:
特殊的唯一索引,要求所有值唯一且非 NULL。
每个表只能有一个主键索引(但可以是复合主键)。
如果未显式定义主键,InnoDB 会尝试找一个非空的唯一索引替代,如果也没有,则隐式创建一个隐藏的主键索引。
对于 InnoDB 表,主键索引即聚簇索引。
唯一索引:
索引列的值必须唯一,但允许 NULL 值(对于 NULL 值,通常也只允许出现一次,具体取决于数据库配置)。
一个表可以有多个唯一索引。
可以是单列索引,也可以是复合索引。
普通索引:
最基本的索引类型,仅加速查询,没有唯一性约束,允许 NULL 值和重复值。
也称为非唯一索引或辅助索引。
应用最广泛。
全文索引:
专门用于对文本列(如
CHAR
,VARCHAR
,TEXT
)进行全文搜索。主要用于
MATCH ... AGAINST
语法,实现复杂的文本匹配(如关键词搜索)。MySQL 5.6+ 的 InnoDB 支持全文索引。
底层结构不同于 B+Tree,通常使用倒排索引。
空间索引:
用于地理空间数据类型(
GEOMETRY
,POINT
,LINESTRING
,POLYGON
等)。基于 R-Tree 结构实现。
支持空间函数进行地理位置的检索。
按物理结构/存储方式分:
聚簇索引:
在 InnoDB 中,表数据本身按照主键(或隐式主键)的顺序存储在 B+Tree 的叶子节点上。主键索引就是聚簇索引。
一个表只能有一个聚簇索引(决定了数据的物理存储顺序)。
非聚簇索引 / 二级索引:
叶子节点存储的不是整行数据,而是该索引的键值 + 主键值(或指向聚簇索引的指针)。
普通索引、唯一索引都属于二级索引(除非唯一索引也用于组织数据存储顺序,但这仅在特殊存储引擎如 TokuDB 中常见)。
根据非聚簇索引找到主键后,需要回到聚簇索引(表数据)中查找完整记录的过程,称为 回表。
按索引列的构成分:
单列索引: 索引只包含表中的一列。
复合索引 / 联合索引: 索引包含表中的多个列。查询时遵守“最左匹配原则”。
特殊索引类型:
前缀索引: 只索引字段开头的一部分字符(如
INDEX name_idx(name(10))
),用于减少大文本字段的索引空间开销。覆盖索引: 不是一种单独的索引类型,而是一种查询优化现象。如果一个索引包含(或者说覆盖)了查询语句需要返回的所有字段的值,查询就可以直接通过扫描索引获得结果,避免了回表操作,效率很高。例如,复合索引
INDEX (a, b)
对于查询SELECT b FROM tbl WHERE a = 10
来说就是一个覆盖索引。
2、什么时候索引不生效?
使用函数或表达式:
SELECT * FROM users WHERE SUBSTRING(phone, 1, 3) = '138';
LIKE模糊查询以通配符开头:
SELECT * FROM users WHERE phone LIKE '%8000';
查询条件包含OR且部分条件无索引:
SELECT * FROM users WHERE phone = '13800138000' OR register_time > '2023-01-01';
优化器判断全表扫描更快(如查询大部分数据)
3、主键索引和唯一索引的区别?
核心区别总结: 主键索引是更严格的唯一索引,它要求非空且每个表只能有一个,并在 InnoDB 中作为聚簇索引。唯一索引只保证唯一性(允许NULL),可以有多个
4、它们在查询方面有什么区别?
主要区别在于对 NULL 值的处理和作为行标识符的特性:
NULL 值处理:
唯一索引: 可以在 WHERE 条件中查找
IS NULL
或IS NOT NULL
(如果索引列允许 NULL)。主键索引: 由于主键列不允许 NULL,查询条件中通常不会有针对主键的
IS NULL
条件(除非写错了)。如果写了,优化器可能会直接认为结果集为空。
行标识符特性:
主键索引:
在 InnoDB 中: 由于是聚簇索引,通过主键查找是最快的查询方式,因为引擎可以直达存储数据的 B+Tree 叶子节点。
SELECT * FROM table WHERE pk = ?
效率最高。是行数据的物理地址依据。
是其他表通过外键引用的目标。
唯一索引:
在 InnoDB 中: 它通常是二级索引。使用唯一索引查询
SELECT * FROM table WHERE unique_col = ?
的过程是:在唯一索引的 B+Tree 中找到对应的主键值。
根据这个主键值回到聚簇索引(主键索引)的 B+Tree 中查找整行数据(即可能发生回表)。
查找整行数据的效率通常比直接使用主键索引查找略低(多一次查找步骤)。但如果查询只需要包含在唯一索引或覆盖索引中的列,则不会回表,效率一样高。
优化器选择:
当查询条件既匹配主键索引也匹配某个唯一索引时,优化器通常优先选择主键索引(聚簇索引) 来避免回表操作。
如果查询条件只需要返回唯一索引列的值(或满足覆盖索引),优化器也可能会选择唯一索引(此时不需要回表)。
5、什么是回表?
聚簇索引:
在 InnoDB 中,表数据本身按照主键(或隐式主键)的顺序存储在 B+Tree 的叶子节点上。主键索引就是聚簇索引。
一个表只能有一个聚簇索引(决定了数据的物理存储顺序)。
非聚簇索引 / 二级索引:
叶子节点存储的不是整行数据,而是该索引的键值 + 主键值(或指向聚簇索引的指针)。
普通索引、唯一索引都属于二级索引(除非唯一索引也用于组织数据存储顺序,但这仅在特殊存储引擎如 TokuDB 中常见)。
根据非聚簇索引找到主键后,需要回到聚簇索引(表数据)中查找完整记录的过程,称为 回表。
示例:
假设表 user
有列: id (INT PRIMARY KEY)
, name (VARCHAR)
, age (INT)
。在 name
上建了一个普通索引 idx_name(name)
。
SELECT * FROM user WHERE name = 'Alice';
使用
idx_name
索引找到'Alice'
对应的id
(比如是100
)。使用
id=100
到主键索引中找到包含id
,name
,age
的完整行数据。 发生了回表!
SELECT id, name FROM user WHERE name = 'Alice';
使用
idx_name
索引找到'Alice'
对应的id
和name
(索引idx_name
本身就是(name, id)
的结构)。查询结果
id
和name
都在二级索引叶子节点中获得了。无需回表!(覆盖索引)
6、索引的底层是什么?
MySQL 索引的底层数据结构最常见、最核心的是 B+Tree(B-树的变种)。几乎所有的 InnoDB 表索引(包括主键索引、唯一索引、普通索引)都是 B+Tree 结构。 Memory 存储引擎默认使用 HASH 索引,但也支持 B+Tree 索引。
为什么选择 B+Tree?
相比于其他数据结构(如 B-Tree, Hash, 二叉树),B+Tree 在数据库索引场景中具有显著优势:
适合磁盘存储 & 高扇出性: B+Tree 是一种平衡多路查找树。每个节点可以存储大量键值(高扇出),使得整个树的高度较低(通常 3-4 层就能存储海量数据)。树的高度低意味着查找时所需的磁盘 I/O 次数少,这对于磁盘读写慢的特点至关重要。
数据全部存储在叶子节点: 在 B+Tree 中,所有实际的键值和指向数据的指针(或主键)都只存储在叶子节点。内部节点仅存储键值和指向子节点的指针(索引),起到导航作用。
优点 1:范围查询高效。只需顺序遍历叶子节点链表即可快速扫描某个范围的数据(如
WHERE id BETWEEN 10 AND 20
)。优点 2:查询更稳定。任何查找都必须到达叶子节点,路径长度相同(稳定 O(logN) 复杂度)。
优点 3:非叶子节点更小。因为非叶子节点只存键值和指针,不存数据,所以一个磁盘块/内存页可以容纳更多非叶子节点,进一步降低树高度,减少 I/O。
叶子节点形成有序链表: 所有叶子节点通过指针双向链接成一个有序链表。
高效范围查询: 找到范围的起始点后,沿着链表顺序扫描即可找到范围内的所有记录,非常适合 SQL 中的
>, <, BETWEEN
等范围条件。高效全表扫描: 直接顺序遍历叶子节点链表几乎等同于全表扫描(按照索引顺序)。
7、最左匹配原则
最左匹配原则(Leftmost Prefix Principle / Matching Prefix)是 MySQL 在使用复合索引(联合索引) 时遵循的一个核心规则。
定义:
当创建一个包含多列 (col1, col2, col3, ...)
的复合索引后,这个索引实际上可以被看作多个独立的索引片段:
(col1)
(col1, col2)
(col1, col2, col3)
... 以此类推。
最左匹配原则指的是,MySQL 在利用这个复合索引进行查询时,只能从索引定义中最左边(最前)的列开始使用,并且必须是一个连续的前缀部分。查询条件必须包含复合索引的最左列(第一列),并且(如果包含后续列的话)必须按索引中定义的列顺序连续地匹配。
关键规则:
必须包含最左列: WHERE 子句中的查询条件必须包含复合索引的第一个列 (col1)。没有 col1 的条件,该复合索引通常无法被有效使用(除非特殊情况如覆盖索引跳过 WHERE)。
连续前缀匹配: 如果查询条件包含了索引定义顺序中的前几列(连续),则索引可以被用于匹配这部分列。例如:
索引
(a, b, c)
有效:
WHERE a = 1
有效:
WHERE a = 1 AND b = 2
有效:
WHERE a = 1 AND b = 2 AND c = 3
有效:
WHERE a = 1 AND c = 3
(能用 a,但 c 不会被索引用于查找!仅在筛选时过滤,且可能导致中断优化)。无效:
WHERE b = 2
(不包含最左列a
)无效:
WHERE b = 2 AND c = 3
(不包含最左列a
)更无效:
WHERE c = 3
8、如果表中的字段是整数类型,你用字符串类型的可以查出来吗?
可以查出来,但这不是一个好主意,并且会带来性能问题。
虽然在语法上能查出来(因为隐式转换),但强烈建议始终让 WHERE 条件中的值与列的数据类型保持一致。如果列是整数类型 (INT
),比较时就应该使用整数 (123
),而不是字符串 ('123'
)。这样可以:
确保结果的正确性(避免隐式转换带来的意外结果)。
保证索引的使用,获得最优查询性能。
代码清晰易懂。
9、你平时一张表里,一般会建多少个索引?
是一个没有绝对正确答案的问题,答案需根据具体情况权衡。但面试官想考察你对索引代价的理解和实践经验。
回答策略:
说明没有固定数字: "没有绝对的硬性限制,需要根据表的具体使用场景、查询模式、数据量、写入频率以及数据库资源来权衡设计"。
强调权衡利弊:
索引的好处: 显著加速查询(尤其是读操作)。
索引的代价:
占用磁盘空间: 每个索引都是一棵 B+Tree,需要额外存储空间。
降低写入速度:
INSERT
,UPDATE
,DELETE
操作发生时,不仅要修改数据,还需要维护所有相关的索引树结构(插入、删除索引项)。表上的索引越多,写入操作的成本就越高,速度就越慢。优化器选择困难: 索引太多可能让优化器在选择执行路径时花费更多时间,甚至偶尔选择不最优的路径。
冗余索引: 两个索引有相同的前缀列,可能其中一个就是冗余的。
10、你有做过分库分表吗?
11、mybaits里面,#和$的区别?
#{}:SQL 预编译阶段被替换为 占位符?
#{}:SQL 解析阶段直接替换为参数值,SQL拼接,有SQL注入的风险
12、为什么MyBatis要设计两种占位符?
MyBatis设计两种占位符是为了兼顾安全性和灵活性:
#{}
通过预编译机制从根本上防止SQL注入,适用于绝大多数值传递场景;${}
提供字符串替换能力,用于解决动态SQL的特殊需求(如动态表名);这种设计体现了MyBatis"约定优于配置"的理念:默认安全(
#{}
),在确有必要时开放风险能力(${}
);实际开发中我们应该遵循"能用#{}就不用"的原则,对于必须使用{}的场景要严格进行参数校验。
13、索引下推(Index Condition Pushdown,ICP)是什么?
简单来说就是没有ICP的时候,存储引擎只按索引找数据,不管WHERE条件,把所有可能匹配的数据都返回给Server层,导致大量无效数据传输和计算。有ICP的话,存储引擎在查索引时,顺便用WHERE条件过滤,只返回真正匹配的数据。
举个具体例子:
SELECT * FROM users WHERE name LIKE '张%' AND age = 20;
假设有联合索引 (name, age)
。
无ICP(传统方式)
存储引擎通过索引找到所有
name LIKE '张%'
的记录(比如100条)。把这100条记录全部回表,读取完整数据返回给Server层。
Server层再根据
age = 20
过滤,最终得到10条结果。
问题:白白回表了90条无效数据!
有ICP(下推优化)
存储引擎查索引时,发现age也在索引中,于是直接检查
name LIKE '张%' AND age = 20
。只对符合条件的10条记录回表,返回给Server层。
优势:减少90%的回表操作!
总结:
“下推” = 把过滤条件从Server层推到存储引擎层执行。
目的:减少数据传输和计算量,提升查询速度。
触发条件:WHERE条件中的字段必须属于当前使用的索引