火柴人武林大会
156.74M · 2026-02-04
时序数据是按时间顺序记录的一系列数据点,具有以下特点:
| 类型 | 存储格式 | 优点 | 缺点 |
|---|---|---|---|
time.Time | 字符串(格式:2006-01-02 15:04:05) | 可读性好,易于理解 | 占用空间大(约19字节),比较速度慢 |
int64(时间戳) | 8字节二进制 | 存储效率高,比较速度快 | 可读性差,需要转换为时间格式查看 |
sfsDb 通过 util.AnyToBytes 函数将不同类型转换为字节数组:
// time.Time 序列化
case time.Time:
return []byte(val.Format(time.DateTime)) // 格式:2006-01-02 15:04:05
// int64 序列化
case int64:
return Int64ToBytes(val, EndianOrder) // 直接转换为8字节
time.Time 作为主键| 特性 | 时间戳(int64) | time.Time |
|---|---|---|
| 存储大小 | 8字节 | 约19字节 |
| 比较速度 | 极快(直接数值比较) | 较慢(字符串字典序比较) |
| 范围查询 | 高效(基于数值范围) | 低效(基于字符串范围) |
| 精度控制 | 灵活(秒、毫秒、纳秒) | 固定(sfsDb中为秒级) |
| 写入性能 | 高 | 中 |
| 查询性能 | 高 | 中 |
推荐使用 int64 时间戳作为主键,原因如下:
// 创建时序数据表
table, _ := TableNew("iot_sensor_data")
// 设置字段,使用时间戳作为主键
fields := map[string]any{
"timestamp": int64(0), // 时间戳主键(毫秒级)
"sensor_id": "", // 传感器ID
"temperature": 0.0, // 温度
"humidity": 0.0, // 湿度
"pressure": 0.0, // 压力
}
table.SetFields(fields)
// 创建时间戳主键索引
table.CreatePrimaryKey("timestamp")
// 可选:为传感器ID创建索引,优化按传感器查询
table.CreateSimpleIndex("idx_sensor_id", "sensor_id")
// 生成毫秒级时间戳
timestamp := time.Now().UnixMilli()
// 准备传感器数据
sensorData := map[string]any{
"timestamp": timestamp,
"sensor_id": "sensor_001",
"temperature": 25.5,
"humidity": 60.2,
"pressure": 1013.25,
}
// 插入数据
_, err := table.Insert(&sensorData)
if err != nil {
log.Printf("插入数据失败: %v", err)
}
// 查询最近1小时的数据
oneHourAgo := time.Now().Add(-time.Hour).UnixMilli()
query := map[string]any{"timestamp": oneHourAgo}
// 使用 GreaterThan 操作符查询
iter := table.Search(&query, util.GreaterThan)
defer iter.Release()
// 获取结果
records := iter.GetRecords(true)
log.Printf("最近1小时数据量: %d", len(records))
// 查询特定传感器最近1小时的数据
oneHourAgo := time.Now().Add(-time.Hour).UnixMilli()
query := map[string]any{
"timestamp": oneHourAgo,
"sensor_id": "sensor_001",
}
// 范围+等值查询
iter := table.Search(&query, util.GreaterThan)
defer iter.Release()
// 查询昨天的数据
yesterday := time.Now().AddDate(0, 0, -1)
startOfDay := time.Date(yesterday.Year(), yesterday.Month(), yesterday.Day(), 0, 0, 0, 0, yesterday.Location())
endOfDay := startOfDay.Add(24 * time.Hour)
startTimestamp := startOfDay.UnixMilli()
endTimestamp := endOfDay.UnixMilli()
// 构造查询条件
startQuery := map[string]any{"timestamp": startTimestamp}
endQuery := map[string]any{"timestamp": endTimestamp}
// 先查询大于等于开始时间的数据,然后在应用层过滤
iter := table.Search(&startQuery, util.GreaterThan)
defer iter.Release()
// 在应用层过滤小于结束时间的数据
var filteredRecords []map[string]any
for _, record := range iter.GetRecords(true) {
if ts, ok := record["timestamp"].(int64); ok && ts < endTimestamp {
filteredRecords = append(filteredRecords, record)
}
}
log.Printf("昨天数据量: %d", len(filteredRecords))
// 更新指定时间戳的数据
updateData := map[string]any{
"timestamp": targetTimestamp, // 用于定位记录
"temperature": 26.0, // 更新温度值
"humidity": 58.5, // 更新湿度值
}
err := table.Update(&updateData)
if err != nil {
log.Printf("更新数据失败: %v", err)
}
// 删除指定时间戳的数据
deleteData := map[string]any{"timestamp": targetTimestamp}
err := table.Delete(&deleteData)
if err != nil {
log.Printf("删除数据失败: %v", err)
}
使用适当的时间精度:
time.Now().Unix()(适合低频数据)time.Now().UnixMilli()(适合大多数IoT场景)time.Now().UnixMicro()(适合高精度传感器)数据压缩考虑:
float32 而非 float64)创建合适的索引:
优化查询范围:
GetRecords(true) 只获取需要的字段批量操作:
package main
import (
"fmt"
"log"
"time"
"github.com/liaoran123/sfsDb/engine"
"github.com/liaoran123/sfsDb/storage"
"github.com/liaoran123/sfsDb/util"
)
func main() {
// 初始化数据库
if _, err := storage.OpenDefaultDb("./iot_data"); err != nil {
log.Fatalf("初始化数据库失败: %v", err)
}
defer storage.CloseDb()
// 创建传感器数据表
table, err := engine.TableNew("sensor_data")
if err != nil {
log.Fatalf("创建表失败: %v", err)
}
// 设置字段
fields := map[string]any{
"timestamp": int64(0), // 时间戳主键
"sensor_id": "", // 传感器ID
"temperature": 0.0, // 温度
"humidity": 0.0, // 湿度
"pressure": 0.0, // 压力
}
if err := table.SetFields(fields); err != nil {
log.Fatalf("设置字段失败: %v", err)
}
// 创建主键索引
if err := table.CreatePrimaryKey("timestamp"); err != nil {
log.Fatalf("创建索引失败: %v", err)
}
// 模拟100条传感器数据
for i := 0; i < 100; i++ {
timestamp := time.Now().Add(time.Duration(i) * time.Second).UnixMilli()
data := map[string]any{
"timestamp": timestamp,
"sensor_id": fmt.Sprintf("sensor_%03d", i%10),
"temperature": 20.0 + float64(i%10),
"humidity": 50.0 + float64(i%20),
"pressure": 1000.0 + float64(i%50),
}
if _, err := table.Insert(&data); err != nil {
log.Printf("插入数据失败: %v", err)
}
}
// 查询最近30秒的数据
thirtySecondsAgo := time.Now().Add(-30 * time.Second).UnixMilli()
query := map[string]any{"timestamp": thirtySecondsAgo}
iter := table.Search(&query, util.GreaterThan)
defer iter.Release()
records := iter.GetRecords(true)
log.Printf("最近30秒数据量: %d", len(records))
for _, record := range records {
log.Printf("时间: %d, 传感器: %s, 温度: %.1f",
record["timestamp"], record["sensor_id"], record["temperature"])
}
}
sfsDb 完全支持时序数据处理,通过以下最佳实践可以获得更好的性能:
int64 时间戳作为主键,提高存储和查询效率通过遵循上述指南,可以充分利用 sfsDb 的轻量级优势,为各种时序数据场景提供高效的存储和查询解决方案。
sfsDb 在时序数据处理方面还有以下优化空间:
这些优化将使 sfsDb 在时序数据处理方面更具竞争力,能够更好地满足各种时序数据场景的需求。