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

在MongoDB中,自动增长的功能主要通过使用数据库的ObjectId或自定义的序列来实现。ObjectId是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 << this.workerIdBits);this.maxDatacenterId = -1n ^ (-1n << this.datacenterIdBits);this.sequenceBits = 12n;this.workerIdShift = this.sequenceBits;this.datacenterIdShift = this.sequenceBits + this.workerIdBits;this.timestampLeftShift = this.sequenceBits + this.workerIdBits + this.datacenterIdBits;this.sequenceMask = -1n ^ (-1n << this.sequenceBits);this.lastTimestamp = -1n;}tilNextMillis(lastTimestamp) {let timestamp = this.timeGen();while (timestamp <= lastTimestamp) {timestamp = this.timeGen();}return timestamp;}timeGen() {return BigInt(Date.now());}nextId() {let timestamp = this.timeGen();if (timestamp < this.lastTimestamp) {throw new Error('Clock moved backwards. Refusing to generate id');}if (this.lastTimestamp === timestamp) {this.sequence = (this.sequence + 1n) & this.sequenceMask;if (this.sequence === 0n) {timestamp = this.tilNextMillis(this.lastTimestamp);}} else {this.sequence = 0n;}this.lastTimestamp = timestamp;return ((timestamp - this.twepoch) << this.timestampLeftShift) |(this.datacenterId << this.datacenterIdShift) |(this.workerId << this.workerIdShift) |this.sequence;}
}// 使用示例
const snowflake = new Snowflake(1n, 1n);
const id = snowflake.nextId();
console.log(id.toString());  // 生成唯一ID
3. 唯一性保证

详解
在分布式环境中,确保ID的唯一性是一个挑战。即使在多个节点上生成ID,也必须保证每个ID是唯一的。通过使用分布式ID生成算法(如上所述的Snowflake),可以在多个节点上生成唯一的ID,而不需要依赖单一的数据库计数器。

示例代码

继续使用上面的Snowflake示例,在多个服务节点上生成唯一ID:

// 服务节点1
const snowflake1 = new Snowflake(1n, 1n);
const id1 = snowflake1.nextId();
console.log(id1.toString());  // 唯一ID// 服务节点2
const snowflake2 = new Snowflake(2n, 1n);
const id2 = snowflake2.nextId();
console.log(id2.toString());  // 唯一ID

通过以上示例,在不同的服务节点上生成的ID仍然是唯一的,确保了分布式环境中的ID唯一性。

总结

在MongoDB中,ObjectId提供了一种简单且有效的唯一标识符生成方式,但在某些情况下,自定义的自动增长ID可能更适合。通过创建计数器集合和使用findAndModify操作,可以实现自定义的自动增长ID。需要注意的是,在实现自定义自动增长ID时,必须处理好并发和性能问题,以确保ID的唯一性和生成效率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/856453.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大模型应用开发实践:RAG与Agent

RAG planning是任务拆解的一些方法。 Agent RAG现在基本上推荐LangChain开发框架。而Agent目前没有一个通用的好的开发框架/范式。 学习路径

程序员做电子书产品变现的复盘(10)

前面提到了我对竞争对手发起的投诉&#xff0c;没想到这竟然引发了一场规模庞大的战争&#xff0c;意外地促进了我国版权合规化的进步 。 以前&#xff0c;每当收到版权方的通知&#xff0c;无论APP有多受欢迎&#xff0c;我都会立即下架&#xff0c;一方面是为了避免法律风险…

达梦8 兼容MySQL语法支持非分组项作为查询列

MySQL 数据库迁移到达梦后&#xff0c;部分GROUP BY语句执行失败&#xff0c;报错如下&#xff1a; 问题原因&#xff1a; 对于Oracle数据库&#xff0c;使用GROUP BY时&#xff0c;SELECT中的非聚合列必须出现在GROUP BY后面&#xff0c;否则就会报上面的错误&#xff0c;达梦…

使用宝塔面板搭建Flask项目保姆级喂饭教程

目录 零.前言 一.准备工作 1.1创建requirements.txt文件 1.2将项目打包为压缩文件 1.3租一台服务器 1.4部署宝塔面板 二.宝塔面板(服务器)上的操作 2.1将本地Flask项目上传到服务器 2.2添加Python项目 2.3配置Python项目 2.4配置Nginx 2.5宝塔面板放行端口 2.6在服…

【html5的video标签在移动端的使用】【微信内部浏览器video自动播放】【vue-video-player】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、使用步骤1. html部分2.js部分 二、使用插件vue-video-player1、下载插件2、使用3、在组件中使用 三、最终的版本&#xff08;自用版本&#xff09;四、vide…

linux运维工作常用命令

命令 --help //可以快速查看命令的用法及其各种选项 cat /etc/passwd //查看所有用户 who //查看已登录用户 sudo useradd 用户名 …

首个AI高考评测结果出炉,GPT-4o排名第二

近日&#xff0c;上海人工智能实验室利用其自主研发的“司南”评测体系OpenCompass&#xff0c;对国内外多个知名大模型进行了一场特殊的“高考”。这些来自阿里巴巴、智谱AI、Mistral等机构&#xff0c;以及OpenAI的GPT-4o等“考生”&#xff0c;接受了新课标I卷“语数外”的全…

百万级 QPS 接入层网关架构方案演进

文章目录 前言1、单机架构2、DNS 轮询3、Nginx 单机4、Nginx 主备 Keepalived5、LVS 主备 Keepalived Nginx 集群6、LVS 主备 Keepalived Nginx 集群 DNS 轮询 前言 随着PC、移动互联网的快速发展&#xff0c;越来越多的人通过手机、电脑、平板等设备访问各种各样APP、网…

找不到com.fasterxml.jackson.core.exc.StreamWriteException的类文件

1. 前言: 使用springboot搭建的项目, 需要使用 jackson 更改json文件的内容; maven管理jar包, 导入jar包版本信息如下: <!-- 读写json文件所需依赖 --> <dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databin…

C++语法06 格式化输出及保留小数点后指定位数

格式化输出 格式化输出所用的函数为 printf&#xff0c;它可以输出任意位数的小数。 使用格式&#xff1a;printf(“%.nf”,a)。这句话的作用是将变量a保留n位小数输出。 注意事项&#xff1a; 1、这里的n&#xff0c;需要具体化为一个数字&#xff0c;保留几位小数&#x…

【ARMv8/v9 GIC 系列 3 -- GIC 的 类型寄存器 GICD_TYPER】

文章目录 GIC 类型寄存器 GICD_TYPERESPI_Range, 位[31:27]RSS, 位[26]No1N, 位[25]A3V, 位[24]IDBits, 位[23:19]DVIS, 位[18]LPIs, 位[17]MBIS, 位[16]NUM_LPIs, 位[15:11]SecurityExtn, 位[10]NMI, 位[9]ESPI, 位[8]CPUNumber, 位[7:5]ITLinesNumber, 位[4:0]GIC 类型寄存器…

GDB调试助手用法详解

什么是GDB GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具。或许&#xff0c;各位比较喜欢那种图形界面方式的&#xff0c;像VC、BCB等IDE的调试&#xff0c;但如果你是在 UNIX平台下做软件&#xff0c;你会发现GDB这个调试工具有比VC、BCB的图形化调试器更强大的功能…

朗科HD10M2Pr震撼上市,自带风扇极速降温,匹敌私有云

近日,存储领域的领军企业朗科旗下全资子公司朗科创新宣布,其最新款磁吸硬盘盒HD10M2Pr正式上市。这款产品凭借超薄设计、极速降温、高速传输等多项优势,迅速成为了行业内的讨论焦点。 随着移动设备使用的普及和短视频内容的日益丰富,对于存储空间不断增长的需求逐渐成为日常生活…

liquibase 错误: 无法打开扩展控制文件 “c:/postgresql/14/share/extension/timescaledb.control“

背景 运行springboot项目时&#xff0c;在一开始运行liquibase部分报错&#xff0c;报错信息如题所示&#xff0c;经查&#xff0c;是因为安装postgresql时没有安装这个名为timescaledb的扩展&#xff0c;所以接下来就是安装timescaledb扩展。 安装timescaledb扩展 我的电脑…

Semaphroe + CountDown

Semaphore 基本使用 synchronized 可以起到锁的作用&#xff0c;但某个时间段内&#xff0c;只能有一个线程允许执行 Semaphore&#xff08;信号量&#xff09;用来限制能同时访问共享资源的线程上限&#xff08;不是资源数&#xff09;&#xff0c;非重入锁 不像之前的reen…

rsync同步目录脚本

假设有两台服务器的示例 IP 地址为&#xff1a; Server A: 192.168.1.100Server B: 192.168.1.200 现在来解释如何使用这个脚本进行服务器之间文件夹内容的同步&#xff0c;保留路径和服务器信息的抽象化。 1. 脚本文件位置和权限 假设脚本文件位于 /root/script.sh&#x…

Hadoop面试题总结

一 、介绍一下hadoop 综述:hadoop是一个适合海量数据的分布式存储和分布式计算的平台 分述:hadoop包含三大组件&#xff0c;分别是HDFS、MapReduce和YARN --HDFS(分布式文件系统) HDFS集群由NameNode,DataNode,SecondaryNameNode构成NameNode&#xff1a;主要负责接受用户请求…

XML 应用程序

XML 应用程序 XML&#xff08;可扩展标记语言&#xff09;是一种用于存储和传输数据的标记语言。它是一种自我描述的语言&#xff0c;允许用户定义自己的标签和文档结构。XML广泛应用于各种应用程序中&#xff0c;包括网站开发、数据交换、文档管理等。本文将探讨XML的一些主要…

导入导出带下拉框模版(EasyExcel)

前言 项目进行到新的一个迭代了&#xff0c;赶了1周需求&#xff0c;接口终于处理完了。分享记录下迭代中处理导入、导出、下载模版功能的细节吧。 一、场景 EasyExcel&#xff08;阿里&#xff09;实现Excel数据处理三层表头&#xff0c;第二、三层表头动态数据根据第二、三层…

RabbitMQ(六)仲裁队列、流式队列、异地容灾(联邦队列Federation Queue)

文章目录 仲裁队列1、创建交换机2、创建仲裁队列3、验证主节点宕机不影响消息发送和接收 流式队列&#xff08;不推荐&#xff0c;Kafka主场&#xff09;概念 异地容灾一、Federation插件概述 二、Federation交换机1、总体说明2、准备工作3、启用联邦插件4、添加上游连接端点5、…