索引是如何将随机 I/O 转换为顺序 I/O 的?

来源:菜鸟站长作者:Robin头衔:草根站长
导读:本期聚焦于小伙伴创作的《索引是如何将随机 I/O 转换为顺序 I/O 的?》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《索引是如何将随机 I/O 转换为顺序 I/O 的?》有用,将其分享出去将是对创作者最好的鼓励。

数据库查询过程中,磁盘I/O的效率直接影响整体性能,随机I/O和顺序I/O的性能差距可达数十倍,而索引的核心作用之一就是将查询过程中的随机I/O转换为顺序I/O,从而大幅提升查询效率。

索引是如何将随机 I/O 转换为顺序 I/O 的?

磁盘I/O的基本类型

要理解索引的作用,首先需要明确两种I/O类型的区别:

  • 随机I/O:指磁盘读写头需要频繁移动到不同的物理位置读取数据,每次移动都需要消耗寻道时间和旋转延迟,性能极低。比如查询用户表中ID为100、200、300的三条不连续数据,磁盘需要三次移动到不同位置读取。
  • 顺序I/O:指磁盘读写头连续读取相邻的物理扇区数据,不需要频繁移动,只需要少量寻道时间,后续读取可以连续进行,性能远高于随机I/O。比如读取一个连续存储的文件,磁盘可以一次性连续读完。

无索引时的查询I/O情况

当表中没有索引时,数据库执行查询会进行全表扫描,也就是从表的第一行开始逐行读取数据,判断是否符合查询条件。如果查询条件对应的数据在表中分布非常分散,那么每找到一条符合条件的数据,都需要进行一次随机读取,整个过程会包含大量的随机I/O。

举个例子,我们有一个用户表user,结构如下:

CREATE TABLE user (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    age INT,
    city VARCHAR(50)
);

如果我们要查询所有年龄大于30岁的用户,且没有对age字段建立索引,数据库会扫描全表,假设符合条件的用户ID分别是10、55、120、300,那么磁盘需要分别移动到这四个ID对应的物理存储位置读取数据,产生四次随机I/O。

索引如何将随机I/O转换为顺序I/O

常见的数据库索引比如B+树索引,本身是有序存储的,这个特性是转换I/O类型的关键,具体过程可以分为以下几步:

1. 索引的有序存储特性

B+树索引的叶子节点是按照索引键的大小顺序存储的,并且叶子节点之间通过双向指针连接,形成一个有序的链表。比如我们对user表的age字段建立索引,那么索引的叶子节点会按照age从小到大排列,相同age的节点再按照主键顺序排列。

2. 范围查询的I/O优化

当执行范围查询时,比如查询age BETWEEN 30 AND 40,数据库首先会在索引的非叶子节点中找到第一个age大于等于30的叶子节点,然后沿着叶子节点的链表顺序向后读取,直到遇到age大于40的节点为止。这个过程读取的索引节点是连续的,属于顺序I/O。

读取完符合条件的索引节点后,索引中存储了对应数据行的物理地址(或者是主键ID,取决于索引类型),如果是聚簇索引,叶子节点直接存储数据行,那么此时已经完成了数据读取;如果是非聚簇索引,还需要根据主键ID回表查询数据行。

但是如果符合条件的数据行在物理存储上也是连续的,那么回表的过程也会是顺序I/O;即使物理存储不连续,由于索引已经过滤掉了大量不符合条件的数据,需要回表的次数会大幅减少,整体的随机I/O次数也会远低于全表扫描的情况。

3. 单点查询的I/O优化

即使是单点查询,比如查询age = 35的用户,索引也可以减少I/O次数。数据库只需要从索引的根节点开始,逐层向下查找,最终定位到age=35的叶子节点,这个过程读取的索引节点数量很少,且索引节点通常连续存储,属于少量顺序I/O,远优于全表扫描的大量随机I/O。

代码示例验证

我们可以通过MySQL的执行计划来观察索引使用前后的I/O变化,首先创建测试表和索引:

-- 创建测试表
CREATE TABLE test_io (
    id INT PRIMARY KEY AUTO_INCREMENT,
    val INT,
    data VARCHAR(100)
);

-- 插入10万条测试数据
DELIMITER //
CREATE PROCEDURE insert_test_data()
BEGIN
    DECLARE i INT DEFAULT 1;
    WHILE i <= 100000 DO
        INSERT INTO test_io (val, data) VALUES (FLOOR(RAND() * 1000), CONCAT('test_data_', i));
        SET i = i + 1;
    END WHILE;
END //
DELIMITER ;

CALL insert_test_data();

-- 未建立索引时查询val在500到550之间的数据
EXPLAIN SELECT * FROM test_io WHERE val BETWEEN 500 AND 550;

-- 对val字段建立索引
CREATE INDEX idx_val ON test_io(val);

-- 建立索引后再次查询
EXPLAIN SELECT * FROM test_io WHERE val BETWEEN 500 AND 550;

第一次执行EXPLAIN时,结果中的type字段会显示ALL,代表全表扫描,会产生大量随机I/O;第二次执行EXPLAIN时,type字段会显示range,代表使用了范围索引扫描,此时读取索引的过程是顺序I/O,I/O效率会大幅提升。

注意事项

  • 不是所有索引使用场景都能完全消除随机I/O,如果回表的数据行物理存储非常分散,还是会产生一定的随机I/O,但整体次数会远少于无索引的情况。
  • 索引本身也会占用存储空间,并且会增加写入操作的开销,因为每次写入数据都需要同步更新索引,所以需要根据实际查询需求合理建立索引,避免过度建索引。
  • 对于写入非常频繁的表,过多的索引会导致写入时的I/O次数增加,反而可能降低整体性能,需要平衡查询和写入的需求。

索引随机I/O顺序I_O数据库优化修改时间:2026-07-18 21:54:33

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。