面试|Mysql八股

文章目录

sql基础

nosql和sql的区别

面试官期待的答案:

SQL(关系型数据库):数据以表格形式存储,有固定 schema,支持 ACID 事务,通过 SQL 语言查询,适合结构化数据和复杂关联查询,代表有 MySQL、PostgreSQL。

NoSQL(非关系型数据库):无固定 schema,数据模型多样(KV、文档、列族、图),通常牺牲部分一致性换取高可用和水平扩展能力,代表有 Redis(KV)、MongoDB(文档)、HBase(列族)、Neo4j(图)。

选型原则:数据结构固定、需要事务和复杂查询 → SQL;数据结构灵活、需要高并发读写或海量存储 → NoSQL。实际生产中两者往往配合使用,比如 MySQL 存业务数据,Redis 做缓存。

数据库三大范式

面试官期待的答案:

  • 第一范式(1NF):每列的值必须是原子的,不可再分。比如"地址"字段不能同时存省市区,应拆开;
  • 第二范式(2NF):在满足 1NF 的基础上,非主键列必须完全依赖于主键,不能只依赖主键的一部分(消除部分依赖)。主要针对联合主键的情况;
  • 第三范式(3NF):在满足 2NF 的基础上,非主键列之间不能存在传递依赖。比如订单表里有 user_id 和 user_name,user_name 依赖 user_id 而非主键,应拆到用户表。

实际开发中不会严格遵守三范式,适当反范式(冗余字段)可以减少 JOIN,提升查询性能。

mysql怎么连表查询

面试官期待的答案:

MySQL 支持以下几种 JOIN:

  • INNER JOIN:取两表的交集,只返回两边都能匹配的行;
  • LEFT JOIN:返回左表所有行,右表没有匹配的补 NULL;
  • RIGHT JOIN:返回右表所有行,左表没有匹配的补 NULL;
  • CROSS JOIN:笛卡尔积,一般不用;
  • 子查询:INEXISTS、派生表等。

性能注意:JOIN 时要确保 ON 条件的列有索引;小表驱动大表(让行数少的表作为驱动表);避免 SELECT *,只取需要的列。

mysql如何避免重复插入数据

面试官期待的答案:

有三种常用方式:

  1. INSERT IGNORE INTO:如果主键或唯一键冲突,忽略本次插入,不报错;
  2. INSERT ... ON DUPLICATE KEY UPDATE:冲突时执行 UPDATE,可以更新指定字段;
  3. REPLACE INTO:冲突时先删除旧行再插入新行(会触发 DELETE + INSERT,自增 ID 会变)。

最推荐 ON DUPLICATE KEY UPDATE,语义最清晰,也最灵活。前提是表上有唯一索引或主键约束。

char和varchar有什么区别

面试官期待的答案:

  • CHAR(n):定长,不足 n 个字符时用空格补齐,存储时固定占 n 个字符的空间,读取时自动去掉尾部空格。适合长度固定的字段,如手机号、MD5 值;
  • VARCHAR(n):变长,实际存多少字符就占多少空间,另外需要 1-2 字节记录长度。适合长度不固定的字段,如用户名、地址。

性能上:CHAR 读写速度略快(不需要计算长度),但空间利用率低;VARCHAR 空间利用率高,但有额外的长度字节开销。

varchar后面代表字节还是字符

面试官期待的答案:
VARCHAR(n) 中的 n 代表字符数 ,不是字节数。在 utf8mb4 字符集下,一个中文字符占 3-4 个字节,但 VARCHAR(100) 表示最多存 100 个字符(不管是中文还是英文)。

实际存储的字节数 = 字符数 × 每个字符的字节数,所以 VARCHAR(100) 在 utf8mb4 下最多占 400 字节。

int(1)、int(10)在mysql中有什么不同

面试官期待的答案:

没有本质区别。INT(1)INT(10) 存储的数据范围完全相同(都是 4 字节,-2^31 到 2^31-1),括号里的数字只是显示宽度 ,配合 ZEROFILL 使用时才有意义(不足位数时用 0 填充)。

MySQL 8.0.17 之后已经废弃了整数类型的显示宽度,建议直接用 INT 不加括号。

text数据类型可以无限大吗

面试官期待的答案:

不能,TEXT 有大小限制:

  • TINYTEXT:最大 255 字节;
  • TEXT:最大 65,535 字节(约 64KB);
  • MEDIUMTEXT:最大 16,777,215 字节(约 16MB);
  • LONGTEXT:最大 4,294,967,295 字节(约 4GB)。

另外,TEXT 字段不能有默认值,不能作为索引(除非用前缀索引),且 TEXT 数据通常存储在行外(溢出页),会增加 I/O 开销。

ip地址如何在数据库里存储

面试官期待的答案:

推荐用 INT UNSIGNED 存储,而不是 VARCHAR(15)

MySQL 提供了内置函数互转:

  • INET_ATON('192.168.1.1') → 将 IP 转为整数(3232235777);
  • INET_NTOA(3232235777) → 将整数转回 IP 字符串。

好处:INT 只占 4 字节,VARCHAR(15) 最多占 15 字节;整数比较和索引效率更高;范围查询(如查某个网段)也更方便。IPv6 则用 VARBINARY(16) 或两个 BIGINT 存储。

什么是外键约束

面试官期待的答案:

外键约束(FOREIGN KEY)用于保证两张表之间数据的引用完整性。子表的外键列值必须在父表的主键(或唯一键)中存在,或者为 NULL。

比如订单表的 user_id 是外键,引用用户表的 id,这样就不会出现订单关联了一个不存在的用户。

实际生产中,互联网公司通常不使用数据库外键,原因是:外键会降低写入性能(每次写子表都要检查父表)、不利于分库分表、业务逻辑变更时 schema 修改麻烦。改为在应用层保证数据一致性。

mysql的关键字in和exist有什么区别

面试官期待的答案:

  • IN:先执行子查询,得到结果集,再用外层查询逐行匹配。适合子查询结果集小的情况;
  • EXISTS:对外层查询的每一行,执行一次子查询,只要子查询返回至少一行就为 true。适合外层查询结果集小、子查询表大的情况。

性能规律:外大内小用 EXISTS,外小内大用 IN。现代 MySQL 优化器对两者的优化已经比较接近,但理解原理有助于在极端情况下做出正确选择。

另外,IN 不能处理 NULL(NULL IN (1,2,NULL) 结果是 NULL 而非 true),EXISTS 不受 NULL 影响。

说一下mysql的基本函数

面试官期待的答案:

常用函数分几类:

  • 字符串:CONCATSUBSTRINGLENGTHTRIMUPPER/LOWERREPLACELIKE
  • 数值:ABSCEIL/FLOORROUNDMODRAND
  • 日期时间:NOW()DATE_FORMATDATEDIFFDATE_ADD/DATE_SUBYEAR/MONTH/DAY
  • 聚合:COUNTSUMAVGMAX/MINGROUP_CONCAT
  • 条件:IF(expr, true_val, false_val)IFNULL(val, default)CASE WHEN ... THEN ... END
  • 类型转换:CASTCONVERT

注意:在 WHERE 条件中对索引列使用函数会导致索引失效,应尽量避免。

sql查询语句的执行顺序是什么样的

面试官期待的答案:

SQL 的书写顺序和执行顺序不同,执行顺序如下:

  1. FROM / JOIN:确定数据来源,生成笛卡尔积;
  2. ON:JOIN 条件过滤;
  3. WHERE:行级过滤(不能用聚合函数);
  4. GROUP BY:分组;
  5. HAVING:对分组结果过滤(可以用聚合函数);
  6. SELECT:选择列,计算表达式;
  7. DISTINCT:去重;
  8. ORDER BY:排序;
  9. LIMIT:限制返回行数。

理解执行顺序有助于写出正确的 SQL,比如 WHERE 里不能用 SELECT 中定义的别名(因为 WHERE 先于 SELECT 执行),但 ORDER BY 可以用别名。

存储引擎

执行一条sql请求的过程是什么

面试官期待的答案:

一条 SELECT 语句的完整执行流程:

  1. 连接层:客户端与 MySQL 建立 TCP 连接,完成身份认证,从线程池分配连接线程;
  2. 解析器:词法分析(识别关键字、表名、列名)+ 语法分析,生成 AST(抽象语法树),检查 SQL 语法是否合法;
  3. 优化器:基于代价模型选择最优执行计划,决定用哪个索引、JOIN 的顺序等;
  4. 执行器:检查用户权限,调用存储引擎 API,按执行计划逐步获取数据;
  5. 存储引擎:InnoDB 先查 Buffer Pool(内存缓存),命中直接返回,未命中从磁盘读取数据页到 Buffer Pool 再返回;
  6. 结果集返回给客户端。

注意:MySQL 8.0 已移除查询缓存(Query Cache),因为缓存命中率低且维护开销大。

介绍下mysql的引擎

面试官期待的答案:

MySQL 的存储引擎是可插拔的,常见的有:

  • InnoDB(默认):支持事务(ACID)、行级锁、外键、崩溃恢复(redo log),适合 OLTP 高并发读写场景;
  • MyISAM:不支持事务,只有表锁,读性能好,适合大量读少量写的场景(现在基本不用了);
  • Memory:数据存在内存中,速度极快,但重启后数据丢失,适合临时表或缓存;
  • Archive:只支持 INSERT 和 SELECT,压缩比高,适合归档日志数据;
  • CSV:数据以 CSV 文件存储,便于导入导出。

生产环境几乎都用 InnoDB,其他引擎了解即可。

mysql为什么使用innodb作为默认引擎

面试官期待的答案:

InnoDB 从 MySQL 5.5 开始成为默认引擎,原因:

  1. 支持事务:ACID 特性,保证数据一致性,这是大多数业务场景的基本需求;
  2. 行级锁:并发写入时只锁定相关行,而不是整张表,并发性能远优于 MyISAM 的表锁;
  3. 崩溃恢复:通过 redo log 实现 WAL(预写日志),宕机后可以自动恢复,数据不丢失;
  4. MVCC:多版本并发控制,读不加锁,读写互不阻塞,高并发下性能好;
  5. 外键支持:可以在数据库层面保证引用完整性(虽然实际很少用)。

mysql中innodb和myisam的区别

面试官期待的答案:

对比项 InnoDB MyISAM
事务 支持(ACID) 不支持
锁粒度 行锁 表锁
外键 支持 不支持
崩溃恢复 支持(redo log) 不支持
count(*) 需要全表扫描 O(1),单独存储总行数
主键 必须有(聚簇索引) 可以没有
适用场景 OLTP 高并发读写 大量读、极少写(现已基本淘汰)

核心区别:InnoDB 支持事务和行锁,适合高并发写入;MyISAM 不支持事务,只有表锁,并发写入性能差。

mysql数据管理里面,数据文件大体分为哪几种数据文件

面试官期待的答案:

InnoDB 主要的数据文件:

  • .ibd 文件 :每张表独立的表空间文件(开启 innodb_file_per_table 后),存储表的数据和索引;
  • ibdata1(系统表空间):存储数据字典、undo log(旧版本)、double write buffer 等;
  • ib_logfile0 / ib_logfile1:redo log 文件,循环写入;
  • binlog 文件mysql-bin.000001 等):Server 层的二进制日志,用于主从复制和数据恢复;
  • relay log:从库的中继日志,存储从主库接收到的 binlog;
  • .frm 文件 (MySQL 8.0 前):表结构定义文件,8.0 后合并到 .ibd 中。

索引

索引是什么,有什么好处

面试官期待的答案:

索引是数据库为了加速查询而建立的额外数据结构,类似书的目录。InnoDB 默认使用 B+ 树实现索引。

好处:大幅减少查询时扫描的数据量,将全表扫描(O(n))变为 B+ 树查找(O(log n)),对于千万级数据表,查询速度可以提升几个数量级;同时也能加速 ORDER BY 和 GROUP BY 操作(利用索引的有序性避免额外排序)。

代价:索引需要额外的存储空间;每次 INSERT/UPDATE/DELETE 都需要同步维护索引,会降低写入性能。所以索引不是越多越好,要根据查询模式合理建立。

索引的分类是什么

面试官期待的答案:

按数据结构:B+ 树索引(最常用)、哈希索引(Memory 引擎)、全文索引(FULLTEXT);

按物理存储:

  • 聚簇索引:数据按主键顺序存储,叶子节点存完整行数据,一张表只有一个;
  • 非聚簇索引(二级索引):叶子节点存索引列值 + 主键值,查完整行需要回表;

按字段数量:单列索引、联合索引(复合索引);

按约束:主键索引(PRIMARY KEY)、唯一索引(UNIQUE)、普通索引(INDEX)、前缀索引。

mysql聚簇索引和非聚簇索引的区别是什么

面试官期待的答案:
聚簇索引:InnoDB 中数据行按主键顺序物理存储,B+ 树的叶子节点直接存储完整的行数据。找到索引就找到了数据,不需要额外 I/O。一张表只能有一个聚簇索引(主键)。

非聚簇索引(二级索引) :叶子节点存储的是索引列的值 + 对应的主键值,不存完整行数据。查询时先通过二级索引找到主键,再用主键去聚簇索引查完整行,这个过程叫回表

核心区别:聚簇索引叶子节点存数据,非聚簇索引叶子节点存主键。这也是为什么 InnoDB 的主键查询比二级索引查询快(少一次回表)。

如果聚簇索引更新的数据更新,它的存储要不要变化

面试官期待的答案:

要看更新的是什么字段:

  • 如果更新的是非主键字段:只需要更新对应数据页中的行数据,聚簇索引的 B+ 树结构不变(主键没变,行的位置不变);
  • 如果更新的是主键字段:InnoDB 会先删除旧主键对应的行,再插入新主键的行。由于数据按主键顺序存储,主键变化意味着行在 B+ 树中的位置变化,可能触发页分裂,代价很高。

这也是为什么主键应该选择不会变化的字段,并且推荐使用自增 ID 作为主键。

mysql主键是聚簇索引吗

面试官期待的答案:

在 InnoDB 中,主键就是聚簇索引。如果没有显式定义主键,InnoDB 会按以下顺序选择:

  1. 选择第一个不含 NULL 的唯一索引作为聚簇索引;
  2. 如果没有,InnoDB 会自动生成一个隐藏的 6 字节 row_id 作为聚簇索引。

所以 InnoDB 的表一定有聚簇索引,只是不一定是用户定义的主键。MyISAM 没有聚簇索引,数据和索引分开存储。

什么字段适合当做主键

面试官期待的答案:

好的主键应满足:

  1. 自增或趋势递增:保证新数据插入到 B+ 树的末尾,避免页分裂,写入性能好;
  2. 数据类型小:主键会被所有二级索引引用,主键越小,二级索引占用空间越小;
  3. 不会变化:主键更新代价极高;
  4. 唯一且非空:这是主键的基本要求。

实践:单库用自增 BIGINT;分布式场景用雪花算法(Snowflake)生成的趋势递增 64 位整数,兼顾全局唯一和顺序插入性能。

性别字段能加索引吗,为什么

面试官期待的答案:

通常不建议单独给性别字段加索引。原因是性别字段区分度极低(只有男/女两个值),索引的选择性(Selectivity = 不同值数量 / 总行数)接近 0,查询时优化器可能判断走索引不如全表扫描(因为回表代价高,而性别索引过滤掉的行很少)。

但如果业务场景是 WHERE gender = 'female' AND age > 25,可以建联合索引 (gender, age),把区分度高的 age 放后面,利用联合索引的过滤能力。

表中十个字段,主键用自增id还是uuid,为什么

面试官期待的答案:

推荐用自增 ID,原因:

  1. 插入性能:自增 ID 是顺序递增的,新行总是插入 B+ 树的最右侧,不会触发页分裂;UUID 是随机的,插入位置随机,频繁触发页分裂,写入性能差;
  2. 存储空间:BIGINT 8 字节,UUID 字符串 36 字节,主键被所有二级索引引用,空间差异会被放大;
  3. 查询性能:整数比较比字符串比较快。

UUID 的优势是全局唯一,适合分布式场景。分布式场景推荐用雪花算法(Snowflake),既全局唯一又趋势递增,兼顾两者优点。

为什么自增id快一些,uuid不快吗,它在b+树里面存储是有序的吗

面试官期待的答案:

UUID 在 B+ 树里存储是无序的。UUID 是随机生成的(v4 版本),每次插入的主键值在整个 ID 空间中是随机分布的,不是递增的。

B+ 树按主键有序存储数据,插入一个随机 UUID 时,需要找到它在 B+ 树中的正确位置(可能在中间某个叶子节点),如果该页已满就要页分裂(将一个页拆成两个页,并更新父节点),这个操作代价很高,还会产生大量碎片。

自增 ID 每次都是最大值,直接追加到最后一个叶子节点,不需要页分裂(除非最后一页满了,但这是顺序扩展,代价小得多)。

mysql中的索引是怎么实现的

面试官期待的答案:

InnoDB 使用 B+ 树实现索引。B+ 树是一种多路平衡搜索树:

  • 内节点:只存储 key(索引列的值),不存数据,一个内节点可以存很多 key,使得树高很低(千万级数据树高约 3-4 层);
  • 叶子节点:存储完整数据(聚簇索引)或主键值(二级索引),叶子节点之间通过双向链表相连,支持高效范围查询;
  • 每次查询从根节点出发,通过 key 比较逐层向下,最多 3-4 次磁盘 I/O 就能找到目标数据。

查询数据时,到了b+树的叶子节点,之后的查找数据是如何做的

面试官期待的答案:

到达叶子节点后:

  • 如果是聚簇索引查询:叶子节点直接存储完整行数据,找到叶子节点就找到了数据,直接返回;
  • 如果是二级索引 查询:叶子节点存储的是索引列值 + 主键值,拿到主键后还需要回表,用主键再去聚簇索引的 B+ 树中查找一次,才能得到完整行数据;
  • 如果是覆盖索引:SELECT 的列都在二级索引中,不需要回表,直接从叶子节点返回。

对于范围查询,找到起始叶子节点后,沿着叶子节点的双向链表顺序遍历,直到不满足条件为止。

b+树的特性是什么

面试官期待的答案:

  1. 所有数据都存储在叶子节点,内节点只存 key 用于导航;
  2. 叶子节点之间通过双向链表相连,支持高效的范围查询和顺序遍历;
  3. 所有叶子节点在同一层(平衡树),查询任意数据的 I/O 次数相同(稳定的 O(log n));
  4. 内节点可以存储大量 key(因为不存数据),树高很低,通常 3-4 层就能支持千万级数据;
  5. 数据按 key 有序存储,天然支持排序和范围查询。

b+树和b树的区别

面试官期待的答案:

对比 B 树 B+ 树
数据存储位置 内节点和叶子节点都存数据 只有叶子节点存数据
内节点容量 存 KV,容量小,树更高 只存 key,容量大,树更矮
范围查询 需要中序遍历,效率低 叶子节点链表,顺序遍历,效率高
查询稳定性 不稳定(内节点命中可以提前返回) 稳定(都要到叶子节点)

B+ 树更适合数据库索引,主要因为:内节点不存数据使得树更矮(减少磁盘 I/O),叶子链表使范围查询高效。

b+树的好处是什么

面试官期待的答案:

  1. 树高低,I/O 少:内节点只存 key,一个 16KB 的页可以存数百个 key,千万级数据树高只有 3-4 层,查询只需 3-4 次磁盘 I/O;
  2. 范围查询高效:叶子节点双向链表,找到起始位置后顺序遍历即可,不需要回溯;
  3. 查询稳定:所有查询都要到叶子节点,时间复杂度稳定;
  4. 顺序访问友好:叶子节点有序,ORDER BY 可以利用索引避免额外排序;
  5. 适合磁盘存储:每个节点对应一个磁盘页(16KB),充分利用磁盘预读。

b+树的叶子节点链表是单向还是双向的

面试官期待的答案:

InnoDB 的 B+ 树叶子节点链表是双向链表 。双向链表支持正向和反向遍历,这样 ORDER BY id ASCORDER BY id DESC 都能高效利用索引,不需要额外排序。

mysql为什么使用b+树,和其他数据结构对比其优点是什么

面试官期待的答案:

  • vs 哈希表:哈希表等值查询 O(1) 很快,但不支持范围查询、排序、LIKE 前缀匹配,而这些是数据库的常见需求;
  • vs 红黑树(二叉树):红黑树树高 O(log₂n),百万数据树高约 20 层,每层一次磁盘 I/O,太慢;B+ 树是多路树,树高 O(log_m n),m 很大,树高只有 3-4 层;
  • vs B 树:B 树内节点存数据,容量小,树更高;范围查询需要中序遍历,效率低;B+ 树内节点只存 key,树更矮,叶子链表支持高效范围查询;
  • vs 跳表:跳表也支持范围查询,但内存占用大,不适合磁盘存储(局部性差);B+ 树每个节点对应一个磁盘页,充分利用磁盘预读。

为什么mysql不用跳表

面试官期待的答案:

跳表是 Redis 等内存数据库的常用数据结构,但不适合 MySQL 的磁盘存储场景:

  1. 磁盘局部性差:跳表的节点在内存中随机分布,访问相邻节点可能触发多次随机磁盘 I/O;B+ 树的节点是磁盘页,相邻节点物理上连续,磁盘预读效果好;
  2. 空间利用率低:跳表每个节点需要多个指针(多层索引),内存开销大;
  3. 树高不可控:跳表层数是随机的,最坏情况下性能不稳定;B+ 树是严格平衡的,查询时间稳定。

Redis 的 ZSet 用跳表是因为数据在内存中,随机访问代价小,且跳表实现简单、范围查询方便。

联合索引的实现原理

面试官期待的答案:

联合索引(复合索引)是对多个列共同建立的 B+ 树索引。B+ 树的 key 是多个列值的组合,排序规则是:先按第一列排序,第一列相同再按第二列排序,以此类推。

比如联合索引 (a, b, c),B+ 树中的数据按 (a, b, c) 的字典序排列。这就是最左前缀原则的来源:查询时必须从最左列开始,才能利用 B+ 树的有序性定位数据;如果跳过了某一列,后面的列在 B+ 树中就不是有序的了,无法利用索引。

创建联合索引的时候需要注意什么

面试官期待的答案:

  1. 最左前缀原则:查询条件必须包含索引的最左列,否则索引失效;
  2. 区分度高的列放前面:提高索引的过滤效率;
  3. 等值条件列放前面,范围条件列放后面 :范围查询(><BETWEEN)之后的列无法使用索引;
  4. 考虑覆盖索引:将 SELECT 中常用的列也加入联合索引,避免回表;
  5. 避免冗余索引(a)(a, b) 同时存在时,(a) 是冗余的,(a, b) 已经能覆盖 (a) 的查询;
  6. 控制索引列数:联合索引列数不宜过多,一般不超过 5 列。

联合索引abc,现在有执行语句a=xxx and c<xxx,索引怎么走

面试官期待的答案:

索引 (a, b, c),查询 WHERE a = xxx AND c < xxx

会用到索引,但只能用到 a 部分。原因:a 是等值条件,可以利用索引定位到 a = xxx 的范围;但 b 没有出现在查询条件中,跳过了 b,c 在 B+ 树中相对于 a 的范围内并不是有序的,所以 c < xxx 无法利用索引过滤,只能在 a = xxx 的结果集上做全扫描过滤。

EXPLAIN 中 key_len 只会显示 a 列的长度,Extra 可能显示 Using index condition(ICP 在索引层过滤 c)。

联合索引abc,where a=2 and c=1,能用到联合索引吗

面试官期待的答案:

能用到,但只能用到 a 部分。MySQL 优化器会利用 a = 2 走联合索引定位到 a = 2 的范围,然后对这个范围内的数据过滤 c = 1(通过 ICP 在索引层过滤,或者回表后过滤)。b 列被跳过,c 列无法利用索引的有序性,只能作为过滤条件。

如果查询频繁,可以考虑建 (a, c) 联合索引,或者调整为 (a, c, b) 的顺序。

索引失效有哪些情况

面试官期待的答案:

常见的索引失效场景:

  1. 对索引列做函数运算WHERE YEAR(create_time) = 2024,应改为范围查询;
  2. 隐式类型转换WHERE phone = 13800138000(phone 是 varchar),字符串不加引号触发类型转换;
  3. LIKE 前导通配符WHERE name LIKE '%Alice',前缀是 % 时索引失效;
  4. 联合索引违反最左前缀:跳过了最左列;
  5. 使用 OR 且有一侧无索引WHERE a = 1 OR b = 2,若 b 没有索引,整个查询可能全表扫描;
  6. NOT IN / !=:通常导致全表扫描;
  7. 数据量太少:优化器认为全表扫描比走索引更快(回表代价 > 全表扫描代价);
  8. 索引列参与计算WHERE id + 1 = 10,应改为 WHERE id = 9

什么情况下会用到回表查询

面试官期待的答案:

当查询使用了二级索引,但 SELECT 的列不能全部从二级索引中获取时,就需要回表。

具体场景:通过二级索引找到主键后,还需要查询索引列之外的其他字段(如 SELECT * 或查询了不在索引中的列),此时需要用主键再去聚簇索引查一次完整行数据。

避免回表的方法:建立覆盖索引,将 SELECT 中需要的列都包含在联合索引中,这样直接从索引叶子节点就能获取所有需要的数据。

了解索引下推吗

面试官期待的答案:

索引下推(Index Condition Pushdown,ICP)是 MySQL 5.6 引入的优化,默认开启。

原理:在使用联合索引时,将部分 WHERE 条件的过滤下推到存储引擎层(索引层)执行,而不是在 Server 层过滤。

举例:联合索引 (age, name),查询 WHERE age > 20 AND name LIKE '%li%'。没有 ICP 时,存储引擎按 age > 20 找到所有行并回表,Server 层再过滤 name;有 ICP 时,存储引擎在索引层就判断 name LIKE '%li%',只对通过的行才回表,减少了回表次数。

EXPLAIN 中 Extra 显示 Using index condition 表示使用了 ICP。

什么是覆盖索引

面试官期待的答案:

覆盖索引是指查询所需的所有列都能从索引中直接获取,不需要回表查聚簇索引。

比如有联合索引 (name, age),执行 SELECT name, age FROM users WHERE name = 'Alice',所需的 name 和 age 都在索引的叶子节点中,不需要回表,这就是覆盖索引。

EXPLAIN 中 Extra 显示 Using index 表示使用了覆盖索引。覆盖索引是一种重要的性能优化手段,可以显著减少 I/O 次数。

如果一个列即是单列索引又是联合索引,单独查询该列的话走哪个

面试官期待的答案:

由 MySQL 优化器决定,优化器会基于代价估算选择最优索引。通常情况下:

  • 如果只查询该列(覆盖索引),优化器可能选择更小的索引(单列索引通常比联合索引小,扫描更快);
  • 如果需要回表,优化器会选择能过滤更多行的索引;
  • 可以用 EXPLAIN 查看实际走的索引,也可以用 FORCE INDEX 强制指定。

一般来说,单列索引是联合索引的冗余,可以删除单列索引,联合索引已经能覆盖单列查询的场景。

索引建好后再插入一条数据,索引会有哪些变化

面试官期待的答案:

插入一条数据时,所有相关索引都需要同步更新:

  1. 聚簇索引 :按主键找到对应位置插入新行,如果该叶子页已满,触发页分裂(将页一分为二,并更新父节点);
  2. 二级索引:同样在对应的 B+ 树中插入新的索引项,可能也触发页分裂;
  3. Change Buffer 优化:对于非唯一二级索引,如果目标索引页不在 Buffer Pool 中,InnoDB 会将修改缓存在 Change Buffer,等该页被读入时再合并,避免立即触发随机 I/O。

这也是为什么索引越多,写入性能越差。

索引字段是不是建的越多越好

面试官期待的答案:

不是。索引有以下代价:

  1. 写入性能下降:每次 INSERT/UPDATE/DELETE 都需要维护所有索引,索引越多写入越慢;
  2. 存储空间增加:每个索引都需要额外的磁盘空间;
  3. 优化器负担增加:索引过多时,优化器需要评估更多执行计划,可能选错索引。

建索引的原则:只给频繁出现在 WHERE、JOIN ON、ORDER BY、GROUP BY 中的列建索引,且要考虑区分度。一般一张表的索引不超过 5-6 个。

如果有一个字段是status,值为0或1,适合建立索引吗

面试官期待的答案:

单独建索引通常不适合,原因和性别字段类似:status 只有 0 和 1 两个值,区分度极低(选择性约 50%),走索引后还需要大量回表,优化器很可能判断全表扫描更快。

但有两种情况可以考虑:

  1. 数据严重倾斜 :比如 status=1 的只有 0.1%,查询 WHERE status = 1 时索引能过滤掉 99.9% 的数据,此时索引有效;
  2. 联合索引 :将 status 与其他高区分度字段组合,如 (user_id, status),利用联合索引的整体过滤能力。

索引的优缺点

面试官期待的答案:

优点:

  • 大幅加速查询,将 O(n) 全表扫描变为 O(log n) 的 B+ 树查找;
  • 加速 ORDER BY、GROUP BY(利用索引有序性);
  • 覆盖索引可以避免回表,进一步提升性能。

缺点:

  • 占用额外存储空间;
  • 降低写入性能(INSERT/UPDATE/DELETE 需要维护索引);
  • 索引过多时优化器可能选错索引;
  • 需要定期维护(重建碎片化的索引)。

怎么决定建立哪些索引

面试官期待的答案:

  1. 分析查询模式:找出频繁执行的慢查询(慢查询日志),用 EXPLAIN 分析执行计划;
  2. WHERE 条件列:频繁出现在 WHERE 中的列,优先考虑;
  3. JOIN 关联列:JOIN ON 的列必须有索引,否则会触发全表扫描;
  4. ORDER BY / GROUP BY 列:加入索引可以避免 filesort;
  5. 区分度 :选择区分度高的列,SELECT COUNT(DISTINCT col) / COUNT(*) 越接近 1 越好;
  6. 覆盖索引:将 SELECT 中常用的列加入联合索引,避免回表;
  7. 控制数量 :索引不超过 5-6 个,删除长期未使用的索引(sys.schema_unused_indexes)。

索引优化详细讲一下

面试官期待的答案:

索引优化的几个核心方向:

  1. 避免索引失效:不对索引列做函数运算、类型转换,LIKE 不用前导 %,OR 两侧都要有索引;
  2. 利用覆盖索引:将 SELECT 的列加入联合索引,避免回表;
  3. 联合索引列顺序:等值条件列放前面,范围条件列放后面,区分度高的列放前面;
  4. 前缀索引:对长字符串(如 URL、邮箱)只取前几个字符建索引,节省空间;
  5. 删除冗余索引(a)(a, b) 同时存在时,(a) 是冗余的;
  6. 强制索引 :优化器选错索引时,用 FORCE INDEX(idx_name) 强制指定;
  7. 定期重建索引 :大量删除后索引碎片化,用 OPTIMIZE TABLEALTER TABLE ... ENGINE=InnoDB 重建。

什么是前缀索引

面试官期待的答案:

前缀索引是对字符串列只取前 n 个字符建立索引,而不是对整个字符串建索引。

sql 复制代码
CREATE INDEX idx_email ON users(email(10)); -- 只取邮箱前10个字符

好处:减少索引占用的存储空间,提升索引的 I/O 效率。

缺点:无法使用覆盖索引 (因为索引中只存了前缀,不是完整值,查询时仍需回表验证完整值);前缀长度选择需要权衡区分度和空间,可以用 SELECT COUNT(DISTINCT LEFT(email, n)) / COUNT(*) 来评估不同前缀长度的区分度。

事务

事务的特性是什么

面试官期待的答案:

事务的四个特性(ACID):

  • 原子性(Atomicity) :事务中的所有操作要么全部成功提交,要么全部回滚,不存在部分执行的情况。由 undo log 实现;
  • 一致性(Consistency):事务执行前后,数据库从一个一致状态变为另一个一致状态(满足业务约束,如转账前后总金额不变)。由其他三个特性共同保证;
  • 隔离性(Isolation) :并发执行的事务互不干扰,一个事务的中间状态对其他事务不可见。由锁 + MVCC 实现;
  • 持久性(Durability) :事务提交后,数据永久保存,即使系统崩溃也不丢失。由 redo log 实现。

mysql可能出现什么和并发相关的问题

面试官期待的答案:

并发事务可能导致三类问题:

  • 脏读:事务 A 读到了事务 B 未提交的数据,如果 B 回滚,A 读到的就是无效数据;
  • 不可重复读:事务 A 在同一事务内两次读取同一行,结果不同(事务 B 在两次读之间提交了修改);
  • 幻读:事务 A 在同一事务内两次执行相同范围查询,返回的行数不同(事务 B 在两次查询之间插入或删除了行)。

不可重复读针对的是同一行数据被修改,幻读针对的是行数变化(插入/删除)。

哪些场景不适合脏读,举个例子

面试官期待的答案:

几乎所有业务场景都不适合脏读,典型例子:

银行转账:事务 A 从账户 X 转 100 元到账户 Y,先扣减 X 的余额(未提交),此时事务 B 查询 X 的余额,读到了扣减后的值(脏读)。如果事务 A 随后因为某种原因回滚,X 的余额恢复了,但事务 B 已经基于错误的余额做了后续操作(比如判断余额不足拒绝了某笔操作),导致业务逻辑错误。

库存扣减:事务 A 扣减库存(未提交),事务 B 读到库存为 0 拒绝了用户下单,但事务 A 随后回滚,库存其实还有,导致用户无法下单的误判。

mysql是怎么解决并发问题的

面试官期待的答案:

MySQL InnoDB 通过两种机制解决并发问题:

  1. MVCC(多版本并发控制):解决读写并发问题。每行数据维护版本链(undo log),读操作通过 ReadView 读取快照版本,不加锁,读写互不阻塞。解决了脏读和不可重复读;

  2. 锁机制:解决写写并发问题。行锁(X 锁)保证同一行数据不会被并发修改;间隙锁(Gap Lock)和 Next-Key Lock 防止幻读(阻止在查询范围内插入新行);表锁用于 DDL 等场景。

两者配合:普通 SELECT 用 MVCC(快照读,不加锁),SELECT FOR UPDATE 用锁(当前读,加 X 锁)。

事务的隔离级别有哪些

面试官期待的答案:

隔离级别 脏读 不可重复读 幻读
READ UNCOMMITTED(读未提交) 可能 可能 可能
READ COMMITTED(读已提交) 不会 可能 可能
REPEATABLE READ(可重复读) 不会 不会 InnoDB 通过 Next-Key Lock 解决
SERIALIZABLE(串行化) 不会 不会 不会

隔离级别越高,并发性能越低。InnoDB 默认 REPEATABLE READ,通过 MVCC + Next-Key Lock 在 RR 级别下也能防止幻读,所以不需要用 SERIALIZABLE。

mysql的默认隔离级别是什么

面试官期待的答案:

MySQL InnoDB 的默认隔离级别是 REPEATABLE READ(可重复读)

选择 RR 而不是 RC 的历史原因:早期 MySQL 的 binlog 格式是 STATEMENT(记录 SQL 语句),RC 级别下可能导致主从数据不一致(同一条 SQL 在主从执行时读到的数据不同)。RR 级别配合 STATEMENT binlog 可以保证主从一致。现在 binlog 推荐用 ROW 格式,这个问题已经不存在了,但默认值保留了下来。

可重复读隔离级别下,A事务提交的数据,在B事务能看见吗

面试官期待的答案:

分两种情况:

  • 快照读(普通 SELECT) :看不见。RR 级别下,事务 B 在第一次执行 SELECT 时生成 ReadView,记录了当时所有活跃事务的 ID。如果事务 A 在 B 的 ReadView 生成之后才提交,A 的 trx_id 在 B 的 ReadView 的 m_ids 中(或 >= max_trx_id),对 B 不可见,B 整个事务内看到的都是同一份快照;

  • 当前读(SELECT FOR UPDATE / UPDATE / DELETE:能看见。当前读不走 MVCC 快照,直接读最新已提交的数据,所以 A 提交后 B 的当前读能看到 A 的修改。

这也是 RR 级别"可重复读"的含义:同一事务内多次快照读结果一致,不受其他事务提交影响。

举例子说明可重复读下的幻读问题

面试官期待的答案:

幻读场景(当前读下):

事务 A:SELECT * FROM orders WHERE amount > 100,返回 3 行;

事务 B:INSERT INTO orders VALUES (4, 200),并提交;

事务 A:再次执行 SELECT * FROM orders WHERE amount > 100 FOR UPDATE(当前读),返回 4 行。

事务 A 在同一事务内两次查询结果不同,多出了一行"幻影行",这就是幻读。

注意:普通 SELECT(快照读)在 RR 级别下不会出现幻读(MVCC 保证),幻读主要出现在当前读(FOR UPDATEUPDATEDELETE)场景下。

mysql设置了可重复读隔离级别后,怎么保证不发生幻读

面试官期待的答案:

InnoDB 通过两种方式防止幻读:

  1. 快照读(普通 SELECT):通过 MVCC,事务首次读时生成 ReadView,后续读取都基于这个快照,其他事务插入的新行 trx_id 大于 ReadView 的 max_trx_id,对当前事务不可见,自然不会出现幻读;

  2. 当前读(SELECT FOR UPDATE / UPDATE / DELETE) :通过 Next-Key Lock(行锁 + 间隙锁)。锁定查询范围内的所有行以及行之间的间隙,阻止其他事务在这个范围内插入新行,从而防止幻读。

两种机制配合,在 RR 级别下基本消除了幻读。

串行化隔离级别是通过什么实现的

面试官期待的答案:

SERIALIZABLE 级别下,InnoDB 将所有普通的 SELECT 语句自动转换为 SELECT ... LOCK IN SHARE MODE(加 S 锁),所有读操作都加共享锁。这样读写之间会互相阻塞,事务只能串行执行,完全避免了脏读、不可重复读和幻读,但并发性能最差。

实际生产中几乎不用 SERIALIZABLE,性能代价太高。

介绍下MVCC及其实现原理

面试官期待的答案:

MVCC(Multi-Version Concurrency Control,多版本并发控制)让读操作不加锁,通过读取历史版本数据来避免读写冲突,提升并发性能。

实现原理依赖三个组件:

  1. 隐藏字段 :每行数据有两个隐藏字段,trx_id(最近修改该行的事务 ID)和 roll_pointer(指向 undo log 的指针);

  2. undo log 版本链 :每次 UPDATE/DELETE 都会生成一条 undo log,记录旧版本数据,通过 roll_pointer 串成版本链,保存了该行的历史版本;

  3. ReadView(读视图) :事务执行快照读时生成,记录当前活跃(未提交)的事务 ID 列表(m_ids)、最小活跃事务 ID(min_trx_id)、下一个事务 ID(max_trx_id)。判断版本可见性:trx_id < min_trx_id → 已提交可见;trx_id >= max_trx_id → 不可见;trx_id 在 m_ids 中 → 活跃未提交不可见;否则可见。

RC 和 RR 的区别:RC 每次 SELECT 都生成新 ReadView;RR 只在第一次 SELECT 时生成,整个事务复用同一个 ReadView,保证可重复读。

一条update语句是不是原子性的,为什么

面试官期待的答案:

是原子性的。InnoDB 通过 undo log 保证原子性:执行 UPDATE 前,先将旧值写入 undo log;如果 UPDATE 执行过程中失败或事务回滚,InnoDB 用 undo log 中的旧值将数据恢复到修改前的状态,保证要么完全执行,要么完全不执行。

另外,单条 SQL 语句在 MySQL 中本身就是一个隐式事务(autocommit=1 时),执行完自动提交,失败自动回滚。

滥用事务或者一个事务里面有特别多的sql会有什么弊端

面试官期待的答案:

  1. 锁持有时间长:事务中的锁在事务提交前不会释放,大事务会长时间持有大量行锁,阻塞其他事务,降低并发性能,甚至导致死锁;
  2. undo log 膨胀:大事务产生大量 undo log,占用大量存储空间,影响 MVCC 的版本链查找性能;
  3. 主从延迟增大:大事务的 binlog 体积大,从库重放耗时长,加剧主从延迟;
  4. 回滚代价高:大事务回滚需要撤销大量操作,耗时很长;
  5. 连接占用时间长:长事务占用数据库连接,连接池资源紧张。

最佳实践:事务尽量短小,只包含必要的操作;避免在事务中做网络请求或耗时计算;大批量操作拆分成小批次事务。

mysql里面都有哪些锁

面试官期待的答案:

按粒度分:

  • 表锁:锁整张表,开销小,并发低。DDL 操作(ALTER TABLE)会加 MDL 表锁;MyISAM 使用表锁;
  • 行锁:InnoDB 支持,只锁定相关行,并发高;
  • 间隙锁(Gap Lock):锁定索引记录之间的间隙,防止幻读;
  • Next-Key Lock:行锁 + 间隙锁的组合,是 InnoDB 默认的行锁方式;
  • 意向锁(IS/IX):InnoDB 自动加,事务加行锁前先在表上加意向锁,让表锁请求快速判断是否有行锁存在。

按读写分:

  • 共享锁(S 锁):读锁,多个事务可以同时持有;
  • 排他锁(X 锁):写锁,持有期间其他事务不能读写。

按态度分:

  • 乐观锁:应用层实现,用 version 字段 CAS,适合读多写少;
  • 悲观锁SELECT FOR UPDATE,适合写多读少。

数据库的表锁和行锁有什么作用

面试官期待的答案:

  • 表锁:保护整张表的数据一致性。适用于 DDL 操作(ALTER TABLE 时加 MDL 锁,防止其他事务读写)、MyISAM 的写操作、以及 InnoDB 在无法使用行锁时的降级(如全表扫描的 UPDATE)。开销小但并发性差;
  • 行锁:只锁定需要操作的行,其他行可以并发访问,大幅提升并发性能。InnoDB 的行锁是加在索引上的,如果 WHERE 条件没有走索引,行锁会升级为表锁(锁定所有行)。

实际使用中要确保 UPDATE/DELETE 的 WHERE 条件走索引,避免行锁升级为表锁。

mysql两个线程的update语句同时处理一条数据会不会有阻塞

面试官期待的答案:

会阻塞。InnoDB 的 UPDATE 语句会对修改的行加排他锁(X 锁),X 锁与任何锁都不兼容。当线程 A 的 UPDATE 持有某行的 X 锁时,线程 B 的 UPDATE 尝试获取同一行的 X 锁,会被阻塞,直到线程 A 的事务提交或回滚释放锁。

这是 InnoDB 行锁的正常行为,保证了并发写入的数据一致性。

两条update语句处理一张表的不同的主键范围的记录,一个事务,会不会遇到阻塞,底层是为什么

面试官期待的答案:

如果两个事务操作的是不同的主键范围 ,且都走了主键索引,通常不会阻塞。InnoDB 的行锁是加在索引记录上的,不同主键范围的行锁互不影响,两个事务可以并发执行。

底层原因:InnoDB 行锁的粒度是索引记录,锁的是具体的索引项,不同主键对应不同的索引项,所以互不干扰。这也是行锁相比表锁并发性高的核心原因。

如果2个范围不是主键或者索引,还会阻塞吗

面试官期待的答案:

会阻塞,而且情况更严重。InnoDB 的行锁是加在索引上的,如果 WHERE 条件的列没有索引,InnoDB 无法精确定位到具体行,只能对全表所有行加锁(实际上退化为表锁)。

这样即使两个事务操作的是不同范围的数据,也会互相阻塞,因为都需要锁定整张表。

这也是为什么 UPDATE/DELETE 的 WHERE 条件一定要走索引,否则会严重影响并发性能。

日志

mysql中日志文件分为了哪几种

面试官期待的答案:

MySQL 主要有三种核心日志:

  • redo log(重做日志):InnoDB 引擎层,物理日志,记录数据页的修改,循环写,用于崩溃恢复,保证持久性;
  • undo log(回滚日志):InnoDB 引擎层,逻辑日志,记录数据修改前的旧值,用于事务回滚和 MVCC 版本链;
  • binlog(二进制日志):MySQL Server 层,逻辑日志,记录所有修改数据的操作,追加写,用于主从复制和数据恢复。

另外还有:错误日志(error log)、慢查询日志(slow query log)、通用查询日志(general log)、中继日志(relay log,从库使用)。

介绍一下binlog

面试官期待的答案:

binlog 是 MySQL Server 层的二进制日志,记录所有修改数据的 DDL 和 DML 操作(不记录 SELECT)。

主要用途:

  1. 主从复制:从库的 IO Thread 读取主库 binlog,写入 relay log,SQL Thread 重放 relay log 实现数据同步;
  2. 数据恢复:全量备份 + 增量 binlog,可以将数据恢复到任意时间点。

binlog 有三种格式:

  • STATEMENT :记录 SQL 语句,体积小,但某些函数(如 NOW())可能导致主从不一致;
  • ROW:记录每行数据的变化(前后镜像),精确但体积大,推荐生产使用;
  • MIXED:混合模式,简单语句用 STATEMENT,可能不一致的用 ROW。

binlog 是追加写,不会覆盖旧数据,可以通过 expire_logs_days 设置自动清理。

undolog日志的作用是什么

面试官期待的答案:

undo log 有两个核心作用:

  1. 事务回滚(原子性):每次 INSERT/UPDATE/DELETE 前,先将逆操作记录到 undo log(INSERT 记录 DELETE,UPDATE 记录旧值,DELETE 记录 INSERT)。事务回滚时,按 undo log 逆序执行,将数据恢复到修改前的状态;

  2. MVCC 版本链 :每行数据的 roll_pointer 指向 undo log,多个版本的 undo log 串成版本链。MVCC 读取时,根据 ReadView 沿版本链找到对应的历史版本,实现快照读。

undo log 存储在系统表空间或独立的 undo 表空间中,事务提交后不会立即删除(MVCC 可能还需要读取),由 purge 线程异步清理不再需要的旧版本。

有了undolog为什么还需要redolog

面试官期待的答案:

两者解决的是不同问题:

  • undo log 解决的是原子性:事务回滚时用旧值恢复数据;
  • redo log 解决的是持久性:事务提交后,即使宕机也不丢数据。

为什么需要 redo log:InnoDB 修改数据时,先修改 Buffer Pool 中的内存页(脏页),脏页是异步刷到磁盘的。如果事务提交后、脏页刷盘前宕机,内存中的修改就丢失了。redo log 采用 WAL(Write-Ahead Logging)策略,事务提交时先将修改写入 redo log 并刷盘(顺序写,速度快),宕机重启后通过 redo log 重做未刷盘的修改,保证数据不丢失。

简记:undo log 是"后悔药"(回滚),redo log 是"保险单"(崩溃恢复)。

redolog怎么保证持久性

面试官期待的答案:

redo log 通过 WAL(Write-Ahead Logging,预写日志) 机制保证持久性:

  1. 事务修改数据时,先将修改记录写入 redo log buffer(内存);
  2. 事务提交时,将 redo log buffer 中的内容 fsync 到磁盘(innodb_flush_log_at_trx_commit=1 时每次提交都刷盘);
  3. 数据页(脏页)异步刷到磁盘;
  4. 宕机重启时,InnoDB 检查 redo log,将已提交但未刷盘的修改重做一遍(前滚),将未提交的事务回滚(配合 undo log)。

关键:redo log 是顺序写(追加),比数据页的随机写快得多,这是 WAL 的性能优势所在。

能不能只用binlog而不用redolog

面试官期待的答案:

不能。两者的职责不同,不能互相替代:

  • binlog 不能用于崩溃恢复:binlog 是 Server 层的逻辑日志,记录的是 SQL 语句或行变化,不记录数据页的物理位置。宕机恢复时,无法知道哪些数据页已经刷盘、哪些没有,无法精确重做;
  • redo log 是物理日志:记录的是"第 X 号数据页,偏移量 Y,修改为 Z",可以精确地重做未刷盘的修改;
  • redo log 是循环写:不能用于主从复制和时间点恢复,这是 binlog 的职责。

两者配合,通过两阶段提交保证一致性,缺一不可。

介绍下binlog两阶段提交过程

面试官期待的答案:

两阶段提交(2PC)保证 redo log 和 binlog 的一致性,防止主从数据不一致或崩溃后数据丢失:

  1. Prepare 阶段 :InnoDB 将 redo log 写入磁盘,标记为 prepare 状态;
  2. 写 binlog:Server 层将 binlog 写入磁盘;
  3. Commit 阶段 :InnoDB 将 redo log 标记为 commit 状态,事务完成。

崩溃恢复逻辑:

  • 若 redo log 是 prepare 且 binlog 完整 → 提交事务(认为事务已成功);
  • 若 redo log 是 prepare 但 binlog 不完整 → 回滚事务;
  • 若 redo log 是 commit → 事务已完成,无需处理。

这样无论在哪个阶段崩溃,都能保证 redo log 和 binlog 的一致性。

update语句的具体执行过程是怎样的

面试官期待的答案:

UPDATE users SET name = 'Bob' WHERE id = 1 为例:

  1. 执行器通过存储引擎找到 id=1 的行(先查 Buffer Pool,未命中则从磁盘读入);
  2. 将旧值写入 undo log(用于回滚和 MVCC);
  3. 在 Buffer Pool 中修改数据页(内存操作,将 name 改为 'Bob'),标记为脏页;
  4. 将修改写入 redo log buffer
  5. 事务提交时:
    • redo log 写盘,标记 prepare;
    • binlog 写盘;
    • redo log 标记 commit;
  6. 后台线程异步将脏页刷到磁盘(checkpoint)。

mysql是如何保障数据不丢失的

面试官期待的答案:

MySQL 通过以下机制保障数据不丢失:

  1. WAL + redo log :事务提交时先将 redo log 刷盘(innodb_flush_log_at_trx_commit=1),宕机后通过 redo log 重做未刷盘的修改;
  2. 两阶段提交:保证 redo log 和 binlog 一致,防止主从数据不一致;
  3. 双写缓冲(Doublewrite Buffer):防止数据页部分写入(partial write)导致页损坏,先将脏页写到 doublewrite 区域备份,再写真正位置;
  4. sync_binlog=1:每次事务提交都将 binlog 刷盘,防止 binlog 丢失;
  5. 半同步复制:至少一个从库确认收到 binlog 后主库才提交,防止主库宕机后数据丢失。

生产环境"双一配置":innodb_flush_log_at_trx_commit=1 + sync_binlog=1,最高安全性。

redolog是在内存里吗

面试官期待的答案:

redo log 有两个部分:

  • redo log buffer:在内存中,事务执行过程中先写这里,速度快;
  • redo log 文件ib_logfile0ib_logfile1):在磁盘上,事务提交时从 buffer 刷到磁盘文件。

innodb_flush_log_at_trx_commit 参数控制刷盘时机:

  • 1:每次提交都 fsync,最安全;
  • 2:每次提交写 OS 缓存,每秒 fsync,OS 崩溃可能丢 1 秒数据;
  • 0:每秒写 buffer 并 fsync,MySQL 崩溃可能丢 1 秒数据。

为什么要写redolog而不是直接写到b+树里面

面试官期待的答案:

核心原因是顺序写 vs 随机写的性能差异:

  • 直接写 B+ 树(数据页):数据页在磁盘上是随机分布的,修改不同行需要随机 I/O,磁盘随机写性能很差(HDD 约 100 IOPS,SSD 也有限制);
  • 写 redo log:redo log 是顺序追加写,磁盘顺序写性能远高于随机写(HDD 顺序写可达 100MB/s+)。

WAL 策略:先顺序写 redo log(快),再异步批量将脏页刷到 B+ 树(慢但可以合并多次修改,减少随机 I/O 次数)。这样既保证了持久性,又提升了写入性能。

mysql两次写(double write buffer)了解吗,介绍下

面试官期待的答案:

Doublewrite Buffer 解决的是**部分页写入(partial write)**问题:InnoDB 的数据页是 16KB,操作系统的页是 4KB,写数据页时可能只写了一部分(比如写了 4KB)就宕机了,导致数据页损坏,redo log 也无法修复(redo log 需要一个完整的页作为基础)。

解决方案:

  1. 将脏页先写入内存中的 doublewrite buffer;
  2. 将 doublewrite buffer 中的数据顺序写到磁盘的 doublewrite 专用区域(系统表空间,顺序写,速度快);
  3. 再将脏页写到真正的数据文件位置(随机写);
  4. 宕机恢复时,如果数据文件中的页损坏,从 doublewrite 区域恢复完整页,再应用 redo log。

性能开销约 5-10%,默认开启,是数据安全的重要保障。

性能调优

mysql的explain有什么作用

面试官期待的答案:
EXPLAIN 用于分析 SQL 语句的执行计划,帮助排查慢查询和索引问题。关键字段:

  • type :访问类型,性能从好到差:const > eq_ref > ref > range > index > ALLALL 是全表扫描,需要优化;
  • key :实际使用的索引,NULL 表示没有用索引;
  • key_len:使用的索引长度,可以判断联合索引用了几列;
  • rows:估算需要扫描的行数,越小越好;
  • Extra :附加信息,Using index(覆盖索引,好)、Using filesort(需要额外排序,需优化)、Using temporary(使用临时表,需优化)、Using index condition(ICP)。

使用方式:EXPLAIN SELECT ...,MySQL 8.0 还支持 EXPLAIN ANALYZE 显示实际执行时间。

给你张表,发现查询速度很慢,你有哪些排查思路以及解决方案

面试官期待的答案:

排查步骤:

  1. 开启慢查询日志slow_query_log=ONlong_query_time=1,找到具体的慢 SQL;
  2. EXPLAIN 分析执行计划:重点看 type(是否全表扫描)、key(是否用了索引)、rows(扫描行数)、Extra(是否有 filesort/temporary);
  3. 根据分析结果优化
    • type=ALL → 加索引;
    • key=NULL → 检查索引是否失效(函数运算、类型转换、最左前缀等);
    • rows 很大 → 索引区分度低,考虑换索引或优化查询条件;
    • Using filesort → 调整 ORDER BY 列与索引的匹配;
    • 深分页 → 用 WHERE id > last_id 替代 LIMIT offset, n
    • 大量回表 → 建覆盖索引;
  4. 检查锁等待SHOW PROCESSLISTinformation_schema.innodb_trx,排查是否有长事务持锁;
  5. 检查服务器资源:CPU、内存、磁盘 I/O 是否达到瓶颈,Buffer Pool 命中率是否过低。

如果explain用到的索引不正确的话,有什么方法干预吗

面试官期待的答案:

有三种方式干预优化器的索引选择:

  1. FORCE INDEX(idx_name):强制使用指定索引,优化器必须使用;
sql 复制代码
SELECT * FROM orders FORCE INDEX(idx_status) WHERE status = 1;
  1. USE INDEX(idx_name):建议使用指定索引,优化器可以忽略;

  2. IGNORE INDEX(idx_name):忽略指定索引,让优化器从其他索引中选择。

另外,可以通过 ANALYZE TABLE 更新统计信息,让优化器基于更准确的数据做决策(优化器选错索引通常是因为统计信息不准确)。长期来看,应该找到根本原因(统计信息问题、索引设计问题),而不是依赖 FORCE INDEX。

架构

mysql主从复制了解吗,介绍下

面试官期待的答案:

主从复制是 MySQL 高可用和读写分离的基础,流程如下:

  1. 主库:所有写操作执行后,将变更记录到 binlog;
  2. 从库 IO Thread:连接主库,读取 binlog,写入本地的 relay log(中继日志);
  3. 从库 SQL Thread:读取 relay log,重放其中的操作,使从库数据与主库保持一致。

复制模式:

  • 异步复制(默认):主库写完 binlog 就返回,不等从库确认,性能最好但主库宕机可能丢数据;
  • 半同步复制:至少一个从库确认收到 binlog 后主库才返回,安全性更高;
  • 全同步复制:所有从库确认后才返回,安全但性能差。

应用场景:读写分离(写主库,读从库)、数据备份、高可用切换。

主从延迟都有什么处理方法

面试官期待的答案:

主从延迟的原因:主库并发写入,从库 SQL Thread 单线程重放(5.6+ 支持并行复制但有限制)、从库读压力大、网络延迟等。

处理方法:

  1. 并行复制 :MySQL 5.7+ 的 MTS(Multi-Threaded Slave),按库或按事务组并行重放,slave_parallel_workers 设置并行线程数;
  2. 强制读主库:对一致性要求高的场景(如下单后立即查询订单),强制走主库读;
  3. 半同步复制:确保从库至少收到 binlog 再提交,减少延迟窗口;
  4. 减少大事务:大事务 binlog 体积大,重放耗时长,拆分成小事务;
  5. 从库硬件优化:提升从库的磁盘 I/O 性能(SSD)、增加 Buffer Pool;
  6. 监控延迟SHOW SLAVE STATUS 中的 Seconds_Behind_Master 字段,设置告警阈值。

分表和分库是什么,有什么区别

面试官期待的答案:

  • 分表:将一张大表的数据按规则拆分到多张表中(可以在同一个数据库实例内)。解决单表数据量过大导致的查询慢问题(B+ 树层高增加,索引维护开销大);
  • 分库:将数据分散到多个数据库实例(不同的 MySQL 服务器)。解决单库并发连接数上限、CPU/磁盘 I/O 瓶颈问题。

分片策略:

  • 垂直分:按业务拆分(垂直分库:用户库、订单库;垂直分表:将宽表的冷热字段拆开);
  • 水平分:同一张表的数据按规则(取模、范围、一致性哈希)分散到多个库/表。

带来的问题:跨库 JOIN 不支持(需应用层组装)、分布式事务复杂、全局唯一 ID 需要雪花算法、count/order by 需要汇总各分片。

工具:ShardingSphere、Vitess,或应用层自实现。

相关推荐
gis分享者2 小时前
华为OD面试-Java、C++、Pyhton等多语言实现-目录
java·c++·华为od·面试·目录·od·机试
蒸汽求职2 小时前
蒸汽教育求职分享:2026年数据工程师就业优势分析与职业发展路径指南
数据库·人工智能·面试·求职招聘·美国求职
程序员buddha2 小时前
Java面试八股文数据库篇
java·数据库·面试
张元清2 小时前
React 拖拽:无需第三方库的完整方案
前端·javascript·面试
重庆小透明2 小时前
【搞定面试之mysql】第二篇:事务和MVCC
java·后端·mysql·面试·职场和发展
用户851160276122 小时前
慢 SQL 如何排查和优化?
mysql·面试
panzer_maus2 小时前
Mysql中的undo log和redo log, bin log的介绍
数据库·mysql
程序员buddha2 小时前
Java面试八股文Redis篇
java·redis·面试
用户851160276122 小时前
Redis 持久化机制有哪些?
redis·面试