数据库相关的面试题
数据库 面经 12

一、索引相关的问题

1、MYSQL的索引有哪些?

MySQL 中常见的索引类型主要按逻辑功能划分,底层结构通常是 B+Tree (除非指定为 HASH):

  1. ​主键索引:​

    • 特殊的唯一索引,要求所有值​​唯一且非 NULL​​。

    • 每个表​​只能有一个​​主键索引(但可以是复合主键)。

    • 如果未显式定义主键,InnoDB 会尝试找一个非空的唯一索引替代,如果也没有,则隐式创建一个隐藏的主键索引。

    • 对于 InnoDB 表,主键索引即​​聚簇索引​​。

  2. ​唯一索引:​

    • 索引列的值必须​​唯一​​,但​​允许 NULL 值​​(对于 NULL 值,通常也只允许出现一次,具体取决于数据库配置)。

    • 一个表可以有​​多个​​唯一索引。

    • 可以是单列索引,也可以是复合索引。

  3. ​普通索引:​

    • 最基本的索引类型,仅加速查询,​​没有唯一性约束​​,​​允许 NULL 值和重复值​​。

    • 也称为​​非唯一索引​​或​​辅助索引​​。

    • 应用最广泛。

  4. ​全文索引:​

    • 专门用于对文本列(如 CHAR, VARCHAR, TEXT)进行​​全文搜索​​。

    • 主要用于 MATCH ... AGAINST 语法,实现复杂的文本匹配(如关键词搜索)。

    • MySQL 5.6+ 的 InnoDB 支持全文索引。

    • 底层结构不同于 B+Tree,通常使用​​倒排索引​​。

  5. ​空间索引:​

    • 用于地理空间数据类型(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 值​​的处理和作为​​行标识符​​的特性:

  1. ​NULL 值处理:​

    • ​唯一索引:​​ 可以在 WHERE 条件中查找 IS NULLIS NOT NULL(如果索引列允许 NULL)。

    • ​主键索引:​​ 由于主键列不允许 NULL,查询条件中通常不会有针对主键的 IS NULL 条件(除非写错了)。如果写了,优化器可能会直接认为结果集为空。

  2. ​行标识符特性:​

    • ​主键索引:​

      • ​在 InnoDB 中:​​ 由于是聚簇索引,​​通过主键查找是最快的查询方式​​,因为引擎可以直达存储数据的 B+Tree 叶子节点。SELECT * FROM table WHERE pk = ? 效率最高。

      • 是行数据的物理地址依据。

      • 是其他表通过外键引用的目标。

    • ​唯一索引:​

      • ​在 InnoDB 中:​​ 它通常是二级索引。使用唯一索引查询 SELECT * FROM table WHERE unique_col = ? 的过程是:

        1. 在唯一索引的 B+Tree 中找到对应的主键值。

        2. 根据这个主键值回到聚簇索引(主键索引)的 B+Tree 中查找整行数据(即可能发生​​回表​​)。

      • 查找整行数据的效率​​通常​​比直接使用主键索引查找略低(多一次查找步骤)。但如果查询只需要包含在唯一索引或覆盖索引中的列,则不会回表,效率一样高。

  3. ​优化器选择:​

    • 当查询条件既匹配主键索引也匹配某个唯一索引时,优化器通常​​优先选择主键索引(聚簇索引)​​ 来避免回表操作。

    • 如果查询条件只需要返回唯一索引列的值(或满足覆盖索引),优化器也可能会选择唯一索引(此时不需要回表)。

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' 对应的 idname (索引 idx_name 本身就是 (name, id) 的结构)。

    • 查询结果 idname 都在二级索引叶子节点中获得了。​​无需回表!(覆盖索引)​

6、索引的底层是什么?

MySQL 索引的底层数据结构​​最常见、最核心​​的是 ​​B+Tree(B-树的变种)​​。几乎所有的 InnoDB 表索引(包括主键索引、唯一索引、普通索引)都是 B+Tree 结构。 Memory 存储引擎默认使用 HASH 索引,但也支持 B+Tree 索引。

​为什么选择 B+Tree?​

相比于其他数据结构(如 B-Tree, Hash, 二叉树),B+Tree 在数据库索引场景中具有显著优势:

  1. ​适合磁盘存储 & 高扇出性:​​ B+Tree 是一种​​平衡多路查找树​​。每个节点可以存储大量键值(高扇出),使得整个树的高度较低(通常 3-4 层就能存储海量数据)。​​树的高度低意味着查找时所需的磁盘 I/O 次数少​​,这对于磁盘读写慢的特点至关重要。

  2. ​数据全部存储在叶子节点:​​ 在 B+Tree 中,​​所有实际的键值和指向数据的指针(或主键)都只存储在叶子节点​​。内部节点仅存储键值和指向子节点的指针(索引),起到导航作用。

    • ​优点 1:范围查询高效​​。只需顺序遍历叶子节点链表即可快速扫描某个范围的数据(如 WHERE id BETWEEN 10 AND 20)。

    • ​优点 2:查询更稳定​​。任何查找都必须到达叶子节点,路径长度相同(稳定 O(logN) 复杂度)。

    • ​优点 3:非叶子节点更小​​。因为非叶子节点只存键值和指针,不存数据,所以一个磁盘块/内存页可以容纳更多非叶子节点,进一步降低树高度,减少 I/O。

  3. ​叶子节点形成有序链表:​​ 所有叶子节点通过指针​​双向链接​​成一个有序链表。

    • ​高效范围查询:​​ 找到范围的起始点后,沿着链表顺序扫描即可找到范围内的所有记录,非常适合 SQL 中的 >, <, BETWEEN 等范围条件。

    • ​高效全表扫描:​​ 直接顺序遍历叶子节点链表几乎等同于全表扫描(按照索引顺序)。

7、最左匹配原则

​最左匹配原则​​(Leftmost Prefix Principle / Matching Prefix)是 MySQL 在使用​​复合索引(联合索引)​​ 时遵循的一个核心规则。

​定义:​
当创建一个包含多列 (col1, col2, col3, ...) 的复合索引后,这个索引实际上可以被看作​​多个独立的索引片段​​:

  • (col1)

  • (col1, col2)

  • (col1, col2, col3)

  • ... 以此类推。

最左匹配原则指的是,MySQL 在利用这个复合索引进行查询时,​​只能从索引定义中最左边(最前)的列开始使用,并且必须是一个连续的前缀部分​​。查询条件必须包含复合索引的​​最左列​​(第一列),并且(如果包含后续列的话)必须按索引中定义的列顺序​​连续地匹配​​。

​关键规则:​

  1. ​必须包含最左列:​​ WHERE 子句中的查询条件​​必须包含复合索引的第一个列 (col1)​​。没有 col1 的条件,该复合索引通常无法被有效使用(除非特殊情况如覆盖索引跳过 WHERE)。

  2. ​连续前缀匹配:​​ 如果查询条件包含了索引定义顺序中的前几列(连续),则索引可以被用于匹配这部分列。例如:

    • 索引 (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、你平时一张表里,一般会建多少个索引?

是一个没有绝对正确答案的问题,答案需根据具体情况权衡。但面试官想考察你对索引代价的理解和实践经验。​

​回答策略:​

  1. ​说明没有固定数字:​​ "没有绝对的硬性限制,需要根据表的具体使用场景、查询模式、数据量、写入频率以及数据库资源来权衡设计"。

  2. ​强调权衡利弊:​

    • ​索引的好处:​​ 显著加速查询(尤其是读操作)。

    • ​索引的代价:​

      • ​占用磁盘空间:​​ 每个索引都是一棵 B+Tree,需要额外存储空间。

      • ​降低写入速度:​INSERT, UPDATE, DELETE 操作发生时,不仅要修改数据,​​还需要维护所有相关的索引树结构​​(插入、删除索引项)。表上的索引越多,写入操作的成本就越高,速度就越慢。

      • ​优化器选择困难:​​ 索引太多可能让优化器在选择执行路径时花费更多时间,甚至偶尔选择不最优的路径。

      • ​冗余索引:​​ 两个索引有相同的前缀列,可能其中一个就是冗余的。

10、你有做过分库分表吗?

11、mybaits里面,#和$的区别?

#{}:SQL 预编译阶段被替换为 占位符?

#{}:SQL 解析阶段直接替换为参数值,SQL拼接,有SQL注入的风险

12、为什么MyBatis要设计两种占位符?

MyBatis设计两种占位符是为了兼顾安全性和灵活性:

  1. #{} 通过预编译机制从根本上防止SQL注入,适用于绝大多数值传递场景;

  2. ${} 提供字符串替换能力,用于解决动态SQL的特殊需求(如动态表名);

  3. 这种设计体现了MyBatis"约定优于配置"的理念:默认安全(#{}),在确有必要时开放风险能力(${});

  4. 实际开发中我们应该遵循"能用#{}就不用"的原则,对于必须使用{}的场景要严格进行参数校验。

13、​索引下推(Index Condition Pushdown,ICP)是什么?

简单来说就是没有ICP的时候,存储引擎只按索引找数据,不管WHERE条件,把所有可能匹配的数据都返回给Server层,导致大量无效数据传输和计算。有ICP的话,​​存储引擎在查索引时,顺便用WHERE条件过滤​​,只返回真正匹配的数据。

举个具体例子​​:

SELECT * FROM users WHERE name LIKE '张%' AND age = 20;

假设有联合索引 (name, age)

​​无ICP(传统方式)​​

  1. 存储引擎通过索引找到所有 name LIKE '张%' 的记录(比如100条)。

  2. 把这100条记录全部回表,读取完整数据返回给Server层。

  3. Server层再根据 age = 20 过滤,最终得到10条结果。
    ​问题​​:白白回表了90条无效数据!

​​有ICP(下推优化)​​

  1. 存储引擎查索引时,​​发现age也在索引中​​,于是直接检查 name LIKE '张%' AND age = 20

  2. 只对符合条件的10条记录回表,返回给Server层。
    ​优势​​:减少90%的回表操作!

总结:

  • ​“下推”​​ = ​​把过滤条件从Server层推到存储引擎层执行​​。

  • ​目的​​:减少数据传输和计算量,提升查询速度。

  • ​触发条件​​:WHERE条件中的字段必须属于当前使用的索引


数据库相关的面试题
https://www.orioncoder.cn/archives/c888UMZS
作者
Orion
发布于
更新于
许可