1. 时序数据库索引概述:为什么时序数据需要特殊索引?B+树与LSM树的对比

大家好,我是这次课程的主讲。做了十几年数据库内核,我见过太多团队在时序数据上栽跟头——明明业务跑得好好的,一到查询历史趋势就卡死,磁盘IO直接打满。说白了,问题往往出在索引上。

今天咱们先聊聊最基础的问题:为什么普通数据库的索引,到了时序场景就不灵了?

1.1 时序数据的“脾气”

时序数据有什么特点?我总结了三句话:

  • 写入量大——每秒几万、几十万条数据是常态
  • 追加为主——数据几乎不更新、不删除
  • 按时间范围查询——查最近一小时、最近一天,很少查单条

你想想看,如果用传统的关系型数据库存这些数据,会怎样?

我遇到过一家做IoT的公司,他们用MySQL存设备上报数据,每天写入量大概5亿条。结果呢?一个月后,单表数据量超过150亿行,写入延迟从2毫秒飙升到200毫秒,查询最近一小时的数据要等十几秒。这就是典型的“索引灾难”。

核心矛盾:传统B+树索引是为“读写均衡”设计的,而时序数据是“写多读少、追加为主”。用错索引,性能直接崩盘。

1.2 B+树:为什么在时序场景“水土不服”?

B+树大家都很熟悉了。MySQL的InnoDB、PostgreSQL都用它。它的优点是读写均衡,随机读性能好。但到了时序场景,问题就暴露了。

问题一:写放大严重

B+树是原地更新的结构。每插入一条数据,都要找到对应的叶子节点,如果节点满了还要分裂。时序数据是连续写入的,时间戳递增,按理说应该顺序写。但B+树的索引键如果包含设备ID+时间戳,写入就变成随机写——因为不同设备的数据会散落在不同叶子节点上。

我记得有一次帮一个金融客户调优,他们的时序表主键是(device_id, timestamp)。写入TPS只有3000,磁盘IO却跑满了。一查,B+树的写放大系数达到了15倍——每写1KB数据,实际要写15KB到磁盘。

问题二:空间利用率低

B+树的节点分裂会导致大量空洞。时序数据频繁写入,叶子节点分裂频繁,空间利用率经常掉到60%以下。你想想看,40%的磁盘空间就这么浪费了。

问题三:范围查询效率不高

虽然B+树支持范围查询,但时序数据的时间范围查询往往跨多个叶子节点。如果索引键不是以时间戳开头,查询效率更差。比如查“某个设备最近一小时的数据”,如果索引是(device_id, timestamp),那还好;如果是(timestamp, device_id),就要扫描大量无关数据。

对比维度 B+树 时序场景表现
写入模式 原地更新,随机写 写放大严重,IO压力大
空间利用率 约60%-70% 频繁分裂导致空洞多
范围查询 支持,但依赖索引顺序 时间范围查询可能跨多节点
并发写入 行锁/页锁,冲突多 高并发下锁竞争激烈

避坑指南:我曾经见过有人用MySQL的B+树存时序数据,还加了自增ID做主键。结果查询时间范围时,必须全表扫描——因为时间戳不是索引的前缀列。千万注意:时序数据的索引设计,第一原则就是“时间戳必须放在索引的最左侧”。

1.3 LSM树:时序数据库的“救星”

LSM树(Log-Structured Merge-Tree)是另一种思路。它不追求“写的时候就把数据排好序”,而是先写日志,再在后台合并排序。

LSM树的核心思想:

  • 写入就是追加——数据先写到内存中的MemTable,再刷到磁盘上的SSTable
  • 顺序写为主——SSTable是顺序写入的,没有随机写
  • 后台合并——多个SSTable在后台合并、排序、去重

说白了,LSM树把“写操作”的成本转移到了“读操作”和“合并操作”上。对于写多读少的时序场景,这简直是量身定做。

LSM树的优势:

  • 写入吞吐高——顺序写,没有随机IO,写放大系数通常只有2-3倍
  • 空间利用率高——SSTable是紧凑的,没有空洞
  • 适合时间范围查询——数据按时间排序存储,范围查询就是顺序扫描

但LSM树也有缺点。读放大比较严重——查一条数据可能要查多个SSTable。不过时序场景很少查单条数据,所以这个缺点可以接受。

对比维度 B+树 LSM树
写入方式 原地更新,随机写 追加写,顺序写
写放大系数 10-20倍 2-3倍
读放大系数 1-2倍 5-10倍(需合并)
空间利用率 60%-70% 90%以上
适用场景 读写均衡,事务型 写多读少,分析型

个人经验:我建议你在选型时,先评估写入量和查询模式。如果写入量超过每秒1万条,且查询以时间范围为主,直接上LSM树。如果写入量不大,但需要复杂的事务支持,B+树更合适。

1.4 一张图看懂:B+树 vs LSM树

下面这张图展示了两种索引结构的核心差异。我画得比较直观,你可以看到B+树是“原地排序”,LSM树是“先写后合并”。

B+树与LSM树核心架构对比 B+树 vs LSM树:核心架构对比 B+树(原地更新) 根节点 内部节点 内部节点 叶子节点 叶子节点 叶子节点 ⚠ 写入时原地更新 → 随机写、节点分裂 写放大系数 10-20 倍,空间利用率低 LSM树(追加写 + 合并) MemTable(内存) 刷盘 SSTable L0 合并 SSTable L1 合并 SSTable L2 ✅ 顺序写 → 写放大 2-3 倍 空间利用率 90%+,适合高吞吐写入

1.5 实际选型建议

说了这么多,到底怎么选?我给出几条实际建议:

  1. 写入量超过每秒1万条——直接上LSM树。B+树扛不住这个量级。
  2. 查询以时间范围为主——LSM树更合适。数据按时间排序,范围查询就是顺序扫描。
  3. 需要强事务支持——B+树更成熟。LSM树的事务实现复杂,性能也差一些。
  4. 硬件资源有限——LSM树对CPU和内存要求更高(合并操作消耗CPU)。如果机器配置低,B+树可能更稳。

我的习惯:做技术选型时,我会先跑一个简单的压测。用同样的数据量、同样的查询模式,分别测B+树和LSM树的写入延迟P99和查询延迟P99。数据不会骗人。别光看理论,一定要动手测。

嗯,这一章就到这里。记住一句话:时序数据用B+树,就像用卡车运快递——能运,但效率太低。LSM树才是为这个场景设计的“快递分拣线”。