硬汉嵌入式论坛

 找回密码
 立即注册
查看: 968|回复: 23
收起左侧

[其它] [求助]有没有真正好用的键值对数据库?

  [复制链接]

27

主题

285

回帖

366

积分

高级会员

积分
366
QQ
发表于 2026-1-6 15:35:02 | 显示全部楼层 |阅读模式
在arduino 环境开发 esp32 时有用到 Preferences ,使用时就是一个全局对象,然后就可以无感 get、set。

不知道 h7 上有哪种推荐的键值对数据库,可以用在内部 flash外部 spi flash 上实现如此简洁的使用体验呢?

有些受够了旧工程结构体整体存储整体加载的体验,维护整个结构体的对齐和初始化、各种旧参量的遗留无法解决兼容问题,好麻烦,想找个键值对做替换。
过去检索一些“嵌入式数据库”但是都看起来没有那么简单,相互之间也难分高下,求助下群友有没有自己这方面在用的推荐



/* 数据库存储对象 */
#include <Preferences.h>
Preferences prefs;

void McuInit(void)
{
    prefs.begin("sys");

    // 读取波特率
    int    uartBaud  = prefs.getInt("uartbaud", 9600);
    int    canBaud   = prefs.getInt("canbtaud", 250000);
    String interface = prefs.getString("interface", "232");
}






回复

使用道具 举报

1万

主题

7万

回帖

12万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
121126
QQ
发表于 2026-1-6 15:42:55 | 显示全部楼层
之前网上收藏了一个,还没有去研究,你看看他这个实现怎么样

https://github.com/cnlohr/cnrbtree
回复

使用道具 举报

4

主题

457

回帖

469

积分

高级会员

积分
469
发表于 2026-1-6 16:01:30 | 显示全部楼层
少量数据推荐用FlashDB。分区别太大,否则开机初始化时间长。不支持H7内部flash,H7最小粒度256bit。FlashDB目前最大支持128bit
回复

使用道具 举报

27

主题

285

回帖

366

积分

高级会员

积分
366
QQ
 楼主| 发表于 2026-1-6 16:11:36 | 显示全部楼层
eric2013 发表于 2026-1-6 15:42
之前网上收藏了一个,还没有去研究,你看看他这个实现怎么样

https://github.com/cnlohr/cnrbtree

这个库属于底层索引算法,需要包装内存管理、持久化、校验才能当作嵌入式数据库。

看起来的话改造成嵌入式数据库有些复杂,主要是他滥用 calloc 和 free,需要实现一套完整紧密的私有内存管理。或许借助 threadx 的内存池可以实现?不过树的恢复也得测试。

难点在于 CNRBTREE_MALLOC/CNRBTREE_FREE 的实现,还有何时持久化,搞完这一套那不如不要基于他的算法。

我一直没想到如何解决 flash 按扇区擦除的耗时这块,在 spi flash 中耗时尤其明显,哪怕有 DMA 也是非常扰乱系统。也没有细看其他数据库是如何处理的,如果他们就是很暴力的每次都持久化,那可能还是用不了。
回复

使用道具 举报

27

主题

285

回帖

366

积分

高级会员

积分
366
QQ
 楼主| 发表于 2026-1-6 16:18:35 | 显示全部楼层
cctv180 发表于 2026-1-6 16:01
少量数据推荐用FlashDB。分区别太大,否则开机初始化时间长。不支持H7内部flash,H7最小粒度256bit。FlashD ...

有实际工程使用过不?体验怎么样?

说实话有点抗拒 armink 这家伙搞的东西,老是搞的又丑又大又耦合。
回复

使用道具 举报

5

主题

23

回帖

38

积分

新手上路

积分
38
发表于 2026-1-6 16:54:17 | 显示全部楼层
yono 发表于 2026-1-6 16:18
有实际工程使用过不?体验怎么样?

说实话有点抗拒 armink 这家伙搞的东西,老是搞的又丑又大又耦合。

我用着还行。配合fal使 还挺无感的,不同工程都不用怎么改,存储部分目前有40来个项目共用公共程序的。不过我这边的项目对于ram flash 的占用都不敏感。
回复

使用道具 举报

27

主题

285

回帖

366

积分

高级会员

积分
366
QQ
 楼主| 发表于 2026-1-6 18:10:47 | 显示全部楼层
1824789902 发表于 2026-1-6 16:54
我用着还行。配合fal使 还挺无感的,不同工程都不用怎么改,存储部分目前有40来个项目共用公共程序的。不 ...

我看 FlashDB 有这样的特性。

优势
1.活跃扇区的轮转进行磨损均衡,这将减少擦除的操作
2.接口简单,按照我编好的 spi-flash 驱动 api,应该可以很简单的移植

缺点
1.必须依赖 fal 或者文件系统,这二者很遗憾我的现有工程都没有,所以需要额外的引入
2.我认为最大的劣势:在内存中上没有一个实际缓存的维护,所以每次 读/写 都会必须引起 spi-flash 线路的动作(当然使用内部flash会快很多),适合少量动作的配置量使用,不适合状态量这样需要频繁读取的变量。这不是他作为嵌入式数据库的劣势,是我的工程中使用状态量实现了很多switch多态但又不是模块化的多态,这是和工程不契合的地方。

总的来说这确实是一个很好的嵌入式数据库,打算试试看,然后把工程改一改。

如果可选”是否维护内存缓存“就完美了。
回复

使用道具 举报

27

主题

285

回帖

366

积分

高级会员

积分
366
QQ
 楼主| 发表于 2026-1-6 18:13:01 | 显示全部楼层
看他也有索引 cache 这种东西,应该是有考虑”内存缓存“的,只是做成常驻功能就不轻量化、做成可裁剪功能有些复杂,就没实现
回复

使用道具 举报

38

主题

235

回帖

349

积分

高级会员

积分
349
发表于 2026-1-7 09:13:27 | 显示全部楼层
eeprom不好用吗
封装成一个结构体,不要内存对齐
回复

使用道具 举报

6

主题

30

回帖

48

积分

新手上路

积分
48
发表于 2026-1-7 09:22:16 | 显示全部楼层
1824789902 发表于 2026-1-6 16:54
我用着还行。配合fal使 还挺无感的,不同工程都不用怎么改,存储部分目前有40来个项目共用公共程序的。不 ...

同感,但是位置不一样,他的东西一直是为rt-thread 服务的
回复

使用道具 举报

27

主题

285

回帖

366

积分

高级会员

积分
366
QQ
 楼主| 发表于 2026-1-7 13:34:34 | 显示全部楼层
LinY 发表于 2026-1-7 09:13
eeprom不好用吗
封装成一个结构体,不要内存对齐

EEPROM 用结构体和 spi-flash 有同一个痛点。

结构体里需要预留一些空位以便后续增加存储,或者旧配置项的位置需要永久预留,哪怕这个配置项在新版程序中被移除(避免从老版本升级后读取数据不正确)。

这种方法在维护老版本的兼容性时很让人头疼,比如一个一年前出货的老机器,和新版本程序的存储排布就很可能不同,升级后crc不通过引发初始化,客户的一些原有配置项就会丢。

键值对不会,反正以键为索引,没索引到有新程序的default回退,根本不用管老版本,直接硬升级就好了。
回复

使用道具 举报

6

主题

55

回帖

73

积分

初级会员

积分
73
发表于 2026-1-7 14:47:19 | 显示全部楼层
同样的问题,现在使用flashdb来处理参数的保存,效果不错。
回复

使用道具 举报

0

主题

24

回帖

24

积分

新手上路

积分
24
发表于 2026-1-7 15:44:11 | 显示全部楼层
yono 发表于 2026-1-6 18:10
我看 FlashDB 有这样的特性。

优势

对 活跃扇区的轮转进行磨损均衡 感兴趣,有没有相关的资料介绍我学习一下
回复

使用道具 举报

27

主题

285

回帖

366

积分

高级会员

积分
366
QQ
 楼主| 发表于 2026-1-14 11:25:25 | 显示全部楼层
stephen.shi 发表于 2026-1-7 15:44
对 活跃扇区的轮转进行磨损均衡 感兴趣,有没有相关的资料介绍我学习一下

没有资料啊,我也在学习,准备构建一个新的库做到 esp32 Preferences 同等的体验。

FlashDB 还是有依赖项我不太能接受,个人对零依赖、仅作 port 绑定这方面很执着。
回复

使用道具 举报

117

主题

637

回帖

1003

积分

至尊会员

积分
1003
QQ
发表于 2026-1-14 17:58:29 | 显示全部楼层
fal应该就是port吧。类似于封装了一下接口。
回复

使用道具 举报

27

主题

285

回帖

366

积分

高级会员

积分
366
QQ
 楼主| 发表于 2026-1-15 17:14:14 | 显示全部楼层
会飞的猪_2020 发表于 2026-1-14 17:58
fal应该就是port吧。类似于封装了一下接口。

没毛病,设计理念上没问题,但是就是感觉很丑很不人文关怀,干不进项目里。

像我这种就是-10x工程师,拖慢整个研发进度的坏东西。
回复

使用道具 举报

4

主题

161

回帖

173

积分

初级会员

积分
173
发表于 2026-1-16 08:41:12 | 显示全部楼层
我现在用https://github.com/protobuf-c/protobuf-c封装,直接存在文件系统里面。
回复

使用道具 举报

0

主题

5

回帖

5

积分

新手上路

积分
5
发表于 2026-1-16 14:15:33 | 显示全部楼层
我用的flashdb   。原来的是SFUD+fal+flashdb,太多了。去掉了SFUD
回复

使用道具 举报

31

主题

101

回帖

194

积分

初级会员

积分
194
发表于 2026-1-19 23:00:37 | 显示全部楼层
直接写文件,ini格式,也好导出
回复

使用道具 举报

27

主题

285

回帖

366

积分

高级会员

积分
366
QQ
 楼主| 发表于 2026-1-20 14:30:42 | 显示全部楼层
zbq 发表于 2026-1-19 23:00
直接写文件,ini格式,也好导出

文件形式在嵌入式程序不太合适,我已经有一些思路了,今年年底前将做一个雏形出来。

我还是希望具有内存镜像+偶尔持久化,读时不要有外设参与,这样有助于内部/外部flash都支持,只需要实现 写入/读取flash 的接口函数,数据库做轮转平衡。

但是难点在于 内存镜像究竟是不是一个可选项 ,运行在内存中或运行在 flash 中的数据结构天然相违背,考虑是不作可选项,必须具有内存镜像。

想要每次读写都通过外设接口的去用 FlashDB,构建一个只允许内存资源比较丰富的MCU使用的数据库,预计占用4kb的内存、一个典型spiflash扇区。

或者大家谁有推荐现有的 嵌入式、内存、键值对、数据库,让我直接拿来用就更好了。
回复

使用道具 举报

31

主题

101

回帖

194

积分

初级会员

积分
194
发表于 2026-1-20 14:33:23 | 显示全部楼层
yono 发表于 2026-1-20 14:30
文件形式在嵌入式程序不太合适,我已经有一些思路了,今年年底前将做一个雏形出来。

我还是希望具有内 ...

zephyr 里面的nvs可以直接拿来用
回复

使用道具 举报

27

主题

285

回帖

366

积分

高级会员

积分
366
QQ
 楼主| 发表于 2026-1-20 16:41:40 | 显示全部楼层
zbq 发表于 2026-1-20 14:33
zephyr 里面的nvs可以直接拿来用

哦有说法

结合硬汉大佬推荐的https://github.com/cnlohr/cnrbtree数据结构,再实现一个不依赖RTOS的紧凑内存管理,加一个api包装层,应该能实现类似 Preferences 的使用体验
回复

使用道具 举报

0

主题

10

回帖

10

积分

新手上路

积分
10
发表于 2026-1-20 21:08:16 | 显示全部楼层

贴一个我在用的,好处小有擦写平衡,直接保存结构体变量。

[C] 纯文本查看 复制代码
#if (RTS_FV_DEBUG_ENABLE)
#define local_printf(format,...) printf("[fv] "format,##__VA_ARGS__)
#else
#define local_printf(format,...) do{}while(0)
#endif

/** X.x.x: Major version */
#define RTS_FV_VERSION_MAJOR      1
/** x.X.x: Minor version */
#define RTS_FV_VERSION_MINOR      0
/** x.x.X: Revision*/
#define RTS_FV_VERSION_REVISION   1

#define RTS_FV_MARK_EMPTY          0xFFFFFFFF
#define RTS_FV_MARK_USING          0x55AA55AA
#define RTS_FV_MARK_USELESS        0x00000000

#define RTS_FV_MARK_PART_SIZE       4
#define RTS_FV_CRC__PART_SIZE       4

void rts_fv_init(rts_fv_layout_t *layout,void *data,uint32_t datasize)
{
    local_printf("V%d.%d.%d\r\n",RTS_FV_VERSION_MAJOR,RTS_FV_VERSION_MINOR,RTS_FV_VERSION_REVISION);
    local_printf("init %s  start addr:0x%x  total size:%d   data size:%d   max data num:%d \r\n",
                 layout->name,layout->addr,layout->size,datasize,layout->size/(RTS_FV_MARK_PART_SIZE+datasize+RTS_FV_CRC__PART_SIZE));
    rts_fv_read(layout,data,datasize);
}

void rts_fv_read(rts_fv_layout_t *layout,void *data,uint32_t datasize)
{
    uint32_t i=0;
    uint32_t fream_size=0;
    uint32_t fream_total=0;
    uint32_t u32value;
    uint32_t crc32;

    if(datasize%4)
    {
        local_printf("datasize must be 4 times\r\n");
        return;
    }
    fream_size=RTS_FV_MARK_PART_SIZE+datasize+RTS_FV_CRC__PART_SIZE;
    fream_total=layout->size/fream_size;
    if(layout->index>=fream_total)
    {
        local_printf("index is overflow\r\n");
        goto _search_all;
    }
    //read fream mark part
    layout->read(layout->addr+layout->index*fream_size,&u32value,RTS_FV_MARK_PART_SIZE);
    if(u32value==RTS_FV_MARK_USING)
    {
        layout->read(layout->addr+layout->index*fream_size+RTS_FV_MARK_PART_SIZE,data,datasize);
        crc32=rts_crc32_calculate(&rts_crc32,data,datasize);
        layout->read(layout->addr+layout->index*fream_size+RTS_FV_MARK_PART_SIZE+datasize,&u32value,RTS_FV_CRC__PART_SIZE);
        if(u32value==crc32)
        {
            local_printf("read success\r\n");
            return;
        }
        else
        {
            local_printf("read check crc32 fail\r\n");
            local_printf("set default values\r\n");
            if(layout->data_set_default)layout->data_set_default(data,datasize);
            else memset(data,0,datasize);
            layout->index=fream_total;//set index overflow in rts_fv_write will call erase
            rts_fv_write(layout,data,datasize);
            return;
        }
    }
    else
    {
        local_printf("mark is not RTS_FV_MARK_USING\r\n");
        goto _search_all;
    }




_search_all:
    local_printf("search all ...\r\n");
    for(i=0;i<fream_total;i++)
    {
        layout->read(layout->addr+i*fream_size,&u32value,RTS_FV_MARK_PART_SIZE);
        if(u32value==RTS_FV_MARK_USING)
        {
            layout->read(layout->addr+i*fream_size+RTS_FV_MARK_PART_SIZE,data,datasize);
            crc32=rts_crc32_calculate(&rts_crc32,data,datasize);
            layout->read(layout->addr+i*fream_size+RTS_FV_MARK_PART_SIZE+datasize,&u32value,RTS_FV_CRC__PART_SIZE);
            if(u32value==crc32)
            {
                layout->index=i;
                local_printf("read success\r\n");
                return;
            }
            else
            {
                local_printf("read check crc32 fail\r\n");
                local_printf("set default values\r\n");
                if(layout->data_set_default)layout->data_set_default(data,datasize);
                else memset(data,0,datasize);
                layout->index=fream_total;//set index overflow in rts_fv_write will call erase
                rts_fv_write(layout,data,datasize);
                return;
            }
        }
    }
    local_printf("search all done,but not foud,set to default\r\n");
    if(layout->data_set_default)layout->data_set_default(data,datasize);
    else memset(data,0,datasize);
    layout->index=fream_total;//set index overflow in rts_fv_write will call erase
    rts_fv_write(layout,data,datasize);
}

void rts_fv_write(rts_fv_layout_t *layout,void *data,uint32_t datasize)
{
    uint32_t fream_size=0;
    uint32_t fream_total=0;
    uint32_t u32value;
    uint32_t crc32;
    bool is_erase=false;
    uint32_t writeIndex=0;

    if(datasize%4)
    {
        local_printf("datasize must be 4 times\r\n");
        return;
    }

    fream_size=RTS_FV_MARK_PART_SIZE+datasize+RTS_FV_CRC__PART_SIZE;
    fream_total=layout->size/fream_size;

    writeIndex=layout->index+1;
    if(writeIndex>=fream_total)
    {
        local_printf("index overflow erase\r\n");
        layout->erase();
        writeIndex=0;
        is_erase=true;
    }
    //check is empty ?
    layout->read(layout->addr+writeIndex*fream_size,&u32value,RTS_FV_MARK_PART_SIZE);
    if(u32value!=RTS_FV_MARK_EMPTY)
    {
        local_printf("fream not empty erase\r\n");
        layout->erase();
        writeIndex=0;
        is_erase=true;
    }
    u32value=RTS_FV_MARK_USING;
    layout->write(layout->addr+writeIndex*fream_size,&u32value,RTS_FV_MARK_PART_SIZE);
    layout->write(layout->addr+writeIndex*fream_size+RTS_FV_MARK_PART_SIZE,data,datasize);
    crc32=rts_crc32_calculate(&rts_crc32,data,datasize);
    layout->write(layout->addr+writeIndex*fream_size+RTS_FV_MARK_PART_SIZE+datasize,&crc32,RTS_FV_CRC__PART_SIZE);
    local_printf("write data(%d) done\r\n",datasize);

    if(is_erase==false)
    {
        u32value=RTS_FV_MARK_USELESS;
        layout->write(layout->addr+layout->index*fream_size,&u32value,RTS_FV_MARK_PART_SIZE);
    }
    layout->index=writeIndex;
    local_printf("adjust index(%d) done\r\n",layout->index);
}


#if (RTS_FV_UINT_TEST_ENABLE)

typedef struct
{
    uint32_t data0;
    uint32_t data1;
    uint32_t data2;
    uint32_t data3;
    uint32_t data4;
    uint32_t data5;
    uint32_t data6;
    uint32_t data7;
    uint32_t data8;
    uint32_t data9;

}user_data_t;

user_data_t user_data;
void rts_fv_uint_test(rts_fv_layout_t *p_fv_layout)
{
    uint8_t u8value;
    uint32_t i,j;
    uint8_t *pu8;

    local_printf("uint test start...\r\n");
    rts_fv_init(p_fv_layout,&user_data,sizeof(user_data));

    for(i=0;i<(p_fv_layout->size/(RTS_FV_MARK_PART_SIZE+sizeof(user_data)+RTS_FV_CRC__PART_SIZE));i++)
    {
        u8value+=0x12;
        //wtire
        memset(&user_data,u8value,sizeof(user_data));
        rts_fv_write(p_fv_layout,&user_data,sizeof(user_data));

        //read
        memset(&user_data,0,sizeof(user_data));
        rts_fv_read(p_fv_layout,&user_data,sizeof(user_data));

        //check
        pu8=(uint8_t *)&user_data;
        for(j=0;j<sizeof(user_data);j++)
        {
            if(*pu8!=u8value)
            {
                local_printf("rts fv uint test fail\r\n");
                return;
            }
        }
    }
    local_printf("rts fv uint test success !!\r\n");
}

#endif

#endif

回复

使用道具 举报

117

主题

637

回帖

1003

积分

至尊会员

积分
1003
QQ
发表于 2026-1-21 08:20:25 | 显示全部楼层
找个空闲时间研究一下FlashDB。之前用过,但是没有研究过它的算法。看公司里有同事在用,我也就用了。

我自己体验的感觉,就是移植没那么复杂,实现一个接口文件就行了。但是他的方法好不好,还没研究过。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2026-2-24 13:52 , Processed in 0.087909 second(s), 25 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表