索引

MySQL 中的索引可以分为逻辑上的主键索引、唯一索引、普通索引、联合索引、全文索引和空间索引,其中 InnoDB 默认使用 B+Tree 作为索引结构,主键索引是聚簇索引,数据和索引存储在一起,而普通索引是非聚簇索引,查询时可能需要回表。

索引失效情况

MySQL 索引失效的常见情况包括对索引列使用函数或表达式、隐式类型转换、LIKE 左模糊查询、联合索引不满足最左前缀原则、范围查询后列失效以及 OR 条件不当等,其本质是查询条件破坏了 B+Tree 索引的有序性或优化器认为全表扫描更高效。

索引创建注意事项

MySQL 创建索引时需要综合考虑查询频率、字段选择性、联合索引设计、索引顺序以及写入成本,优先为高频查询和高选择性字段建立索引,合理设计联合索引遵循最左前缀原则,并避免冗余索引和过多索引,以实现查询性能和写入性能的平衡。

最左匹配原则

MySQL 的最左匹配原则是指在联合索引中,查询必须从索引的最左字段开始连续匹配,才能有效利用索引结构,否则索引会失效或只部分生效,其本质是基于 B+Tree 的有序排列特性,MySQL 只能从最左前缀开始进行范围查找。

阻断最左匹配原则的情况包括跳过最左字段、中间字段缺失、对索引列使用范围查询以及函数或表达式操作等,其本质是这些操作破坏了联合索引在 B+Tree 中从左到右的连续有序结构,使得后续字段无法继续利用索引进行精确查找。

单库单表向分库分表平滑过渡

单库单表到分库分表的平滑迁移通常采用双写机制保证新旧数据一致,通过全量+增量同步完成数据迁移,再通过读写路由和灰度发布逐步切换流量,最终实现从旧库到分库分表的无缝迁移。主要使用到工具 ShardingSphere(路由) 和 Canal(数据迁移)。

分库分表条件

MySQL 分库分表的触发条件通常包括数据量过大、QPS 和写入压力过高、单表索引性能下降、磁盘和 IO 成为瓶颈以及锁竞争严重等,其本质是单库数据库在性能和容量上无法继续水平扩展,需要通过拆分数据实现横向扩展能力。

单表数据量的参考阈值:

数据量级 建议 说明
小于 100 万行 不需要分表 普通索引优化足矣
100 万 ~ 1000 万行 优化索引+读写分离 可考虑垂直拆分或读写分离
超过 1000 万行 可能考虑分表 需要根据具体业务查询频率和写入量判断
超过 5000 万 ~ 1 亿行 优先考虑分库分表 性能问题开始明显,维护成本增高

每日数据增长量

  • < 10 万条/天:短期内不必分表,优化索引和查询即可。
  • 10 万 ~ 100 万条/天:中期内可能需要考虑分区表或分表。
  • > 100 万条/天:通常推荐使用分库分表,或考虑使用大数据平台(如ClickHouse、TiDB等)。

查询耗时的考量

  • 查询时间 > 200ms 且优化空间有限(如已建好索引);
  • 慢查询频繁出现(> 1s)
  • JOIN 操作变慢 或排序、分页卡顿;
  • 大表上频繁的写入/更新操作对性能造成明显影响;

此时,可以考虑进行 水平分表(按范围、哈希、时间等拆分)或 垂直分表(按字段或功能模块划分)。

💡 替代方案(分库分表前的优化)

  1. 优化 SQL 语句,避免 SELECT *。
  2. 合理建索引,覆盖索引优先。
  3. 使用分区表(MySQL 5.7 以后支持)。
  4. 读写分离(主从复制 + 中间件)。
  5. 缓存热点数据(Redis 缓存热点查询)。

事务隔离级别实现原理

MySQL 事务隔离级别主要通过 MVCC + Read View + 锁机制实现,其中 Read Uncommitted 直接读最新数据,Read Committed 每次生成 Read View,Repeatable Read 复用 Read View 并结合 Next-Key Lock 防止幻读,而 Serializable 通过加锁将并发执行变为串行执行。

ACID 实现原理

MySQL InnoDB 的 ACID 特性是通过 Undo Log 保证原子性,通过 Redo Log 保证持久性,通过 MVCC 和锁机制保证隔离性,而一致性则依赖于事务机制与数据库约束共同实现的结果。

binlog、redolog、undolog

MySQL 中 redo log 是 InnoDB 引擎层的物理日志,用于保证事务的持久性和崩溃恢复;undo log 用于事务回滚和 MVCC 多版本控制;binlog 是 Server 层的逻辑日志,用于主从复制和数据恢复,三者通过两阶段提交机制保证数据一致性。

MySQL 支持三种 Binlog 格式,每种格式在记录数据时有不同的粒度:

  1. Statement-based Logging (SBL,基于语句的日志),记录实际执行的 SQL 语句。这是最常见的格式。这种方式存储的日志文件较小,便于管理和传输。
  2. Row-based Logging (RBL,基于行的日志),记录每一行数据的修改操作。这意味着每条 INSERTUPDATEDELETE 操作都会记录具体的行变化。这种方式在复制过程中不容易出现数据不一致的问题。但是日志文件可能非常庞大,尤其是当数据表更新非常频繁时。
  3. Mixed Logging (混合日志),结合了 Statement-basedRow-based 两种格式。MySQL 会根据具体的 SQL 语句决定采用哪种日志格式。在大多数情况下,它可以兼顾性能和一致性,避免 Statement-based 和 Row-based 的缺点。但是相较于纯 Statement-based 或 Row-based,配置和管理稍显复杂。

行级锁升级为表级锁

InnoDB 中行级锁不会真正升级为表级锁,但在索引失效、全表扫描或范围查询(如 next-key lock)情况下,锁的范围会扩大,甚至覆盖整张表,从而表现出类似表锁的效果,其本质是由于无法精确定位行导致的锁粒度退化。

InnoDB 引擎

InnoDB 是 MySQL 默认的存储引擎,支持事务、行级锁和崩溃恢复能力,是目前使用最广泛的引擎。

首先在事务方面,InnoDB 完全支持 ACID 特性,通过 redo log 和 undo log 实现事务的提交、回滚以及崩溃恢复。

其次在存储结构上,InnoDB 采用 聚簇索引(Clustered Index),也就是说数据本身存储在主键索引的叶子节点上,主键查询效率非常高。

并发控制方面,InnoDB 支持行级锁,并结合 MVCC(多版本并发控制)实现高并发读写,避免读写互相阻塞。

日志机制方面,通过 redo log 保证事务的持久性,通过 undo log 支持事务回滚,并实现 MVCC 的版本链。

此外,InnoDB 还支持外键约束,并且具有自动崩溃恢复能力,保证数据一致性和可靠性。

InnoDB 是支持事务的存储引擎,通过聚簇索引、MVCC、行级锁以及 redo/undo log,实现高并发与强一致性的平衡。

MyISAM 和 InnoDB 区别

MyISAM 不支持事务和行级锁,使用表级锁,适合读多写少的场景,但不具备崩溃恢复能力;InnoDB 支持事务、行级锁和外键,具备 redo/undo log 机制保证数据安全,是 MySQL 当前默认的主流存储引擎。

“MyISAM 索引和数据分离,而 InnoDB 索引和数据在一起”

  • MyISAM 的索引是典型的 B+ 树结构
  • 叶子节点存储的是数据的物理地址(指针),不是数据本身
  • 查数据过程是“两步走”:
    1. 先查索引定位数据文件的位置
    2. 再去 .MYD 文件里根据地址把数据读出来。

两阶段提交

MySQL 两阶段提交是为了解决 redo log 和 binlog 一致性问题,在 prepare 阶段先写 redo log,在 commit 阶段先写 binlog 再提交 redo log,通过状态控制保证事务要么全部成功,要么全部失败,从而实现崩溃恢复和主从复制的一致性。

MVCC 版本控制机制原理

MVCC 的本质是:通过 Undo Log 保存数据的多个历史版本,并结合 Read View 判断当前事务的可见性,从而实现“读历史版本数据”,避免读写冲突。通过读取“历史版本”替代加锁,读不阻塞写,写不阻塞读(大部分情况)。

在 MySQL 的 InnoDB 存储引擎中,MVCC 主要用于**可重复读(REPEATABLE READ)读已提交(READ COMMITTED)**这两种事务隔离级别。

Read Committed(RC),每次 SELECT 都生成新的 Read View,每次读最新已提交数据,不可重复读。

Repeatable Read(RR,默认),只在第一次 SELECT 生成 Read View,后续复用,实现可重复读。

解决读写冲突,提高并发性能,避免加锁读

如果没有 MVCC 读要加锁(性能差),写会阻塞读

MVCC核心组成:Undo Log(版本链)用来保存数据的历史版本,结构:最新数据 → undo log v1 → undo log v2

每条数据隐藏字段:trx_id(事务ID),roll_pointer(指向上一版本),形成“版本链”

Read View(一致性视图)用来判断当前事务能看到哪些版本的数据,核心组成如下:

  • trx_id:每个事务都有一个唯一递增的事务 ID,越新的事务 ID 值越大。
  • m_ids(活跃事务列表):当 Read View 生成时,当前正在执行但未提交的事务 ID 列表。
  • min_trx_id(最小活跃事务 ID):m_ids 中最小的事务 ID。
  • max_trx_id(下一个将要分配的事务 ID):比当前所有活跃事务 ID 都大的值,代表未来新事务的起始 ID。

当一个事务读取数据时,它需要判断哪些数据版本对自己可见。InnoDB 通过 Read View(读取视图)来管理可见性。

  1. 数据版本的 (trx_id) < Read View 的最小活跃事务 ID (min_trx_id):该数据版本已经提交,对当前事务可见。
  2. 数据版本的 (trx_id) >= Read View 的最大活跃事务 ID (max_trx_id):该数据版本是在当前事务之后创建的,不可见。
  3. 数据版本的 (trx_id) 介于 min_trx_idmax_trx_id 之间
    • trx_id 属于活跃事务列表,则表示该事务还未提交,不可见。
    • trx_id 不在活跃事务列表中,则可见。

MVCC工作流程,读取数据过程:

1. 生成 Read View
2. 找当前最新数据
3. 判断 trx_id 是否可见
4. 不可见 → 顺着 undo log 找历史版本
5. 直到找到可见版本

MVCC 适用于读多写少的场景,对于高并发写入,可能需要结合锁机制或优化索引来提升性能。

REPEATABLE READ 级别的 Read View 确保了事务期间看到的行数据不会随其他事务的提交而变化,但对 INSERT 仍然可能出现幻读(需借助 Next-Key Lock 解决)。

死锁产生条件

MySQL 中的死锁是指两个或多个事务在执行过程中,因相互持有对方需要的锁资源,导致互相等待而无法继续执行的现象。InnoDB 通过死锁检测机制 + 超时回滚机制来解决死锁问题。

    -- 事务 A
    START TRANSACTION;
    UPDATE table1 SET column1 = 'value1' WHERE id = 1;  -- 锁住 id = 1
    UPDATE table1 SET column1 = 'value2' WHERE id = 2;  -- 等待事务 B 释放 id = 2

    -- 事务 B
    START TRANSACTION;
    UPDATE table1 SET column1 = 'value2' WHERE id = 2;  -- 锁住 id = 2
    UPDATE table1 SET column1 = 'value1' WHERE id = 1;  -- 等待事务 A 释放 id = 1

由于事务 A 和事务 B 互相等待对方释放锁,造成死锁。MySQL 会自动检测死锁,并回滚其中一个事务,释放其占有的锁,以使另一个事务得以继续执行。

事务隔离级别

MySQL 一共有四种事务隔离级别,从低到高分别是:
读未提交(Read Uncommitted),可以读到未提交的数据,会产生脏读,基本不用。
读已提交(Read Committed),只能读到已提交的数据,解决了脏读,但会出现不可重复读和幻读。
**可重复读(Repeatable Read)**是 MySQL InnoDB 默认隔离级别,在同一个事务内多次读取结果一致,通过 MVCC 保证快照读的一致性,并结合 Next-Key Lock 在一定程度上避免幻读。
**串行化(Serializable)**是最高隔离级别,通过强制事务串行执行来保证完全一致性,解决所有并发问题,但性能最差。

脏读是指一个事务读到了另一个事务未提交的数据,如果对方回滚,就会读到无效数据。

不可重复读是指在同一个事务内,多次读取同一条记录,但由于其他事务对该记录进行了修改并提交,导致前后读取结果不一致。

幻读是指在同一个事务内,多次执行范围查询时,由于其他事务插入或删除了符合条件的记录,导致第二次查询结果“多了或少了几行”,像出现了“幻影”。

delete、truncate、drop

Delete:属于 DML、可回滚、表结构还在,删除表的全部或者一部分数据行、删除速度慢,需要逐行删除。
Truncate:属于 DDL、不可回滚、表结构还在,删除表中的所有数据、删除速度快。
Drop:属于 DDL、不可回滚、从数据库中删除表,所有的数据行,索引和权限也会被删除、删除速度最快。

ROW_ID vs 主键

在 MySQL InnoDB 中,主键(Primary Key)是用于唯一标识一行数据的字段,并且数据会按照主键的顺序组织存储在聚簇索引中。也就是说,InnoDB 的数据本身就存储在主键索引的叶子节点上,所以主键不仅用于唯一性约束,也决定了数据的物理存储顺序。

如果表中没有显式定义主键,InnoDB 会选择一个唯一非空索引作为聚簇索引;如果也没有这样的索引,则会自动生成一个隐藏的 ROW_ID

ROW_ID 是 InnoDB 自动生成的隐藏列,用于在没有主键的情况下唯一标识一行数据,它只在内部使用,用户无法直接访问。

ROW_ID 一旦达到其数据类型的最大值,不会自动从 0 开始重新计数。此时,插入新行时会出现错误,表明无法插入数据。应采取措施,例如添加主键、归档旧数据或重建表,以避免达到极限。设计良好的表结构和定期监控数据使用情况是必要的,以防止 ROW_ID 达到最大值。

ROW_ID 具有自增属性,自增列会根据当前最大值生成下一个值。如果重启前自增列的最大值为 100,重启后下一个插入的值仍然是 101。因为自增计数器的信息存储在数据库内部,重启不会影响其状态。如果手动修改自增列的值(例如,通过 ALTER TABLE),可能会影响后续插入的自增值。如果删除了行,插入新行时不会使用被删除行的自增值,而是继续使用下一个自增值。

MySQL 的锁机制主要用于解决并发访问时的数据一致性问题,InnoDB 中常见的锁可以分为行级锁、表级锁和意向锁三大类。

首先,表锁是粒度最大的锁,比如 MyISAM 引擎主要使用表锁,特点是实现简单,但并发性能较差。

InnoDB 默认使用的是行级锁,包括记录锁(Record Lock)、间隙锁(Gap Lock)以及 Next-Key Lock。
记录锁是锁定某一行数据;
间隙锁是锁定索引记录之间的间隙,防止幻读;
Next-Key Lock 是记录锁 + 间隙锁的组合,用来解决范围查询下的并发问题。

另外还有意向锁,它是表级锁,用来表示事务未来要加行锁,作用是提高锁冲突判断效率,避免逐行检查。

MySQL InnoDB 以行级锁为核心,通过记录锁、间隙锁和 Next-Key Lock 控制并发,并用意向锁协调表级锁与行级锁的冲突。

调优

MySQL 调优一般从四个层面来做:SQL层、索引层、架构层和参数层

首先在 SQL 层,主要通过慢查询日志定位慢 SQL,然后优化执行计划,比如避免全表扫描、减少不必要的字段查询、避免函数操作索引字段,以及拆分复杂 SQL。

其次在 索引层,核心是建立合适的索引,比如联合索引遵循最左前缀原则,同时避免过度索引,因为索引会增加写入成本。还要关注索引是否命中,比如是否发生回表。

第三是 架构层优化,比如读写分离、分库分表、加缓存(如 Redis),以及通过中间件降低数据库压力。

最后是 参数层优化,主要是 InnoDB 相关配置,比如 buffer pool 大小、redo log、连接数等,让数据库更好利用内存和磁盘资源。

MySQL 调优本质是通过 SQL 优化 + 索引设计 + 架构拆分 + 参数调优,逐步降低扫描量和磁盘 IO。

主从复制

MySQL 主从复制是指将一个数据库实例(主库 Master)的数据变更,实时或准实时同步到一个或多个从库(Slave),用于实现读写分离和数据备份。

它的核心流程分三步:
第一步,主库写入 binlog(二进制日志),记录所有数据变更操作。

第二步,从库通过 IO 线程向主库请求 binlog,主库将 binlog 发送给从库,从库写入到 relay log(中继日志)

第三步,从库的 SQL 线程读取 relay log 并执行,从而重放主库的操作,实现数据同步。

按照复制方式,可以分为:

  • 异步复制:主库写完 binlog 就返回,不保证从库是否执行完成(最常见)
  • 半同步复制:主库需要至少一个从库确认收到 binlog
  • 同步复制:所有从库执行完成才返回(性能较差,基本不用)

分区

MySQL 分区是指将一张大表按照某种规则拆分成多个物理分区,但逻辑上仍然是一张表,对外查询方式不变。

它的核心目的是提升大表的查询和维护性能,比如减少扫描数据量、提高查询效率、以及方便数据管理。

MySQL 支持多种分区方式,常见的有:
RANGE 分区,按照范围分区,比如按时间或ID区间划分,最常用于按日期归档数据。
LIST 分区,按照枚举值分区,比如地区、省份。
HASH 分区,根据字段的 hash 值均匀分布数据,适合数据分布比较均匀的场景。
KEY 分区,类似 HASH,但由 MySQL 内部计算 hash 值。

分区并不是索引的替代品,它解决的是数据量过大导致的扫描问题(partition pruning),但如果设计不合理,也可能导致性能下降。

范式化 vs 反范式化

范式化是指按照数据库范式规则来设计表结构,核心目标是减少数据冗余、避免更新异常。通常通过拆表,把数据拆分成多个关联表,比如用户表、订单表、商品表分开存储。它的优点是数据一致性好、冗余少,但缺点是查询时需要多表 JOIN,可能影响性能。

反范式化则是在设计时适当冗余数据,通过增加字段或合并表来减少 JOIN,提高查询性能。例如在订单表中冗余用户信息或商品信息。它的优点是查询快、减少关联操作,但缺点是数据冗余、更新时容易产生不一致,需要额外维护一致性。

在实际生产中,通常是范式化和反范式化结合使用,在一致性和性能之间做权衡,比如写操作严格范式化,读操作适当反范式化。

范式化追求数据一致性和减少冗余,反范式化追求查询性能和减少 JOIN,实际系统中通常在两者之间折中设计。

MySQL 中常见的三大范式是用来规范数据库设计、减少数据冗余和避免更新异常的规则。
第一范式(1NF)要求字段必须是原子性的,也就是每一列不能再拆分,比如不能在一个字段里存多个值或数组,必须保证每个字段都是不可再分的基本数据单元。

第二范式(2NF)是在满足第一范式的基础上,要求表中的非主键字段必须完全依赖主键,不能只依赖主键的一部分,主要是针对联合主键的情况,用来消除部分依赖。

第三范式(3NF)是在满足第二范式的基础上,要求非主键字段之间不能存在传递依赖,也就是说非主键字段必须直接依赖主键,不能间接依赖其他非主键字段。

第一范式保证字段原子性,第二范式消除部分依赖,第三范式消除传递依赖,从而减少数据冗余、保证数据一致性。

回表查询

MySQL 中的回表查询是指:在使用二级索引(非主键索引)查询时,先通过二级索引找到对应的主键值,再根据主键到**聚簇索引(主键索引)**中重新查找完整数据行的过程。

这是因为 InnoDB 的二级索引叶子节点存储的是索引字段 + 主键值,并不存完整行数据,所以当查询的字段不在二级索引中时,就需要回到主键索引再查一次,这个过程就叫回表。

如果查询的字段可以被索引完全覆盖,就不会回表,这种情况叫覆盖索引

回表查询就是通过二级索引找到主键后,再回到主键索引查完整数据行的过程,本质是一次额外的随机 IO。

SQL 语句的生命周期

MySQL 中一条 SQL 语句的生命周期,通常可以分为几个阶段:

首先是连接阶段,客户端通过连接器建立与 MySQL 的连接,并完成权限认证。

然后进入SQL 解析阶段,MySQL 会对 SQL 语句进行词法分析和语法分析,生成解析树。

接着是查询优化阶段,优化器会根据统计信息选择最优执行计划,比如选择使用哪个索引、是否走全表扫描等。

之后进入执行阶段,执行器根据执行计划调用存储引擎(比如 InnoDB),进行数据读取或修改操作。

在执行过程中,如果涉及数据修改,还会记录 redo log、undo log 以及 binlog,用于事务恢复和主从复制。

最后是结果返回阶段,将查询结果返回给客户端,并释放相关资源(如果是长连接则保留连接上下文)。

MySQL SQL 生命周期就是:连接 → 解析 → 优化 → 执行 → 存储引擎处理 → 返回结果。

MySQL 和 Redis 数据一致性问题

MySQL 和 Redis 的一致性问题,本质是因为两者分别是持久化数据库和缓存系统,数据来源不同,无法天然保证强一致,只能做权衡,一般追求的是最终一致性

常见一致性问题主要发生在“先写数据库还是先写缓存”的顺序上:

如果先写 MySQL 再写 Redis,可能出现数据库更新成功但缓存更新失败,导致缓存脏数据。

如果先写 Redis 再写 MySQL,则可能数据库写失败,但缓存已经更新,也会产生不一致。

在实际工程中,最常见的方案是 Cache Aside(旁路缓存模式)

读的时候先查缓存,缓存没有再查数据库并回写缓存;
写的时候先更新数据库,再删除缓存,而不是直接更新缓存。

这样可以尽量避免并发场景下的数据不一致问题。

但即使这样,也可能出现短暂不一致,比如并发读写时缓存刚被删除但旧数据又被回填,所以通常还会结合过期时间(TTL)+ 重试机制 + 异步补偿来保证最终一致性。

MySQL 和 Redis 一致性通常通过“先写库、再删缓存”的 Cache Aside 模式实现,配合 TTL 和补偿机制,保证最终一致性而非强一致性。

删除缓存时有个停顿时间,这个时间的长短视系统的并发量而定,如果并发量高的话,就设定长一些,如果并发量不高的话,就设定短一些。

数据库查询避免深分页问题

在数据库查询方面,传统的 page 查询会有深度分页问题,随着 offset 越来越大,性能也会越来越差(呈线性退化),因为随着分页深度的增加,前面不需要的数据依然会被查询出来,然后丢弃,只取最后一页的数据。

使用 cursor 换了一种分页方式,从根本上绕开 offset 带来的性能问题。 cursor 思路是不再跳过数据,而是“从上一次的位置继续查”,所以使用 cursor 进行查询的性能始终比较稳定。不过 cursor 不能随便跳页,只能逐页查询,如果在查询时中间有新数据的插入,可能出现漏数据的情况,解决方法可以使用“时间 + id”双 cursor。

慢查询原因

MySQL 中导致慢查询的原因主要可以从 SQL 设计、索引使用、数据量、锁竞争和系统资源五个方面来分析。

首先在 SQL 层面,常见问题包括写了复杂 SQL,比如多表 JOIN 过多、子查询嵌套、或者使用函数操作字段,导致优化器无法有效使用索引。

其次是 索引问题,比如没有合适索引、索引失效、联合索引不满足最左前缀原则,或者查询字段无法覆盖索引,导致全表扫描或回表次数过多。

第三是 数据量问题,当表数据过大时,即使有索引,如果选择性不好或者扫描范围太大,也会导致查询变慢。

第四是 锁和事务问题,比如存在行锁竞争、间隙锁冲突,或者长事务导致锁等待,也会造成查询延迟。

最后是 系统层面问题,比如 buffer pool 太小导致频繁磁盘 IO,或者 CPU、磁盘 IO、连接数达到瓶颈。

MySQL 慢查询通常是 SQL 不合理、索引设计不当、数据量过大、锁竞争以及系统资源不足共同导致的结果。

单表数据量不超过1000万的原因

核心原因是为了保证查询性能和索引效率稳定。

首先是 索引层面,InnoDB 使用 B+ 树索引,当数据量过大时,B+ 树高度会增加,虽然仍是 O(logN),但磁盘 IO 次数会增加,查询延迟变高。

其次是 回表成本增加,大量数据下使用二级索引查询时,可能会产生大量随机 IO 回表,性能明显下降。

第三是 缓存命中率下降,数据量越大,Buffer Pool 中能够缓存的热点数据比例越低,导致更多请求需要访问磁盘。

第四是 维护成本增加,比如索引重建、DDL 操作、备份恢复、主从同步都会变慢,甚至影响线上稳定性。

最后是 锁和并发影响更明显,数据量大时,扫描范围变大,更容易产生锁竞争和延迟抖动。

单表不超过 1000 万本质是为了控制 B+ 树深度、降低 IO 成本、提高缓存命中率,并保证系统整体稳定性。

MySQL 索引的底层结构以及使用此结构的原因

MySQL InnoDB 存储引擎的索引底层采用 B+Tree。B+Tree 的非叶子节点只保存索引键和子节点指针,叶子节点保存实际数据(聚簇索引)或主键值(二级索引),并且所有叶子节点通过双向链表连接。之所以采用 B+Tree,而不是 Hash、红黑树或普通 B 树,是因为 B+Tree 的分叉数高、树高度低,可以将查询控制在 3~4 次磁盘 IO 内;同时叶子节点按顺序链接,天然支持范围查询和排序,非常适合数据库索引场景。相比之下,Hash 不支持范围查询,红黑树层数太高导致随机 IO 次数多,而普通 B 树由于非叶子节点也存放数据,分叉数更低、范围查询效率也不如 B+Tree,因此 B+Tree 成为了关系型数据库索引的最佳选择。

explain 怎么看?

MySQL 中 EXPLAIN 用来分析 SQL 的执行计划,帮助我们判断一条 SQL 是否走索引、扫描了多少数据,以及执行效率如何。

在结果中,重点关注几个核心字段:

首先是 type,表示访问类型,性能从好到差大致是:const、eq_ref、ref、range、index、ALL,其中 ALL 表示全表扫描,是最差的情况。

其次是 key,表示实际使用的索引,如果为 NULL,说明没有用到索引。

然后是 rows,表示 MySQL 预估扫描的行数,这个值越小通常越好,代表扫描范围越小。

还有 extra 字段,重点看是否出现 “Using index”(覆盖索引)、“Using where”(条件过滤)以及 “Using temporary / filesort”(表示使用临时表或排序,通常需要优化)。

另外还可以看 possible_keys,表示可能使用的索引,但最终是否使用以 key 为准。

EXPLAIN 主要用来判断 SQL 是否走索引、扫描行数多少以及是否发生额外排序或临时表,从而优化 SQL 执行计划。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐