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+树的优势
IO效率高:非叶子节点只保存键值和指针,一个节点能容纳更多索引键,树的高度更低。
范围查询高效:叶子节点链表结构让范围扫描非常顺畅。
顺序访问友好:天然支持
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 如何避免索引失效
避免函数或计算:
-- ❌ 索引失效 SELECT * FROM login_logs WHERE YEAR(login_time) = 2025;-- ✅ 索引生效 SELECT * FROM login_logs WHERE login_time BETWEEN '2025-01-01' AND '2025-12-31';
避免隐式类型转换:
-- ❌ email 是字符串,但传入数字 SELECT * FROM users WHERE email = 12345;-- ✅ 参数类型一致 SELECT * FROM users WHERE email = '12345';
避免无效的 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 = 13800000000
(phone
是 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+树索引的性能调优涉及:
索引设计(合理选择列顺序、覆盖索引、避免冗余);
SQL 编写(避免索引失效、利用索引下推);
参数优化(Buffer Pool、页大小、统计信息更新);
持续监控与诊断(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 实战案例
组合索引与覆盖查询
电商订单表(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;
→ 既利用了索引过滤,又避免了回表和排序。
Index Merge 的补救措施
如果已有(a)
和(b)
索引,查询WHERE a = ? AND b = ?
能触发 Index Merge。若该查询极为频繁,建议直接创建(a, b)
索引。分区索引与分区裁剪
日志表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 索引设计模式
主键索引优先(聚簇索引)
OLTP 中主键查询极其常见(如订单号、用户ID),因此主键通常设计为整型自增或雪花算法生成的数值型 ID,保证聚簇索引的有序性和插入效率。
高选择性单列索引
针对用户手机号、订单号等高选择性字段,建立单列索引可以加速点查。
组合索引覆盖查询
OLTP 查询往往只涉及少量字段,设计“覆盖索引”(覆盖
SELECT
所需列)能避免回表,减少磁盘 I/O。
避免过多索引
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 索引设计模式
宽组合索引
针对典型报表查询的多列条件(如时间 + 地域 + 类别),构建多列索引,减少回表和全表扫描。
前缀索引与分区索引结合
如果存在长字符串列(如 URL、日志关键字),可以使用前缀索引结合分区裁剪,加速范围扫描。
降维覆盖索引
通过索引只存储分析需要的少数字段(如时间戳、金额),配合
INDEX ONLY SCAN
,减少磁盘访问量。
索引与分区表结合
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 索引设计模式
冷热数据分离 + 差异化索引
热数据(近期订单)保持轻量索引,保证写入性能;冷数据(历史归档)建立更复杂的分析型索引。
组合索引 + 分区索引混合
核心表通过组合索引支撑事务查询,同时利用分区索引支撑报表分析。
覆盖索引 + 冗余索引
对于事务型查询,使用覆盖索引加速。
对于分析型查询,可增加冗余索引字段(即存储冗余列到索引里),减少回表。
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+树索引的设计没有“一刀切”的方案,而是要根据业务场景的特性选择合适的模式,从而在性能、空间与维护成本之间取得平衡。