如何在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流程
- 优先复用缺失值:原子操作从
missing_saks中弹出第一个元素,直接返回该值。 - 无缺失则分配新值:如果
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
相关产品推荐
相关产品推荐

