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树是“先写后合并”。
1.5 实际选型建议
说了这么多,到底怎么选?我给出几条实际建议:
- 写入量超过每秒1万条——直接上LSM树。B+树扛不住这个量级。
- 查询以时间范围为主——LSM树更合适。数据按时间排序,范围查询就是顺序扫描。
- 需要强事务支持——B+树更成熟。LSM树的事务实现复杂,性能也差一些。
- 硬件资源有限——LSM树对CPU和内存要求更高(合并操作消耗CPU)。如果机器配置低,B+树可能更稳。
我的习惯:做技术选型时,我会先跑一个简单的压测。用同样的数据量、同样的查询模式,分别测B+树和LSM树的写入延迟P99和查询延迟P99。数据不会骗人。别光看理论,一定要动手测。
嗯,这一章就到这里。记住一句话:时序数据用B+树,就像用卡车运快递——能运,但效率太低。LSM树才是为这个场景设计的“快递分拣线”。