---
source_url: https://docs.dolphindb.cn/zh/tutorials/tsdb_engine.html
fetched_at: 2026-05-19T09:48:03Z
category: tutorials
title: TSDB 存储引擎简介
sha1: 8cbcb0425a9d70a7fce9c8322ecab043be02a2d0
---

# TSDB 存储引擎简介

本文简介 DolphinDB 在 2.0 版本中新推出的 TSDB 存储引擎、其适用场景以及与 OLAP 存储引擎的特点差异。

## 1. OLAP 与 TSDB 适用的场景

OLAP 是 DolphinDB 在 2.0 版本之前的唯一存储引擎。数据表中每个分区的每一列存为一个文件。数据在表中的存储顺序与数据写入的顺序一致，数据写入有非常高的效率。

OLAP 引擎的主要局限：

1. 分区内不支持索引，即使只是检索几行数据，也必须加载整个分区（的相关列）。
2. 写入过程设计简单，不支持去重；
3. 不适合几百几千列的宽表存储。
4. 即使修改一条记录，也必须重写整个分区。

TSDB 是 DolphinDB 基于 LSM 树自研的新的存储引擎。它很好地克服了 OLAP 引擎的局限性，功能更加全面。每一个分区的数据写在一个或多个 level file 中。每一个 level file 内部的数据按照指定的列进行排序且创建块索引。

TSDB 引擎具有以下优点：

1. 过滤条件使用分区列以及分区内排序字段的点查询非常高效。
2. 可以在写入时对数据进行排序和去重。
3. 适合存储几百几千列的宽表，也适合存储 array vector 和 BLOB 类型的数据。
4. 若指定去重时保留最后一条记录（设置 keepDuplicates=LAST），则修改数据时重写数据所在 level file 即可，不需要重写整个分区。

与 OLAP 引擎相比，TSDB 引擎的不足之处在于：

1. 写入吞吐量低。TSDB 引擎中，数据需在 cache engine 中进行排序；level file 会进行合并与压缩。
2. 读取整个分区数据或整个分区的某几列数据时，效率低于 OLAP。

下面通过一个例子来解释 OLAP 与 TSDB 引擎适合的 query。假设数据表 t 按照股票代码与交易日进行 COMPO 分区。以下是该数据表中的 4 行数据：

| StockID | Timestamp | Bid |
| --- | --- | --- |
| AAPL | 2021.08.05T09:30:00 | 1.5 |
| MSFT | 2021.08.05T09:30:00 | 1.3 |
| AAPL | 2021.08.05T09:31:00 | 1.6 |
| GOOG | 2021.08.05T09:31:00 | 1.4 |
| ...... | ...... | ...... |

OLAP 存储引擎的建库建表脚本如下：

```dolphindb
dbTime = database("", VALUE, 2021.08.01..2021.09.01)
dbStockID = database("", HASH, [SYMBOL, 100])

db = database(directory="dfs://stock",partitionType=COMPO,partitionScheme=[dbTime,dbStockID],engine="OLAP")

schema = table(1:0, `Timestamp`StockID`bid, [TIMESTAMP, SYMBOL, DOUBLE])
stocks = db.createPartitionedTable(table=schema, tableName=`stocks, partitionColumns=`Timestamp`StockID)
```

TSDB 存储引擎的建库建表脚本如下：

```dolphindb
dbTime = database("", VALUE, 2021.08.01..2021.09.01)
dbStockID = database("", HASH, [SYMBOL, 100])

db = database(directory="dfs://stock",partitionType=COMPO,partitionScheme=[dbTime,dbStockID],engine="TSDB")

schema = table(1:0, `Timestamp`StockID`bid, [TIMESTAMP, SYMBOL, DOUBLE])
stocks = db.createPartitionedTable(table=schema, tableName=`stocks, partitionColumns=`Timestamp`StockID, sortColumns=`StockID`Timestamp)
```

和 OLAP 的脚本相比，TSDB 的脚本在 database 初始化的时候指定存储引擎为 "TSDB"，并在创建表格的时候指定 `sortColumns` 为 `StockID` 和 `Timestamp`。

OLAP 引擎适合执行以下的 query：

```dolphindb
select avg(Bid) from t where date=2021.08.05 group by StockID
```

TSDB 引擎适合执行以下的 query：

```dolphindb
select * from table where StockID='AAPL', Timestamp > 2021.08.05T09:30:00, Timestamp < 2021.08.05T09:35:00
```

使用 OLAP 引擎所创建的数据库中，每个分区内部无索引，一个 query 从数据库中读取数据的最小单位是一个分区的一列。即使要查找一行数据，也必须读取该行数据所在分区的所有行，耗时一般在 100 毫秒以上。TSDB 引擎则在每个分区内部设置了索引，查找少量数据时无需读取分区内所有行，耗时可以低至几毫秒。TSDB 引擎在海量数据查询与分析方面的性能仅仅略慢于 OLAP 引擎。因此，若对查询少量数据有极致性能要求，推荐使用 TSDB 引擎。

## 2. TSDB 原理简介

本节叙述中，为简便起见，我们将结合上一节中的例子。

### 2.1. redo log

redo log 类似于 Write-Ahead Log。数据加载到内存之后，会先持久化到 redo log 中。即便数据库在写入时宕机了，重启时，系统仍能够从 redo log 中恢复数据。

### 2.2. Cache Engine

确认数据写入 WAL 之后，数据会写入内存中的 cache engine。

新写入 cache engine 的数据是直接追加（append）的，且未经排序的（unsorted write buffer）。待缓存的数据量达到一个阈值之后，对其按照 StockID 排序（sorted write buffer）。sorted write buffer 是只读的，可在内存中开启压缩，以在内存中存储更多的数据，降低查询数据的时延。待产生多个 sorted write buffer，其总数据量达到一个阈值（即配置参数 *TSDBCacheEngineSize* 设定值的 50%）之后，TSDB 会将所有 sorted write buffer 中的数据按照 Timestamp 排序后写入磁盘的数据文件（称为 level file）。

TSDB cache engine 的这种从 unsorted write buffer 到 sorted write buffer 两阶段的设计，与大多数的基于 LSMT 系统的 cache engine（或称 MemTable）有所不同。这种独特的设计是为了平衡系统的读性能与写性能。

### 2.3. Sort Columns

类似上例中 StockID 与 Timestamp 这样用于将数据进行排序的列，在 TSDB 中被称为 sort columns。sort columns 的最后一列必须为时间类型或整型。sortColumns 除了最后一列的其他列通常为在点查中过滤条件会用到的列，其唯一值组合，称为 sort key。使用 TSDB 引擎，查询时可利用 sort key 直接定位到过滤条件所指定的数据块，然后仅读取这些数据块的数据，点查性能可大幅提升。

### 2.4. Level File

每个 level file 中的数据均按照 StockID 与 Timestamp 排序。每个 StockID 的记录会存储为多个一定大小（16KB）的数据块，并将每个数据块在该文件中存储的起始位置记录下来。后续写入的数据会写入另一个 level file，依此类推。

level file 最多可有 4 个 level。Level 0 的文件由 cache engine 写入到磁盘而产生，而更高层次的 level file 是由更低层次的 level file 合并生成的。每个 level 0 文件最大为 32MB。每次从 cache engine 写入同一分区的数据量若大于 32MB，则产生多个 level 0 文件。从 level 0 到 level 1 的过程如下：当一个分区内的 level 0 文件数量超过 10 个或 level 0 总数据量大于某个阙值（256MB）时，所有 level 0 文件会被合并压缩为一个 level 1 的文件，以此类推。合并文件时会将数据按照 StockID 与 Timestamp 进行排序。通过不断进行数据文件的合并压缩，可以有效控制 level file 的数量，以免 level file 数量过多导致性能降低。

## 3. TSDB 使用技巧

本文在这一部分简单介绍 TSDB 的一些配置项和使用技巧。

### 3.1. 如何处理重复数据

一个分区内 sortColumns 重复的数据应当如何处理，由建表时 `createPartitionedTable` 函数的 *keepDuplicates* 参数指定。在上面这个例子中，即股票代码与时间列都一致的数据。默认的值为 ALL，即保留所有数据。若设置为 FIRST，则仅会保留重复数据中的第一条；若设置为 LAST，则仅会保留重复数据中的最新一条。

### 3.2. 控制 sort key 数量

为保证性能最优，建议每个分区内 sort key 最好不超过 1000 个。若 sort key 过多，每个 sort key 对应的数据量少，造成部分数据块内的数据量可能不足 *TSDBMaxBlockSize*，数据块数量也很多。当用户查询数据时，由于读取大量数据块，造成读文件耗时增加。此时，用户可通过建表函数指定 *sortKeyMappingFunction*，对 sort key 降维。请注意，降维操作一定程度上将影响写入性能，建议用户优先合理规划 sortColumns。

### 3.3. 设置 cache engine 容量

使用配置参数 *TSDBCacheEngineSize* 设定 cache engine 容量（以 GB 为单位）。默认值为 1GB，建议设置 1GB 或以上。

### 3.4. 触发 level file 合并

level file 的数量越多，则查询的效率会越低。对不再有数据写入的分区，可使用 `triggerTSDBCompaction` 命令以手动触发 level 0 file 的合并，既可以提升查询性能，亦可提升压缩率。
