MongoDB中自动增长ID详解:实现、应用及优化

2024-06-30 1279阅读

在MongoDB中,自动增长的功能主要通过使用数据库的ObjectId或自定义的序列来实现。ObjectId是MongoDB默认的主键类型,它是唯一的并且具有一定的排序特性。然而,在某些场景下,可能需要使用自定义的自动增长ID,例如在某些遗留系统中或者为了更好的用户体验。

MongoDB中自动增长ID详解:实现、应用及优化
(图片来源网络,侵删)

基本语法和命令

使用ObjectId

ObjectId是MongoDB默认的主键类型,它由12字节组成,包括时间戳、机器标识符、进程ID和计数器。每次插入新文档时,MongoDB会自动生成一个新的ObjectId。

插入新文档时,_id字段会自动生成:

db.collection.insertOne({name: "example"})
自定义自动增长ID

如果需要自定义自动增长ID,可以使用以下方法:

  1. 创建计数器集合:

    创建一个专门的集合来存储序列计数器。

    db.createCollection("counters")
    db.counters.insertOne({_id: "productid", seq: 0})
    
  2. 定义获取下一个序列值的函数:

    使用findAndModify原子操作来获取并增加序列值。

    function getNextSequence(name) {
        var ret = db.counters.findAndModify({
            query: { _id: name },
            update: { $inc: { seq: 1 } },
            new: true
        });
        return ret.seq;
    }
    
  3. 插入新文档并使用自定义ID:

    在插入新文档时,调用该函数以生成新的ID。

    db.products.insertOne({
        _id: getNextSequence("productid"),
        name: "example"
    })
    

示例

以下是完整的示例代码:

  1. 创建计数器集合并插入初始值:

    db.counters.insertOne({_id: "userid", seq: 0})
    
  2. 定义获取下一个序列值的函数:

    function getNextSequence(name) {
        var ret = db.counters.findAndModify({
            query: { _id: name },
            update: { $inc: { seq: 1 } },
            new: true
        });
        return ret.seq;
    }
    
  3. 插入新文档并使用自定义ID:

    db.users.insertOne({
        _id: getNextSequence("userid"),
        name: "John Doe"
    })
    

应用场景

1. 遗留系统迁移

详解:

在许多企业中,遗留系统使用关系数据库(如MySQL、PostgreSQL等),并依赖于自增ID作为主键。如果计划将这些系统迁移到MongoDB中,直接使用MongoDB的ObjectId可能会导致兼容性问题或破坏现有业务逻辑。因此,自定义自动增长ID可以简化迁移过程,保留原有系统的ID生成机制。

示例场景:

假设一家电子商务公司决定将其产品数据库从MySQL迁移到MongoDB。原系统中的产品ID是自增的整数。

// MySQL中的产品表
CREATE TABLE products (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255),
    price DECIMAL(10, 2)
);
// 原有数据
INSERT INTO products (name, price) VALUES ('Laptop', 999.99), ('Smartphone', 699.99);

在迁移到MongoDB时,需要保留这些自增的ID。

// 在MongoDB中创建计数器集合
db.counters.insertOne({_id: "productid", seq: 2}) // 假设MySQL中已有两个产品
// 定义获取下一个序列值的函数
function getNextSequence(name) {
    var ret = db.counters.findAndModify({
        query: { _id: name },
        update: { $inc: { seq: 1 } },
        new: true
    });
    return ret.seq;
}
// 插入新产品时使用自定义ID
db.products.insertOne({
    _id: getNextSequence("productid"),
    name: "Tablet",
    price: 499.99
});
2. 用户友好ID

详解:

对于前端用户,使用连续的、自增的数字ID比使用ObjectId更友好、更容易记忆。特别是在需要用户手动输入或引用ID的场景中,自增ID会更简洁、易读。

示例场景:

一个博客平台希望用户能够通过短链接直接访问文章。使用自增ID可以生成短链接,提升用户体验。

// 创建计数器集合
db.counters.insertOne({_id: "postid", seq: 0})
// 定义获取下一个序列值的函数
function getNextSequence(name) {
    var ret = db.counters.findAndModify({
        query: { _id: name },
        update: { $inc: { seq: 1 } },
        new: true
    });
    return ret.seq;
}
// 插入新文章时使用自定义ID
db.posts.insertOne({
    _id: getNextSequence("postid"),
    title: "How to Use MongoDB",
    content: "MongoDB is a NoSQL database..."
});
// 生成的短链接
var postId = getNextSequence("postid");
var shortLink = `http://blogplatform.com/post/${postId}`; // http://blogplatform.com/post/1
3. 特定业务需求

详解:

某些业务逻辑需要使用连续的、自增的数字ID。例如,订单管理系统可能需要连续的订单号,以便于财务对账和客户查询。

示例场景:

一家在线零售商需要为每个订单生成连续的订单号,以便于物流跟踪和客户服务。

// 创建计数器集合
db.counters.insertOne({_id: "orderid", seq: 1000}) // 假设订单号从1001开始
// 定义获取下一个序列值的函数
function getNextSequence(name) {
    var ret = db.counters.findAndModify({
        query: { _id: name },
        update: { $inc: { seq: 1 } },
        new: true
    });
    return ret.seq;
}
// 插入新订单时使用自定义ID
db.orders.insertOne({
    _id: getNextSequence("orderid"),
    customerName: "Alice",
    items: [
        {productId: 1, quantity: 2},
        {productId: 2, quantity: 1}
    ],
    total: 1699.97
});
// 新生成的订单号
var orderId = getNextSequence("orderid");
console.log("New Order ID: " + orderId); // New Order ID: 1001

注意事项

1. 并发问题

详解:

在高并发环境中,多个请求同时访问计数器集合时,必须确保findAndModify操作是原子的,以避免生成重复ID。MongoDB的findAndModify操作是原子的,它可以保证在高并发环境下每次操作都是唯一的,从而避免重复ID的生成。

示例代码:

假设有一个计数器集合counters,我们使用以下代码来确保原子性:

// 获取下一个自增ID的函数
function getNextSequenceValue(sequenceName) {
    var sequenceDocument = db.counters.findAndModify({
        query: { _id: sequenceName },
        update: { $inc: { sequence_value: 1 } },
        new: true,
        upsert: true
    });
    return sequenceDocument.sequence_value;
}
// 使用示例
var nextUserId = getNextSequenceValue('user_id');
db.users.insert({ _id: nextUserId, name: "John Doe" });
2. 性能影响

详解:

频繁更新计数器集合可能会成为性能瓶颈,尤其是在高并发环境中。每次获取新的ID都需要对计数器集合进行读写操作。这种频繁的读写操作可能会影响数据库的整体性能。为了解决这个问题,可以考虑使用分布式ID生成算法,如Twitter的Snowflake,它生成的ID不仅是唯一的,而且是分布式的,不需要频繁访问数据库。

示例代码:

使用JavaScript实现简单版的Snowflake算法:

class Snowflake {
    constructor(workerId, datacenterId, sequence = 0) {
        this.workerId = workerId;
        this.datacenterId = datacenterId;
        this.sequence = sequence;
        this.twepoch = 1288834974657n;
        this.workerIdBits = 5n;
        this.datacenterIdBits = 5n;
        this.maxWorkerId = -1n ^ (-1n 
        let timestamp = this.timeGen();
        while (timestamp 
            timestamp = this.timeGen();
        }
        return timestamp;
    }
    timeGen() {
        return BigInt(Date.now());
    }
    nextId() {
        let timestamp = this.timeGen();
        if (timestamp 
VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]