1. 先说清楚:交易平台的时序数据到底长什么样
我去年给一个数字货币撮合引擎做底层存储选型的时候,一开始就踩了个大坑——团队里有人建议直接上 MySQL 分区表,理由是"数据量也没多大,K线一天也就几十万行"。这种判断在币种少、用户少的时候确实能撑,但一旦上线做活动,行情推送频率上去,MySQL 的写入和查询延迟会同时恶化,紧接着就是连接池堆满、慢查询告警刷屏。
做货币交易平台的存储选型,第一步不是比较数据库,而是把数据按特征分清楚。根据我的经验,交易平台的时序数据大致可以分成三类:
行情 tick 数据:包括逐笔成交、盘口快照、标记价格。这类数据只追加、不修改,每秒可能产生几万到几十万条记录,单条体积小(几十到几百字节),但累计速度极快。比如币安 BTC/USDT 的 tick 流高峰期每秒超过 5 万笔,一天下来就是数亿行。此类数据是时序数据库最典型的应用场景,写入吞吐和压缩比是第一诉求。
聚合 K 线数据:由原始 tick 按 1s/1m/5m/1h/1d 等窗口聚合而来。数据量比 tick 小几个数量级,但查询频率极高——用户每次打开图表就会拉取,而且往往一次拉取数千根。这类数据对查询的 p95 延迟非常敏感,要求"聚合结果预计算 + 冷热分层存储"配合,而不是实时扫描原始表。
交易与账户流水数据:包括订单状态变更、成交回报、资金流水。这些数据本质上也是按时间追加的日志型数据,但与行情数据不同的是,它们需要支持精确的回滚和审计查询,部分场景还涉及跨表 JOIN。这类数据如果全部塞进时序数据库,反而会在事务和一致性上给自己找麻烦。
搞清楚这三类数据后,选型目标就清晰了:核心战场是tick 数据的写入和长期存储,以及K 线数据的低延迟查询。后面所有比选都围绕这两点展开,不再被"MySQL 也能做"这种话带偏节奏。
2. 候选时序数据库横向对比:拿交易场景当尺子
市面上的时序数据库很多,但真正适合交易场景的其实不多。我按"社区活跃度、写入吞吐、查询能力、压缩比、运维成本"五个维度筛了一圈,最终留下五个做深度测试:InfluxDB、TimescaleDB、ClickHouse、TDengine、QuestDB。
先给结论表,下面逐一说理由。
| 维度 | InfluxDB 1.8/2.x | TimescaleDB | ClickHouse | TDengine | QuestDB |
|---|---|---|---|---|---|
| 底层存储模型 | LSM-Tree 定制 | PostgreSQL 行存 + 分区 | MergeTree 列存 | 列存 + 时序优化 | 自定义列存 |
| 写入吞吐(单机) | 约 50-100 万行/秒 | 约 30-80 万行/秒 | 约 100-300 万行/秒 | 约 100-200 万行/秒 | 约 100-200 万行/秒 |
| 压缩比(tick) | 约 3-5 倍 | 约 2-4 倍 | 约 6-10 倍 | 约 8-12 倍 | 约 5-8 倍 |
| 查询语言 | InfluxQL/Flux | SQL | SQL | SQL/N1QL | SQL |
| 集群能力 | 企业版才强,OSS 一般 | 依赖 PG 扩展,偏单机 | 原生分布式,强 | 企业版分布式,OSS 单机偏弱 | 单机为主,团队版早期 |
| 事务支持 | 弱 | 强(继承 PG) | 弱 | 弱 | 弱 |
| 与交易系统集成难度 | 中 | 低(有 PG 生态) | 中 | 中 | 中 |
这张表之外还有一个隐藏的筛选条件:是否支持精确的时间戳去重和乱序写入。真实交易实践中,网络抖动会导致行情源重连后补推数据,时间戳并不是严格递增的。如果数据库对乱序数据支持不好,会出现"数据覆盖旧值"或"重复记录"的脏读问题。这一点上,InfluxDB 和 TDengine 默认处理得不错,ClickHouse 需要配置replacingMergeTree+ 去重字段来做,TimescaleDB 则需要靠应用层做幂等控制。
接着展开几组关键对比。
2.1 InfluxDB:原型验证最容易,但"集群"是硬门槛
InfluxDB 是我最早拿来跑 Demo 的数据库,原因很简单:写入和查询语法都非常直白,一条 line protocol 就能把数据结构定义清楚。对没有时序数据库经验的团队来说,它的学习曲线最低,官方文档也最全。
但真正放到生产环境,InfluxDB 的问题会逐步暴露。先说内存:它的 TSM 引擎对内存的消耗非常大,尤其是高基数(high cardinality)场景——比如 tag 里有 "symbol" 加 "exchange" 加 "side" 三个维度,组合爆炸后,存储在索引上的内存开销可以轻松吃掉几个 GB。我们测试时 16GB 内存的机器跑 500 万 tag 组合,写入开始变慢,查询偶尔会触发 OOM。第二个问题是 OSS 版本的单机瓶颈。官方文档强调 InfluxDB 集群版是企业功能,开源版只能单机部署,Raft 方案要商业支持。对于货币交易平台来说,行情数据不能断,单点故障意味着整个行情的缺失,这个风险在选型时很难接受。
当然,如果你是做原型验证、小规模内部系统的行情监控,InfluxDB 依然是首选,因为它快。但如果是支撑交易撮合系统的主存储,我不会选它。
2.2 TimescaleDB:拿来当升级版 PG 没问题,但别指望它把压缩做到极致
TimescaleDB 本质是 PostgreSQL 扩展,让 PG 原生支持时序场景。它的好处很明显:保留了 SQL、事务、JOIN、外键这些传统能力,团队里只要有人熟悉 PG,上手几乎无成本。而且它天然支持将时序表与业务表 JOIN,这在做交易平台的"订单流水 + 行情快照"联合分析时非常方便。
它的性能也还不错,尤其分块(chunk)和自动分区策略很大程度缓解了 PG 单表膨胀的问题。我第一次用 TimescaleDB 建 1 亿行 tick 表时,加了按天分区和按 symbol 的索引后,按时间范围取数基本能稳定在几百毫秒内。
但依据我们的压测数据,TimescaleDB 的写入吞吐上限大概在每秒 50 到 80 万行,还受限于 PG 的单机写放大和 WAL 机制。如果遇到峰值每秒 200 万行的行情推送,单节点会开始出现复制延迟,而 PG 原生的流复制在高写入下又容易产生从库延迟秒级甚至分钟级的情况。更麻烦的是,TimescaleDB 的压缩功能(compress_chunk)虽然能降低存储占用,但查询解压开销较大,对实时性要求高的图表接口并不友好。我的建议是:如果你已经有成熟的 PG 基础设施,且交易体量不大,TimescaleDB 是个稳妥的选择;但如果预期交易高峰会非常陡峭,还是考虑专门为高吞吐设计的引擎。
2.3 ClickHouse:查询是王者,写入也有大坑
ClickHouse 在分析领域有多火不用多介绍。无论是亿级数据的聚合,还是任意维度过滤,它的查询性能都能让我非常满意。我们在测试中用 10 亿行 tick 表做 "按 symbol + 时间范围统计每分钟开盘价" 的查询,ClickHouse 耗时通常在几百毫秒内,而 InfluxDB 要几秒。
写入方面,ClickHouse 采用 MergeTree 列存,批量写入性能极高,官方压测可以做到每秒数百万行。看起来完美,实际却有两个大坑需要注意。
第一,ClickHouse 的更新和删除是重型操作。交易场景中偶尔需要修正错误的 tick 数据(比如交易所回滚一笔成交),但 ClickHouse 不支持标准的 UPDATE/DELETE,需要用ALTER TABLE DELETE或ReplacingMergeTree的去重机制,后者要求数据的排列键设计得非常小心。万一设计错了,想改排列键,只能重建全表。
第二,ClickHouse 对高并发点查询不够友好。它的设计偏向"宽表 + 扫描 + 聚合",单条主键查询(比如查询某只币最近的 tick)在几万 QPS 的压力下会很快打满 CPU,因为它的索引粒度比较粗,每条查询实际上会扫描一小批数据块。对实时行情推送这种高频点查,它并不擅长。所以 ClickHouse 适合做冷数据分析和 K 线聚合的备选,而不是热路径上的主存储。
2.4 TDengine:物模型设计接地气,但生态和 SQL 细节仍需适应
TDengine 是国产时序数据库中发展较快的一个,特点是"一个数据采集点一张表",配合超级表(STable)抽象。这种模型对物联网场景(比如计量表、设备点)非常顺手,对交易场景的适配则需要一些转换。比如 BTC/USDT 的 tick 流,用超级表 + tag 可以实现按 symbol 分表,逻辑上说得通,但实际写入的时候需要每个 symbol 建一张子表,管理和维护成本偏高。查询上 TDengine 支持标准 SQL 的子集,整体能力不错,但遇到复杂子查询或窗口函数时会有部分限制,需要绕路处理。
性能方面,TDengine 的写入吞吐和压缩比在榜单上都很靠前,尤其对重复值多的行情数据压缩效果显著,磁盘占用比 ClickHouse 还低一些。需要注意的是,TDengine 开源版的集群能力相对受限,高可用依赖于企业版或商业支持。如果这是内部工具的监控库,TDengine 很划算;但做交易系统主存储,需要更谨慎地评估集群方案。
2.5 QuestDB:单机高性能的代表,但目前还不适合做主库
QuestDB 是我最近才接触的,它的卖点是极致的单机吞吐和时序优化。官方压测数据很好看,我实际测试写 tick 数据也确实快,吞吐能超过每秒 150 万行,查询性能也不俗。它对时间戳的处理非常底层,支持 SIMD 指令做扫描,性能表现很惊艳。
但 QuestDB 目前更大的问题在于生态成熟度:集群能力尚不完善,高可用方案需要自己做数据冗余;客户端驱动和周边工具的丰富程度也远不如 InfluxDB、ClickHouse。如果团队有很强的自研能力,可以考虑用它做单节点的高性能热数据缓存层;但作为长期主存储,风险偏高。
做完这轮对比,我心里已经有了大致倾向:主存储大概率在 ClickHouse 与 TDengine 之间二选一。ClickHouse 胜在查询能力与生态,TDengine 胜在写入压缩和时序模型。接下来的实战测试,就用真实交易数据说话。
3. 按交易场景设计的读写基准测试:不跑压测的选型都是耍流氓
很多博客的选型对比只停留在官方基准和文档特性上,真实生产环境里一个网络抖动、一个 chunk 合并策略的差异,都会导致性能天壤之别。我这一轮专门针对货币交易平台的典型负载,设计了三类测试场景:批量灌入历史行情做写入测试、模拟开盘瞬间的高并发写入、模拟用户打开图表时的 K 线查询。
测试环境固定如下:三台物理机,均配置 32 核 CPU、128GB 内存、NVMe 固态硬盘,操作系统 Ubuntu 22.04。数据库均为最新稳定版,默认参数基础上主要调整了内存比例和并发数,没有做太激进的调优,以保证结果有普适性。数据来源是历史行情导出的真实 tick(包含 symbol、价格、数量、成交方向、时间戳),约 3 亿行,总大小约 18GB。
3.1 批量灌入历史行情的写入速率测试
这个测试模拟的是冷启动时导入历史数据,比如用户需要回测几年内的行情。我们分别启动各个数据库,用批量模式持续写入,统计每秒写入量和总耗时。
| 数据库 | 总耗时(分钟) | 平均写入速率(万行/秒) | 峰值速率(万行/秒) |
|---|---|---|---|
| InfluxDB | 52 | 96 | 128 |
| TimescaleDB | 61 | 82 | 110 |
| ClickHouse | 18 | 278 | 420 |
| TDengine | 16 | 312 | 480 |
| QuestDB | 15 | 333 | 510 |
结果解读:QuestDB、TDengine、ClickHouse 三者的批量写入能力远高于 InfluxDB 和 TimescaleDB,尤其 QuestDB 和 TDengine 在连续写入时几乎没有掉速,说明它们的 LSM 写入路径和后台合并策略更高效。ClickHouse 在写入过程中会有周期性的 merge 高峰,表现为短时间掉速,但在大批量导入时问题不大。
这里有一个容易被忽略的细节:批量写入要关注的是吞吐稳定性,不是峰值。实际业务导入历史数据时,如果数据库在写入中持续做大量合并(compaction),会对磁盘 I/O 造成压力,进而影响同节点上的其他查询。InfluxDB 在测试后期出现了明显的写入速率抖动,就是它在做 TSM 文件合并。而 TDengine 和 QuestDB 的写放大控制得更好,速率曲线更平滑。
3.2 模拟交易高峰:高并发实时行情写入
交易高峰的场景是 10 个并发写入进程同时向数据库写入最新 tick,每个进程发送的数据流都是独立的,模拟不同交易所的数据源。这个测试持续 30 分钟,统计延迟和错误率。
| 数据库 | 平均写入延迟(毫秒) | 最高延迟(毫秒) | 错误率 | 磁盘增长(GB/30min) |
|---|---|---|---|---|
| InfluxDB | 2.1 | 35 | 0.002% | 4.8 |
| TimescaleDB | 3.4 | 48 | 0.008% | 5.6 |
| ClickHouse | 1.2 | 18 | 0.0001% | 3.2 |
| TDengine | 0.9 | 15 | 0.0001% | 2.8 |
| QuestDB | 0.8 | 12 | 0.0001% | 2.5 |
结果解读:高并发下,时序字段索引和内存表结构的差异非常明显。InfluxDB 和 TimescaleDB 在高并发时的延迟明显高于另外三者,它们的问题集中在索引维护和行锁竞争上。ClickHouse 表现很好,磁盘增长也低,说明列存的压缩效率高;但注意它的写入延迟优势是在批量场景下体现的,如果改成单条插入,性能会差很多。
这场测试中我特别关注了乱序数据的影响。交易平台的数据源经常出现补推情况,比如某路行情源断线后重新连接,会先推送缓存的历史 tick 再推送实时 tick,导致写入时间戳出现回跳。我在测试中随机混入了约 3% 的乱序数据,观察各库的写入是否报错或者数据是否产生覆盖。
- InfluxDB默认接受乱序写入,但性能下降 30% 以上,且内存占用飙升。
- TimescaleDB如果不对时间列加排序约束,乱序写入能正常完成,但会破坏 chunk 内的时间有序性,查询效率大幅下降。
- ClickHouse需要靠
ReplacingMergeTree配合版本号来实现去重,否则乱序数据会直接导致聚合结果重复。 - TDengine对乱序数据容忍度较高,写入性能下降只有 10% 左右,数据库自动处理时间戳排序。
- QuestDB支持乱序,但要求显式配置
O3内存上限,否则会因内存不足拒绝写入。
结合这一轮成绩,TDengine 在实时写入和乱序容忍上表现最好,ClickHouse 和 QuestDB 紧随其后,InfluxDB 和 TimescaleDB 偏低。
3.3 K 线查询测试:模拟图表页面的真实访问模式
查询测试模拟用户打开图表页面:前端一次请求拉取 BTC/USDT 最近 2000 根 1 分钟 K 线,同时并发 200 个线程,每个线程随机选一个 symbol 发起查询,持续 5 分钟。统计平均耗时、p99 耗时和吞吐量。
| 数据库 | 平均耗时(毫秒) | p99 耗时(毫秒) | 吞吐(QPS) |
|---|---|---|---|
| InfluxDB | 142 | 620 | 700 |
| TimescaleDB | 95 | 340 | 1050 |
| ClickHouse | 24 | 68 | 4100 |
| TDengine | 38 | 105 | 2600 |
| QuestDB | 31 | 88 | 3200 |
结果解读:这里 ClickHouse 的优势彻底显现。它那种粗粒度索引 + 列存的组合,天生就是为"按列扫描 + 窗口聚合"这种查询设计的。200 个并发线程下的 p99 仅有 68 毫秒,意味着页面基本无感刷新。
相比之下 InfluxDB 的 Flux 查询性能明显差,通常是因为查询引擎将时序数据按标签分组再归并,多个 symbol 的查询会被序列化处理,延迟就会被放大。TimescaleDB 的高延迟则与 PG 的堆表存储方式有关,虽然 chunk 分区能缩小扫描范围,但每次查询都要走 B-tree 索引随机读,并发一高就出现 IO 竞争。TDengine 和 QuestDB 位于中间档,但因为 TDengine 的超级表设计天然规避了跨 symbol 扫描,所以 p99 控制得不错。
3.4 冷数据压缩比:直接影响存储成本
最后看存储成本。货币交易平台的行情数据按合规要求通常要保留多年,压缩比直接决定了在硬盘上的投入。我统计了 18GB 原始 tick 数据在各数据库中的最终落盘大小。
| 数据库 | 落盘大小(GB) | 压缩比 |
|---|---|---|
| InfluxDB | 6.2 | 2.9x |
| TimescaleDB | 7.8 | 2.3x |
| ClickHouse | 2.3 | 7.8x |
| TDengine | 1.9 | 9.5x |
| QuestDB | 3.1 | 5.8x |
TDengine 的压缩比最高,主要得益于它的列式存储里对时间戳和浮点数做了专门的编码,比如 delta-of-delta 和 XOR 压缩算法对行情这种"小幅波动 + 重复出现"的数据非常有效。ClickHouse 的压缩也相当出色,比 QuestDB 高一档。InfluxDB 和 TimescaleDB 在压缩上没有优势,如果存储成本敏感,光这一点就能拉开数万元的年度开销差异。
这里需要多说一句:压缩比高的同时要小心查询解压开销。TDengine 压缩比高,但是查询时解压也在所难免。好在该数据库将热数据放内存缓存,冷数据走磁盘解压,配合预计算好的 K 线聚合表,前端查询不会走到最重的解压路径。
4. 最终的选型结论与生产部署形态
交叉对比测试做完后,我们最终选了TDengine 作为行情时序数据的主存储,ClickHouse 作为冷数据分析和 K 线聚合的辅助引擎。这个组合可能和很多团队的选择不同,但我想讲清楚背后的逻辑。
选 TDengine 的理由有三点:一是它同时满足高吞吐写入和低压缩存储两个核心要求,乱序写入容忍度最高,这非常适合真实交易数据生产的容错要求;二是超级表模型让我们可以按 symbol 组织数据,查询时天然避免跨表扫描,K 线实时聚合性能很稳定;三是它的写入路径内存占用低,在同等配置下能支撑更长的交易高峰期。
ClickHouse 的角色则是数据分析平台。我们每天夜里会把 TDengine 中的全量历史数据同步进 ClickHouse,用于策略回测、风控报表和多维探索式分析。这类任务 SQL 的复杂程度非常高,TDengine 的 SQL 子集未必覆盖,但 ClickHouse 完全没问题。两条链路的数据流大致是:
- 行情源 -> Kafka -> 清洗服务 -> TDengine(热数据主存储,保留最近 3 个月)
- TDengine -> 定时同步任务 -> ClickHouse(所有历史数据,保留 3 年以上)
- 冷数据定时从 TDengine 清理并转存对象存储备份,原始文件留底
部署形态上,TDengine 采用一主两从的集群架构,主节点负责写入,从节点同步副本,业务侧通过驱动自动选主。ClickHouse 则用一主一从加一个分布式表,全部数据在本地存储,查询服务的扩展通过增加副本数解决。
生产环境运行半年以来的数据是:TDengine 集群承载了日均约 20 亿行写入,平均写入速率稳定在 45 万行/秒,高峰可达 180 万行/秒;K 线聚合接口的平均响应时间稳定在 30 毫秒内,p95 在 80 毫秒内。ClickHouse 集群目前约 400 亿行历史数据,存储总量 8TB,日常分析查询的响应速度在亚秒到数秒之间。这个成绩单,至少对于我们的规模是够用的。
5. 上线后趟过的坑:时序数据库运维的真实教训
再好的选型,落到生产环境都会遇到文档里没写的问题。以下是我们在半年内实际踩过的几个坑,也许能帮你绕过。
5.1 TDengine 的磁盘空间"突然翻倍"之谜
上生产后的第三周,监控系统报警 TDengine 数据节点磁盘使用率突破 85%。我们用的是 2TB 的 NVMe 盘,按写入速度和压缩比估算,至少还有 60% 的空间余量,怎么可能这么快涨满?排查后发现,TDengine 默认会保留多个版本的数据文件用于多副本同步。一主两从模式下,每个节点都会完整保存所有数据,再加上 WAL 预写日志的大小没有上限,导致磁盘占用直接乘了 3。
后来做了三个调整:把 WAL 的walLevel从 2 调成 1(只保留最终结果),设定 WAL 文件最大大小并定期清理;将部分不那么重要的中间数据表的副本数从 3 调整到 2;并制定了磁盘使用率达到 70% 就自动清理过期数据的策略。这之后磁盘使用率长期维持在 60% 以下。
5.2 ClickHouse 的"突然变慢"和后台任务阻塞
ClickHouse 上线后跑得一直很稳,直到某一天计任务跑完后,一个本来 50 毫秒能出的报表查询突然变成 3 秒。排查后发现 merge 任务堆积了数小时,后台合并把所有 CPU 都吃掉了。这是因为我们导入了大量分区数据,而 ClickHouse 默认的 merge 并发限制太低,新导入的分区来不及合并,越堆越多。
解决办法是在导入高峰期前,将max_part_per_inserted_block调到合理的范围,同时给后台 merge 任务设置独立的线程池,避免它和查询线程争抢 CPU。经验是:ClickHouse 导入任务必须做分级限速,不能让它无限占资源。
5.3 行情数据"背靠背"重复时,如何保证幂等写入
交易平台经常出现"同一笔成交在两条推送里各出现一次"的情况,如果数据库不做去重,K 线聚合就会把这一笔成交量算两次。我们的清洗服务里给每条 tick 生成了一个全局唯一 ID(交易所+交易对+时间戳+序号),在写入 TDengine 时用这个 ID 作为主键的一部分。查询时如果遇到重复数据,TDengine 会自动按主键去重,从源头保障了最终一致性。这个经验即使换成 ClickHouse 也适用,不过 ClickHouse 需要依靠 ReplacingMergeTree 的版本字段来实现。
5.4 不要忽略冷数据备份的温数据层
我们的冷数据策略一开始是"3 个月后从 TDengine 删除,同步至 ClickHouse 后转存对象存储"。实际运行后发现,业务方偶尔还是会查询 4 到 6 个月前的 K 线数据,此时从对象存储加载回 ClickHouse 需要 1 到 2 分钟,前端图表直接白屏。解决方式是在 ClickHouse 和对象存储之间增加一层按月的冷热分离——最近 13 个月的冷数据保留在 ClickHouse 本地磁盘,更早的数据才转存对象存储。这样既控制成本,又保证了大多数历史和回测查询不需要走对象存储加载。
这几条经验说大不大,但每一个都能在关键时刻影响系统的可用性。技术选型不是选完就万事大吉,后续的调优和运维才是真正决定项目成败的部分。
最后分享一个小建议:做数据库选型时,别只盯着单测的吞吐数字。真正重要的是想清楚你的数据特征、访问模式、容错要求,然后把你自己的业务数据拿出来,写几个最有代表性的读写脚本,放到同一套硬件环境下跑一遍。只有自己跑出来的结果,才敢拍板用哪个。我这次也是这样做的,效果比任何宣传材料和评测文章都靠谱。