You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Mongo中实现可复用已删除键的系统分配键(SAK)?

复用连续系统分配键(SAK)的Mongo实现方案

针对你用libbson mongo-c-driver 1.15.0迁移DISAM到Mongo,需要优先复用已删除SAK、无缺失则取最大SAK+1的需求,以下是几个务实的解决方案:

一、最优方案:独立计数器集合+原子操作

这个方案彻底解决性能问题,逻辑简单易维护,适合百万级数据场景:

核心思路

维护一个单独的sequence_counters集合,每个需要SAK的表对应一条文档,记录当前最大SAK和已删除的SAK列表:

{
  "_id": "student", // 对应表名
  "current_max": 100, // 当前已分配的最大SAK
  "missing_saks": [25, 30] // 已删除的SAK列表
}

生成SAK流程

  1. 优先复用缺失值:原子操作从missing_saks中弹出第一个元素,直接返回该值。
  2. 无缺失则分配新值:如果missing_saks为空,原子递增current_max并返回新值。

优势

  • 所有操作都是单文档原子操作,跨服务器调用性能极高,不受数据量影响。
  • C语言实现逻辑简单,维护成本低。
  • 删除记录时只需将SAK原子添加到missing_saks即可,无需复杂操作。

C驱动代码示例

#include <mongoc/mongoc.h>
#include <bson/bson.h>

// 获取可用SAK
int64_t get_available_sak(mongoc_collection_t *counter_coll, const char *coll_name) {
    int64_t sak = -1;
    bson_t query = BSON_INITIALIZER;
    BSON_APPEND_UTF8(&query, "_id", coll_name);

    // 1. 尝试获取并删除第一个缺失的SAK
    const bson_t *counter_doc = mongoc_collection_find_one(counter_coll, &query, NULL, NULL, NULL);
    if (counter_doc) {
        bson_iter_t iter;
        if (bson_iter_init_find(&iter, counter_doc, "missing_saks") && bson_iter_type(&iter) == BSON_TYPE_ARRAY) {
            const bson_t *missing_array = bson_iter_array(&iter);
            if (bson_count_keys(missing_array) > 0) {
                // 取出第一个缺失SAK
                bson_iter_t arr_iter;
                if (bson_iter_init(&arr_iter, missing_array) && bson_iter_next(&arr_iter)) {
                    sak = bson_iter_int64(&arr_iter);
                    // 原子删除该SAK
                    bson_t update = BSON_INITIALIZER;
                    BSON_APPEND_DOCUMENT_BEGIN(&update, "$pull");
                    BSON_APPEND_INT64(&update, "missing_saks", sak);
                    bson_append_document_end(&update, &update);
                    mongoc_collection_update_one(counter_coll, &query, &update, NULL, NULL, NULL);
                    bson_destroy(&update);
                }
            }
        }
        bson_destroy((bson_t *)counter_doc);
    }

    // 2. 无缺失则递增current_max
    if (sak == -1) {
        bson_t update = BSON_INITIALIZER;
        BSON_APPEND_DOCUMENT_BEGIN(&update, "$inc");
        BSON_APPEND_INT64(&update, "current_max", 1);
        bson_append_document_end(&update, &update);

        mongoc_find_and_update_opts_t *opts = mongoc_find_and_update_opts_new();
        mongoc_find_and_update_opts_set_return_document(opts, MONGOC_RETURN_DOCUMENT_AFTER);

        bson_t result = BSON_INITIALIZER;
        if (mongoc_collection_find_one_and_update(counter_coll, &query, &update, opts, &result, NULL)) {
            bson_iter_t iter;
            if (bson_iter_init_find(&iter, &result, "current_max")) {
                sak = bson_iter_int64(&iter);
            }
        }

        mongoc_find_and_update_opts_destroy(opts);
        bson_destroy(&update);
        bson_destroy(&result);
    }

    bson_destroy(&query);
    return sak;
}

// 删除记录时添加缺失SAK
void add_missing_sak(mongoc_collection_t *counter_coll, const char *coll_name, int64_t sak) {
    bson_t query = BSON_INITIALIZER;
    BSON_APPEND_UTF8(&query, "_id", coll_name);

    bson_t update = BSON_INITIALIZER;
    BSON_APPEND_DOCUMENT_BEGIN(&update, "$push");
    BSON_APPEND_INT64(&update, "missing_saks", sak);
    bson_append_document_end(&update, &update);

    mongoc_collection_update_one(counter_coll, &query, &update, NULL, NULL, NULL);

    bson_destroy(&query);
    bson_destroy(&update);
}

初始化步骤

先给每个需要SAK的表创建计数器文档:

db.sequence_counters.insertOne({_id: "student", current_max: 100, missing_saks: []})

二、简化聚合查询方案(无需额外集合)

如果不想维护独立集合,可使用更简洁的聚合查询获取目标SAK,逻辑比你之前的查询更清晰:

db.student.aggregate([
    // 收集所有SAK和当前最大值
    { $group: {
        _id: null,
        all_saks: { $push: "$_id" },
        max_sak: { $max: "$_id" }
    }},
    // 生成从1到max_sak+1的连续序列,找出缺失值
    { $project: {
        _id: 0,
        missing_saks: {
            $setDifference: [
                { $range: [1, { $add: ["$max_sak", 2] }] },
                "$all_saks"
            ]
        },
        max_sak: 1
    }},
    // 取最小缺失值,无缺失则返回max_sak+1
    { $project: {
        target_sak: {
            $cond: {
                if: { $gt: [{ $size: "$missing_saks" }, 0] },
                then: { $arrayElemAt: ["$missing_saks", 0] },
                else: { $add: ["$max_sak", 1] }
            }
        }
    }}
])

注意事项

  • 该方案需要扫描全表聚合,数据量大时性能会下降,适合中小规模表。
  • 在C驱动中构建这个管道比你之前的复杂聚合更容易维护。

三、方案对比

方案性能维护成本适用场景
独立计数器集合O(1)原子操作低高并发、百万级大数据量
简化聚合查询O(n)全表扫描中中小规模表
DISAM逐个检查方案极差(多次调用)高不适用Mongo跨服务器场景

内容的提问来源于stack exchange,提问作者Swarnagowri

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 18:24:49