当前位置: 首页 > news >正文

MySQL B+树索引使用

1. B+树索引的核心原理

1.1 为什么需要索引?

在关系型数据库中,随着数据量的增加,查询速度往往会成为性能瓶颈。没有索引时,数据库需要通过 全表扫描 去逐行比对目标记录,效率非常低。
索引的作用就类似于图书的目录,通过关键字快速定位目标行,从而减少无效的扫描。

其中,B+树索引是 MySQL InnoDB 存储引擎的默认索引结构,也是最常用、最核心的一种索引。


1.2 B+树的结构特点

1.2.1 B树 vs B+树

  • B树(Balanced Tree)

    • 每个节点都存储 键(key)和数据(data)

    • 查询时可能在中间节点就找到目标数据。

  • B+树(Balanced Plus Tree)

    • 只有叶子节点存储完整的记录数据;非叶子节点只存储索引键和指向子节点的指针。

    • 所有叶子节点通过 链表 相连,便于范围查询和排序。

直观比喻

  • B树就像图书馆中每一层书架都放有完整的书;

  • B+树则是只有最底层的书架存放书籍,上层只放目录卡片,方便快速检索。

1.2.2 B+树的优势

  1. IO效率高:非叶子节点只保存键值和指针,一个节点能容纳更多索引键,树的高度更低。

  2. 范围查询高效:叶子节点链表结构让范围扫描非常顺畅。

  3. 顺序访问友好:天然支持 ORDER BY,避免额外排序操作。


1.3 InnoDB中的索引实现

1.3.1 聚簇索引(Clustered Index)

  • 特点:数据本身存储在B+树的叶子节点

  • 默认:InnoDB 会以主键作为聚簇索引。

  • 查询主键时非常高效,因为直接命中数据页。

-- 创建一个用户表
CREATE TABLE users (user_id INT PRIMARY KEY,   -- 主键即为聚簇索引username VARCHAR(50),email VARCHAR(100)
);

👉 当执行 SELECT * FROM users WHERE user_id = 100; 时,InnoDB 直接在聚簇索引的 B+树中找到数据行。

1.3.2 二级索引(Secondary Index)

  • 特点:叶子节点存储的是 主键值,而不是数据行。

  • 查询时需通过 回表:先找到主键,再到聚簇索引取出完整行。

    -- 在 email 上建立二级索引
    ALTER TABLE users ADD INDEX idx_email (email);-- 查询:先定位 idx_email -> 找到 user_id -> 再到聚簇索引取数据
    SELECT * FROM users WHERE email = 'test@example.com';
    

👉 这就是为什么有时候查询需要两步:先查索引,再回表


1.4 B+树的查询过程

1.4.1 等值查询

EXPLAIN SELECT * FROM users WHERE user_id = 100;
  • type: const(常量查询,最快)

  • key: PRIMARY(命中聚簇索引)

  • 无需扫描多行,效率极高。

1.4.2 范围查询

EXPLAIN SELECT * FROM users WHERE user_id BETWEEN 100 AND 200;
  • B+树通过叶子节点的链表顺序扫描,避免全表扫描。

  • type: range(使用范围扫描)。

1.4.3 排序与分组

  • ORDER BY user_id:若使用主键索引,MySQL 可以直接顺序读取叶子节点,无需额外排序。

  • GROUP BY user_id:同理,B+树天然支持分组操作。


1.5 实际场景:电商订单表

在电商系统中,订单表往往数据量巨大。
例如:

CREATE TABLE orders (order_id BIGINT PRIMARY KEY,         -- 聚簇索引user_id INT,status ENUM('paid','unpaid'),create_time DATETIME,INDEX idx_user_time (user_id, create_time) -- 二级索引
);

查询 1:按订单号查询

EXPLAIN SELECT * FROM orders WHERE order_id = 1000001;
  • 命中 主键聚簇索引,只需一次B+树查找。

查询 2:按用户与时间范围查询

EXPLAIN SELECT * FROM orders 
WHERE user_id = 1001 AND create_time BETWEEN '2025-01-01' AND '2025-01-31';
  • 命中 idx_user_time 复合索引。

  • 扫描指定区间的叶子节点即可,性能远高于全表扫描。


1.6 小结

  • B+树是 MySQL InnoDB 的核心索引结构。

  • 聚簇索引存储完整行数据,二级索引存储主键,需要回表。

  • B+树天然支持等值查询、范围查询、排序和分组,查询效率远高于全表扫描。

  • 在实际业务(如电商订单表)中,合理设计索引可以极大提升查询性能。

2. B+树索引的代价与局限性

2.1 索引的存储开销

索引不是“免费”的,它需要额外的磁盘和内存空间。

  • 磁盘空间消耗
    每个索引本质上是一棵 B+树,需要存储在数据页(Page)中。索引越多,占用的磁盘空间也越大。

  • 内存开销
    MySQL 会将部分索引页缓存到 Buffer Pool 中,以提高查询效率。过多的索引会挤占内存空间,影响数据页的缓存率。

示例:

-- 查看表的索引信息
SHOW INDEX FROM orders;

若一个表上创建了多个索引:

  • PRIMARY KEY (order_id)

  • INDEX idx_user_time (user_id, create_time)

  • INDEX idx_status (status)

👉 每个索引都会额外维护一棵 B+树。对大表来说,磁盘和内存压力非常明显。


2.2 索引的维护成本

索引不仅占用空间,在 写操作(INSERT、UPDATE、DELETE) 时,也需要维护。

  • 插入(INSERT)
    新记录必须插入聚簇索引,同时更新相关的二级索引。

  • 更新(UPDATE)
    若更新了索引列,必须在索引树上执行删除+插入操作,代价很高。

  • 删除(DELETE)
    删除记录时,索引项同样需要移除。

示例:

-- 假设在 user_id 上有索引
UPDATE orders SET user_id = 2002 WHERE order_id = 1001;

👉 这个操作不仅要修改数据行,还需要更新 idx_user_time 索引中的位置。如果是大表,这类更新会拖慢写性能。


2.3 回表操作的性能影响

在 InnoDB 中,二级索引的叶子节点只存储主键值,要获取完整数据,需要“回表”。

示例:

EXPLAIN SELECT * FROM orders WHERE user_id = 1001;
  • key: idx_user_time (命中二级索引)

  • 但最终 SELECT * 需要回表去聚簇索引拿到完整行。

  • 如果结果集很大,回表开销会非常明显。

👉 优化手段:覆盖索引

EXPLAIN SELECT user_id, create_time 
FROM orders WHERE user_id = 1001;
  • 由于查询列全部包含在 idx_user_time 中,无需回表,性能更高。


2.4 索引失效的常见场景

即使建了索引,也可能因为一些原因导致 索引无法使用,退化为全表扫描。

2.4.1 使用函数或表达式

-- 索引列被函数包裹,索引失效
SELECT * FROM users WHERE YEAR(create_time) = 2025;

👉 因为 MySQL 必须逐行计算 YEAR(create_time),索引无法使用。

优化:

-- 改写为范围查询
SELECT * FROM users 
WHERE create_time BETWEEN '2025-01-01' AND '2025-12-31';

2.4.2 隐式类型转换

-- email 是 VARCHAR,但参数是数字
SELECT * FROM users WHERE email = 12345;

👉 MySQL 会将 email 转换为数字再比较,导致索引失效。

2.4.3 不满足最左前缀法则

-- 复合索引 (user_id, create_time)
SELECT * FROM orders WHERE create_time = '2025-01-01';

👉 没有使用 user_id,索引无法利用。

优化:

SELECT * FROM orders 
WHERE user_id = 1001 AND create_time = '2025-01-01';

2.4.4 使用不等号或 LIKE 前缀模糊

-- 不等号
SELECT * FROM orders WHERE user_id <> 1001;-- 模糊查询
SELECT * FROM users WHERE username LIKE '%abc';

👉 这类条件会导致索引失效,退化为全表扫描。


2.5 实际案例:用户登录日志表

假设有一个登录日志表:

CREATE TABLE login_logs (log_id BIGINT PRIMARY KEY,user_id INT,login_time DATETIME,ip_address VARCHAR(50),INDEX idx_login_time (login_time)
);

查询 1:时间范围过滤(索引有效)

EXPLAIN SELECT * FROM login_logs 
WHERE login_time BETWEEN '2025-01-01' AND '2025-01-31';
  • type: range

  • key: idx_login_time

  • 高效扫描。

查询 2:函数包裹(索引失效)

EXPLAIN SELECT * FROM login_logs 
WHERE DATE(login_time) = '2025-01-01';
  • type: ALL(全表扫描)

  • key: NULL(未使用索引)。


2.6 小结

  • 索引代价:额外的存储空间、写入维护开销。

  • 回表问题:二级索引查询可能导致性能下降。

  • 索引失效:函数、类型转换、不等号、最左前缀失效等场景需要避免。

  • 结论:索引能提升查询性能,但使用不当反而拖累系统。

3. 高效使用 B+树索引的策略

3.1 索引设计的基本原则

3.1.1 高选择性优先

  • 选择性(Selectivity):不同值的数量 / 总行数。

  • 选择性越高,索引过滤效果越好。

例子:

-- status 字段只有 'paid'、'unpaid'
SELECT * FROM orders WHERE status = 'paid';

👉 即使建了索引,过滤效果也不好,因为 status 区分度太低。

对比:

-- user_id 的区分度高
SELECT * FROM orders WHERE user_id = 1001;

👉 更适合作为索引列。


3.1.2 最左前缀法则

复合索引必须遵循“最左前缀”原则:

  • 只有查询条件包含了从最左边开始的连续列,索引才能生效。

例子:

-- 复合索引 (user_id, create_time)
SELECT * FROM orders WHERE user_id = 1001;         -- ✅ 生效
SELECT * FROM orders WHERE user_id = 1001 AND create_time > '2025-01-01';  -- ✅ 生效
SELECT * FROM orders WHERE create_time > '2025-01-01'; -- ❌ 不生效

👉 设计复合索引时,要把 区分度最高、使用最频繁的列放在前面


3.1.3 覆盖索引(Covering Index)

  • 定义:查询所需的字段全部在索引中,不必回表。

  • 优点:减少一次 I/O,性能显著提升。

例子:

-- 建复合索引
CREATE INDEX idx_user_time ON orders (user_id, create_time);-- 查询只用到了 user_id, create_time
EXPLAIN SELECT user_id, create_time 
FROM orders WHERE user_id = 1001;

👉 Extra: Using index 表示这是覆盖索引,避免了回表。


3.2 单列索引 vs 复合索引

3.2.1 单列索引

  • 优点:维护成本低,适合只针对某个字段查询的场景。

  • 缺点:多个单列索引不能自动合并成一个复合索引(除非优化器选择 Index Merge,但效率通常一般)。

3.2.2 复合索引

  • 优点:适合多条件查询,可以同时利用多个字段过滤。

  • 缺点:设计不合理时,可能导致索引失效。

例子:

-- 单列索引
CREATE INDEX idx_user ON orders (user_id);
CREATE INDEX idx_time ON orders (create_time);-- 复合索引
CREATE INDEX idx_user_time ON orders (user_id, create_time);

查询对比:

EXPLAIN SELECT * FROM orders 
WHERE user_id = 1001 AND create_time > '2025-01-01';
  • 如果只有单列索引,优化器可能用 idx_user,但 create_time 不能同时利用。

  • 如果有复合索引 idx_user_time,则能直接定位到范围区间,性能更高。


3.3 索引优化案例

3.3.1 案例一:电商订单表

表结构:

CREATE TABLE orders (order_id BIGINT PRIMARY KEY,user_id INT,status ENUM('paid','unpaid','cancelled'),create_time DATETIME,INDEX idx_user_status_time (user_id, status, create_time)
);

查询需求:

  • 查询某个用户,在某个状态下的最近订单。

    EXPLAIN SELECT * FROM orders 
    WHERE user_id = 1001 AND status = 'paid' 
    ORDER BY create_time DESC LIMIT 10;
    

分析:

  • 索引 idx_user_status_time 完全覆盖 user_id + status + create_time

  • 不仅能快速过滤,还能按时间顺序直接取 Top N,无需额外排序。

👉 这比单独建索引 user_id + status + create_time 的组合要高效得多。


3.3.2 案例二:用户登录日志表

表结构:

CREATE TABLE login_logs (log_id BIGINT PRIMARY KEY,user_id INT,login_time DATETIME,ip_address VARCHAR(50),INDEX idx_user_time (user_id, login_time)
);

查询需求:

  • 查询某个用户在指定时间段内的登录记录。

    EXPLAIN SELECT user_id, login_time, ip_address 
    FROM login_logs 
    WHERE user_id = 1001 AND login_time BETWEEN '2025-01-01' AND '2025-01-31';
    

分析:

  • 命中复合索引 idx_user_time

  • 只需要扫描某个用户的部分区间,而不是全表。

👉 如果只建了 idx_login_time,则查询效率会很差。


3.4 如何避免索引失效

  1. 避免函数或计算:

    -- ❌ 索引失效
    SELECT * FROM login_logs WHERE YEAR(login_time) = 2025;-- ✅ 索引生效
    SELECT * FROM login_logs 
    WHERE login_time BETWEEN '2025-01-01' AND '2025-12-31';
    

  2. 避免隐式类型转换:

    -- ❌ email 是字符串,但传入数字
    SELECT * FROM users WHERE email = 12345;-- ✅ 参数类型一致
    SELECT * FROM users WHERE email = '12345';
    

  3. 避免无效的 OR 条件:

    -- ❌ user_id 有索引,但 OR 会导致索引失效
    SELECT * FROM orders WHERE user_id = 1001 OR status = 'paid';-- ✅ 改写为 UNION
    SELECT * FROM orders WHERE user_id = 1001
    UNION
    SELECT * FROM orders WHERE status = 'paid';
    


3.5 小结

  • 高选择性列优先建索引,能最大化过滤效果。

  • 复合索引 > 单列索引组合,合理顺序是关键。

  • 覆盖索引可显著减少回表,提升查询效率。

  • 避免函数、隐式转换、OR 等导致索引失效的写法。

  • 结合业务查询场景(订单表、日志表)来设计索引,才能发挥 B+树的最大价值。

第四章:B+树索引的性能调优实践

B+树索引虽然能显著提升查询性能,但如果缺乏合理的调优与维护,它也可能成为性能瓶颈。本章将从索引结构优化、查询优化、存储引擎参数调优、监控与诊断四个角度展开,系统介绍 B+树索引的性能优化方法。


4.1 索引结构优化

4.1.1 合理设计索引列顺序

  • 最左前缀原则:复合索引在查询中必须从最左列开始使用,否则无法利用索引。例如 (a, b, c) 索引,可以支持 (a), (a, b), (a, b, c),但无法直接支持 (b, c)

  • 高选择性优先:高基数(选择性高)的列放在前面,可以快速过滤数据,减少回表次数。

👉 问题诊断:

EXPLAIN SELECT * FROM user WHERE last_name='Zhang' AND age=30;

如果复合索引建在 (age, last_name) 上,就无法高效使用。


4.1.2 避免冗余索引

  • 冗余索引会增加写入开销。例如表上已有 (a, b) 索引时,再创建 (a) 索引通常是多余的。

  • 可通过 pt-duplicate-key-checker 工具或 SHOW INDEX FROM 语句检查重复或冗余索引。


4.1.3 合理选择覆盖索引

覆盖索引能避免回表,例如:

SELECT id, name FROM user WHERE age=30;

如果 (age, id, name) 是索引,查询可直接从索引中获取数据,无需访问数据页。


4.2 查询优化

4.2.1 避免索引失效

以下写法会导致 B+树索引失效:

  • 使用函数:WHERE DATE(create_time) = '2023-08-22'

  • 隐式类型转换:WHERE phone = 13800000000phone 是 VARCHAR)

  • 前模糊匹配:WHERE name LIKE '%abc'

优化方式:

  • 改写为范围:WHERE create_time >= '2023-08-22 00:00:00' AND create_time < '2023-08-23'

  • 显式转换:WHERE phone = '13800000000'

  • 使用倒排索引或全文索引处理前模糊匹配。


4.2.2 利用索引下推(Index Condition Pushdown, ICP)

MySQL 5.6+ 支持 索引下推优化,可在索引扫描阶段提前过滤数据,减少回表。
例如:

SELECT * FROM user WHERE age > 30 AND name LIKE 'A%';

在有 (age, name) 索引时,MySQL 会先在索引中判断 name LIKE 'A%',减少回表次数。


4.3 存储引擎参数调优

4.3.1 调整缓冲池大小

InnoDB 的 Buffer Pool 用于缓存索引页和数据页。

  • 推荐分配 50%-70% 的物理内存innodb_buffer_pool_size

  • 使用 SHOW ENGINE INNODB STATUS\G 查看缓存命中率,若过低应增加 Buffer Pool。


4.3.2 调整页大小与填充因子

  • InnoDB 默认页大小为 16KB。对于大行数据或长字符串索引,可以考虑压缩表或页分裂优化。

  • 合理控制索引列长度(如 VARCHAR(255) 改为 VARCHAR(50)),减少页分裂。


4.3.3 调优索引创建与维护

  • 批量插入时先删除索引,再插入数据,最后重建索引,性能更高。

  • 定期 ANALYZE TABLE 更新索引统计信息,保证优化器选择正确索引。

  • 使用 OPTIMIZE TABLE 减少碎片,提升 B+树的访问效率。


4.4 监控与诊断

4.4.1 使用 EXPLAIN 分析执行计划

SELECT * FROM user WHERE age > 30 AND name LIKE 'A%';

关注:

  • key:是否正确使用索引

  • rows:扫描行数是否过大

  • Extra:是否出现 "Using filesort", "Using temporary"


4.4.2 Performance Schema 与 Sys Schema

  • performance_schema 可监控索引使用情况。

  • sys.schema_unused_indexes 可检查长时间未使用的索引,避免无用索引拖慢写入。


4.4.3 慢查询日志

  • 开启 slow_query_log,分析未使用索引的查询。

  • 配合 pt-query-digest 工具定位热点 SQL,并进一步优化索引。


小结

B+树索引的性能调优涉及:

  1. 索引设计(合理选择列顺序、覆盖索引、避免冗余);

  2. SQL 编写(避免索引失效、利用索引下推);

  3. 参数优化(Buffer Pool、页大小、统计信息更新);

  4. 持续监控与诊断(EXPLAIN、Performance Schema、慢查询日志)。

通过索引与查询的双向优化,结合存储引擎的调优手段,才能确保 B+树索引在实际业务中发挥最大性能优势。

第五章:B+树索引的进阶应用

5.1 组合索引(Composite Index)

5.1.1 定义与特性

组合索引是指在多个列上建立的单个 B+ 树索引,例如 (a, b, c)。它并不是简单的“多个单列索引的并集”,而是将多个列的值作为一个整体键值存储在同一个索引树中。

  • 键值排序规则:按照索引定义的列顺序进行字典序比较。

  • 左前缀原则(Leftmost Prefix Rule):查询条件能否利用索引取决于是否匹配索引的前导列。

5.1.2 使用场景

  • 高频多列条件查询:如 WHERE user_id = ? AND status = ?,适合建立 (user_id, status)

  • 避免回表:组合索引覆盖查询(Covering Index)可显著减少 I/O。

  • 排序优化ORDER BY a, b(a, b) 索引下可以避免额外的排序操作。

5.1.3 优化要点

  • 高选择性列放前面:能更快过滤数据。

  • 考虑查询模式:如果常用条件是 (a, b),就不应把 c 放在第二列。

  • 覆盖索引:在组合索引中包含查询涉及的列,减少回表。


5.2 联合索引优化(Index Merge 与多索引交互)

5.2.1 Index Merge 概念

MySQL 优化器在某些情况下会选择“索引合并”策略:

  • Intersection(交集):同时利用多个索引,并取交集,例如:

    SELECT * FROM t WHERE a = 1 AND b = 2;

    若存在单列索引 (a)(b),优化器可能选择 Index Merge。

  • Union(并集):多个索引结果合并,例如:

    SELECT * FROM t WHERE a = 1 OR b = 2;
  • Sort-Union:对索引结果做合并排序后去重。

5.2.2 优缺点

  • 优点:避免在某些情况下必须创建昂贵的组合索引。

  • 缺点:效率通常低于直接使用组合索引,因为需要额外的合并、去重操作。

5.2.3 实战建议

  • 频繁联合条件:优先考虑建立组合索引。

  • 临时性条件组合:Index Merge 更合适。

  • 优化器可控性有限:必要时可通过 FORCE INDEX 或调整索引设计来干预。


5.3 分区索引(Partitioned Index)

5.3.1 分区表与索引类型

在分区表中,索引策略和普通表有区别:

  • 本地索引(Local Index):每个分区单独维护一棵索引树,仅能检索该分区的数据。

  • 全局索引(Global Index)(MySQL 8.0.13+ 支持):一棵跨分区的全局索引,适合全局唯一约束和跨分区查询。

5.3.2 优化点

  • 分区裁剪(Partition Pruning):查询条件中包含分区键时,只扫描必要分区,大幅减少 I/O。

  • 分区键与索引键结合:常见模式是将分区键作为组合索引的前缀,提高命中率。

  • 避免全表扫描:对于不包含分区键的查询,优化器可能访问所有分区,导致索引失效。

5.3.3 应用场景

  • 时间序列数据:如日志表,按时间字段分区,并在 (partition_key, user_id) 上建索引。

  • 大数据量表:单表规模达到数亿行时,通过分区索引减少检索范围。


5.4 实战案例

  1. 组合索引与覆盖查询
    电商订单表 (order_id, user_id, status, create_time)

    • 索引 (user_id, status, create_time) 支持:

      SELECT create_time FROM orders WHERE user_id = ? AND status = ? ORDER BY create_time DESC LIMIT 10;

      → 既利用了索引过滤,又避免了回表和排序。

  2. Index Merge 的补救措施
    如果已有 (a)(b) 索引,查询 WHERE a = ? AND b = ? 能触发 Index Merge。若该查询极为频繁,建议直接创建 (a, b) 索引。

  3. 分区索引与分区裁剪
    日志表 logs (log_id, user_id, log_date, message),按 log_date 范围分区:

    • 索引 (log_date, user_id)

    • 查询:

      SELECT * FROM logs WHERE log_date BETWEEN '2025-01-01' AND '2025-01-31' AND user_id = 123;

      → 优化器仅访问 1 月份分区,并使用二级索引快速定位。


本章总结
B+ 树索引的进阶应用包括:

  • 组合索引:通过合理顺序和覆盖查询提升性能。

  • 联合优化:Index Merge 在特定场景下有用,但通常不如组合索引高效。

  • 分区索引:结合分区裁剪和局部/全局索引设计,能显著减少大数据表的检索开销。

这些高级技巧的核心思想是:根据实际查询模式选择合适的索引策略,而不是盲目堆叠索引

第六章:B+树索引在不同业务场景下的设计模式

B+树索引作为 MySQL 最核心的索引结构,其设计和使用策略必须与业务场景紧密结合。不同的业务场景(OLTP、OLAP、混合型 HTAP)在查询模式、数据规模、访问频率、延迟要求上都有显著差异,因此索引设计也呈现出不同的模式。

6.1 OLTP 场景下的索引设计模式

OLTP(On-Line Transaction Processing,联机事务处理)场景以高并发、小事务、点查询和频繁更新为典型特征,如银行转账、订单管理、电商交易系统。

6.1.1 特点

  • 事务短小、请求频繁

  • 单行读写为主,更新和插入操作占比较高

  • 对延迟极为敏感(毫秒级)

6.1.2 索引设计模式

  1. 主键索引优先(聚簇索引)

    • OLTP 中主键查询极其常见(如订单号、用户ID),因此主键通常设计为整型自增或雪花算法生成的数值型 ID,保证聚簇索引的有序性和插入效率。

  2. 高选择性单列索引

    • 针对用户手机号、订单号等高选择性字段,建立单列索引可以加速点查。

  3. 组合索引覆盖查询

    • OLTP 查询往往只涉及少量字段,设计“覆盖索引”(覆盖 SELECT 所需列)能避免回表,减少磁盘 I/O。

  4. 避免过多索引

    • OLTP 更新和插入频繁,过多索引会导致写入放大。通常建议核心表保持 3~5 个关键索引即可。

6.1.3 示例

CREATE TABLE orders (order_id BIGINT PRIMARY KEY AUTO_INCREMENT,user_id BIGINT NOT NULL,status TINYINT NOT NULL,created_at DATETIME NOT NULL,INDEX idx_user_status_created (user_id, status, created_at)
) ENGINE=InnoDB;
  • 主键:自增 ID(聚簇索引)

  • 组合索引:(user_id, status, created_at) 用于高频查询场景:

    SELECT * FROM orders 
    WHERE user_id = ? AND status = ? 
    ORDER BY created_at DESC LIMIT 10;
    


6.2 OLAP 场景下的索引设计模式

OLAP(On-Line Analytical Processing,联机分析处理)场景以大规模数据扫描、复杂聚合查询为典型特征,如报表统计、BI 分析。

6.2.1 特点

  • 查询涉及百万级甚至亿级数据

  • 聚合、排序、分组频繁

  • 对吞吐量要求高,对单次查询延迟容忍度较高

6.2.2 索引设计模式

  1. 宽组合索引

    • 针对典型报表查询的多列条件(如时间 + 地域 + 类别),构建多列索引,减少回表和全表扫描。

  2. 前缀索引与分区索引结合

    • 如果存在长字符串列(如 URL、日志关键字),可以使用前缀索引结合分区裁剪,加速范围扫描。

  3. 降维覆盖索引

    • 通过索引只存储分析需要的少数字段(如时间戳、金额),配合 INDEX ONLY SCAN,减少磁盘访问量。

  4. 索引与分区表结合

    • OLAP 查询通常带有时间范围条件,可通过时间分区(按日/月),结合局部索引,实现“分区裁剪 + 局部索引”优化。

6.2.3 示例

CREATE TABLE sales (id BIGINT AUTO_INCREMENT PRIMARY KEY,region_id INT NOT NULL,category_id INT NOT NULL,sale_date DATE NOT NULL,amount DECIMAL(10,2) NOT NULL,INDEX idx_region_category_date (region_id, category_id, sale_date)
) PARTITION BY RANGE (YEAR(sale_date)) (PARTITION p2019 VALUES LESS THAN (2020),PARTITION p2020 VALUES LESS THAN (2021),PARTITION p2021 VALUES LESS THAN (2022),PARTITION pmax VALUES LESS THAN MAXVALUE
);
  • 分区:按年份分区

  • 索引:(region_id, category_id, sale_date),典型报表查询:

    SELECT region_id, category_id, SUM(amount)
    FROM sales
    WHERE sale_date BETWEEN '2020-01-01' AND '2020-12-31'
    GROUP BY region_id, category_id;
    


6.3 混合型(HTAP)场景下的索引设计模式

HTAP(Hybrid Transactional/Analytical Processing)场景既包含 OLTP 高频事务,又包含 OLAP 分析查询,如电商系统:既要支持下单、支付,又要支持实时大屏分析。

6.3.1 特点

  • 数据同时面向实时交易与统计分析

  • 读写混合压力大

  • 既要保证事务延迟低,又要保证统计查询不拖慢核心业务

6.3.2 索引设计模式

  1. 冷热数据分离 + 差异化索引

    • 热数据(近期订单)保持轻量索引,保证写入性能;冷数据(历史归档)建立更复杂的分析型索引。

  2. 组合索引 + 分区索引混合

    • 核心表通过组合索引支撑事务查询,同时利用分区索引支撑报表分析。

  3. 覆盖索引 + 冗余索引

    • 对于事务型查询,使用覆盖索引加速。

    • 对于分析型查询,可增加冗余索引字段(即存储冗余列到索引里),减少回表。

6.3.3 示例

CREATE TABLE order_logs (order_id BIGINT NOT NULL,user_id BIGINT NOT NULL,status TINYINT NOT NULL,created_at DATETIME NOT NULL,amount DECIMAL(10,2) NOT NULL,PRIMARY KEY (order_id),INDEX idx_user_created (user_id, created_at),INDEX idx_status_created_amount (status, created_at, amount)
) PARTITION BY RANGE (YEAR(created_at)) (PARTITION p2021 VALUES LESS THAN (2022),PARTITION p2022 VALUES LESS THAN (2023),PARTITION pmax VALUES LESS THAN MAXVALUE
);
  • OLTP 查询(事务场景):

    SELECT * FROM order_logs WHERE user_id = ? ORDER BY created_at DESC LIMIT 10;
  • OLAP 查询(分析场景):

    SELECT status, SUM(amount) FROM order_logs WHERE created_at BETWEEN '2022-01-01' AND '2022-12-31' GROUP BY status;

6.4 小结

  • OLTP 场景:主键索引 + 高选择性单列索引 + 覆盖索引,避免过多索引。

  • OLAP 场景:宽组合索引 + 分区索引 + 降维覆盖索引,优化大规模扫描。

  • HTAP 场景:冷热分离 + 分区结合 + 冗余索引,兼顾事务与分析需求。

B+树索引的设计没有“一刀切”的方案,而是要根据业务场景的特性选择合适的模式,从而在性能、空间与维护成本之间取得平衡。

http://www.dtcms.com/a/344645.html

相关文章:

  • QT之QSS的使用方法和常用控件的样式设置
  • Qt 的事件类QEvent及其他子类事件的开发详解:从基础到实践的全方位指南
  • 高并发用户数峰值对系统架构设计有哪些影响?
  • Qt-窗口类部件
  • 极验demo(float)(一)
  • 数据结构:队列 二叉树
  • vivo“空间计算-机器人”生态落下关键一子
  • 码蹄杯进阶
  • 笔试——Day46
  • 基于SpringBoot+Vue框架的高校论坛系统 博客论坛系统 论坛小程序
  • 企业版Idea 无快捷键的启动方式
  • 和AI Agent一起读论文——A SURVEY OF S ELF EVOLVING A GENTS(五)
  • 如何监控和管理微服务之间的调用关系
  • 微信开发者工具:更改 AppID 失败
  • Unreal Engine Class System
  • 滑动窗口+子串+普通数组算法
  • Spring AI调用本地大模型实战
  • 【LINUX】CentOS7在VMware15中,从命令行界面切换到图形界面的异常汇总
  • Day10 Go语言深入学习(2)
  • 零成本 Redis 实战:用Amazon免费套餐练手 + 缓存优化
  • skywalking-agent与logback-spring.xml中的traceId自动关联的原理
  • 使用C#的 PdfDocument 实现 PDF 页眉页脚的编辑
  • 我用Photoshop Firefly+Blender,拯救被环境毁掉的人像大片
  • Blender模型动画导入到UE5
  • uniappx新增的几个api
  • AI + 教育:个性化学习如何落地?教师角色转变与技术伦理的双重考验
  • 文字提取技术让文档实现数字化效率翻倍-文字识别接口
  • Kubernetes概念:ETCD 的本质与备份恢复实践
  • 永磁同步电机控制算法-反馈线性化滑模直接转矩控制
  • 智慧工厂烟雾检测:全场景覆盖与精准防控