MongoDB中自動增長ID詳解(實(shí)現(xiàn)、應(yīng)用及優(yōu)化)
引言
在MongoDB中,自動增長的功能主要通過使用數(shù)據(jù)庫的ObjectId或自定義的序列來實(shí)現(xiàn)。ObjectId是MongoDB默認(rèn)的主鍵類型,它是唯一的并且具有一定的排序特性。然而,在某些場景下,可能需要使用自定義的自動增長ID,例如在某些遺留系統(tǒng)中或者為了更好的用戶體驗(yàn)。
基本語法和命令
使用ObjectId
ObjectId是MongoDB默認(rèn)的主鍵類型,它由12字節(jié)組成,包括時間戳、機(jī)器標(biāo)識符、進(jìn)程ID和計(jì)數(shù)器。每次插入新文檔時,MongoDB會自動生成一個新的ObjectId。
插入新文檔時,_id字段會自動生成:
db.collection.insertOne({name: "example"})
自定義自動增長ID
如果需要自定義自動增長ID,可以使用以下方法:
創(chuàng)建計(jì)數(shù)器集合:
創(chuàng)建一個專門的集合來存儲序列計(jì)數(shù)器。
db.createCollection("counters")
db.counters.insertOne({_id: "productid", seq: 0})
定義獲取下一個序列值的函數(shù):
使用findAndModify原子操作來獲取并增加序列值。
function getNextSequence(name) {
var ret = db.counters.findAndModify({
query: { _id: name },
update: { $inc: { seq: 1 } },
new: true
});
return ret.seq;
}
插入新文檔并使用自定義ID:
在插入新文檔時,調(diào)用該函數(shù)以生成新的ID。
db.products.insertOne({
_id: getNextSequence("productid"),
name: "example"
})
示例
以下是完整的示例代碼:
- 創(chuàng)建計(jì)數(shù)器集合并插入初始值:
db.counters.insertOne({_id: "userid", seq: 0})
- 定義獲取下一個序列值的函數(shù):
function getNextSequence(name) {
var ret = db.counters.findAndModify({
query: { _id: name },
update: { $inc: { seq: 1 } },
new: true
});
return ret.seq;
}
- 插入新文檔并使用自定義ID:
db.users.insertOne({
_id: getNextSequence("userid"),
name: "John Doe"
})
應(yīng)用場景
1. 遺留系統(tǒng)遷移
詳解:
在許多企業(yè)中,遺留系統(tǒng)使用關(guān)系數(shù)據(jù)庫(如MySQL、PostgreSQL等),并依賴于自增ID作為主鍵。如果計(jì)劃將這些系統(tǒng)遷移到MongoDB中,直接使用MongoDB的ObjectId可能會導(dǎo)致兼容性問題或破壞現(xiàn)有業(yè)務(wù)邏輯。因此,自定義自動增長ID可以簡化遷移過程,保留原有系統(tǒng)的ID生成機(jī)制。
示例場景:
假設(shè)一家電子商務(wù)公司決定將其產(chǎn)品數(shù)據(jù)庫從MySQL遷移到MongoDB。原系統(tǒng)中的產(chǎn)品ID是自增的整數(shù)。
// MySQL中的產(chǎn)品表
CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255),
price DECIMAL(10, 2)
);
// 原有數(shù)據(jù)
INSERT INTO products (name, price) VALUES ('Laptop', 999.99), ('Smartphone', 699.99);
在遷移到MongoDB時,需要保留這些自增的ID。
// 在MongoDB中創(chuàng)建計(jì)數(shù)器集合
db.counters.insertOne({_id: "productid", seq: 2}) // 假設(shè)MySQL中已有兩個產(chǎn)品
// 定義獲取下一個序列值的函數(shù)
function getNextSequence(name) {
var ret = db.counters.findAndModify({
query: { _id: name },
update: { $inc: { seq: 1 } },
new: true
});
return ret.seq;
}
// 插入新產(chǎn)品時使用自定義ID
db.products.insertOne({
_id: getNextSequence("productid"),
name: "Tablet",
price: 499.99
});
2. 用戶友好ID
詳解:
對于前端用戶,使用連續(xù)的、自增的數(shù)字ID比使用ObjectId更友好、更容易記憶。特別是在需要用戶手動輸入或引用ID的場景中,自增ID會更簡潔、易讀。
示例場景:
一個博客平臺希望用戶能夠通過短鏈接直接訪問文章。使用自增ID可以生成短鏈接,提升用戶體驗(yàn)。
// 創(chuàng)建計(jì)數(shù)器集合
db.counters.insertOne({_id: "postid", seq: 0})
// 定義獲取下一個序列值的函數(shù)
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. 特定業(yè)務(wù)需求
詳解:
某些業(yè)務(wù)邏輯需要使用連續(xù)的、自增的數(shù)字ID。例如,訂單管理系統(tǒng)可能需要連續(xù)的訂單號,以便于財務(wù)對賬和客戶查詢。
示例場景:
一家在線零售商需要為每個訂單生成連續(xù)的訂單號,以便于物流跟蹤和客戶服務(wù)。
// 創(chuàng)建計(jì)數(shù)器集合
db.counters.insertOne({_id: "orderid", seq: 1000}) // 假設(shè)訂單號從1001開始
// 定義獲取下一個序列值的函數(shù)
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
注意事項(xiàng)
1. 并發(fā)問題
詳解:
在高并發(fā)環(huán)境中,多個請求同時訪問計(jì)數(shù)器集合時,必須確保findAndModify操作是原子的,以避免生成重復(fù)ID。MongoDB的findAndModify操作是原子的,它可以保證在高并發(fā)環(huán)境下每次操作都是唯一的,從而避免重復(fù)ID的生成。
示例代碼:
假設(shè)有一個計(jì)數(shù)器集合counters,我們使用以下代碼來確保原子性:
// 獲取下一個自增ID的函數(shù)
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. 性能影響
詳解:
頻繁更新計(jì)數(shù)器集合可能會成為性能瓶頸,尤其是在高并發(fā)環(huán)境中。每次獲取新的ID都需要對計(jì)數(shù)器集合進(jìn)行讀寫操作。這種頻繁的讀寫操作可能會影響數(shù)據(jù)庫的整體性能。為了解決這個問題,可以考慮使用分布式ID生成算法,如Twitter的Snowflake,它生成的ID不僅是唯一的,而且是分布式的,不需要頻繁訪問數(shù)據(jù)庫。
示例代碼:
使用JavaScript實(shí)現(xiàn)簡單版的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. 唯一性保證
詳解:
在分布式環(huán)境中,確保ID的唯一性是一個挑戰(zhàn)。即使在多個節(jié)點(diǎn)上生成ID,也必須保證每個ID是唯一的。通過使用分布式ID生成算法(如上所述的Snowflake),可以在多個節(jié)點(diǎn)上生成唯一的ID,而不需要依賴單一的數(shù)據(jù)庫計(jì)數(shù)器。
示例代碼:
繼續(xù)使用上面的Snowflake示例,在多個服務(wù)節(jié)點(diǎn)上生成唯一ID:
// 服務(wù)節(jié)點(diǎn)1 const snowflake1 = new Snowflake(1n, 1n); const id1 = snowflake1.nextId(); console.log(id1.toString()); // 唯一ID // 服務(wù)節(jié)點(diǎn)2 const snowflake2 = new Snowflake(2n, 1n); const id2 = snowflake2.nextId(); console.log(id2.toString()); // 唯一ID
通過以上示例,在不同的服務(wù)節(jié)點(diǎn)上生成的ID仍然是唯一的,確保了分布式環(huán)境中的ID唯一性。
總結(jié)
在MongoDB中,ObjectId提供了一種簡單且有效的唯一標(biāo)識符生成方式,但在某些情況下,自定義的自動增長ID可能更適合。通過創(chuàng)建計(jì)數(shù)器集合和使用findAndModify操作,可以實(shí)現(xiàn)自定義的自動增長ID。需要注意的是,在實(shí)現(xiàn)自定義自動增長ID時,必須處理好并發(fā)和性能問題,以確保ID的唯一性和生成效率。
以上就是MongoDB中自動增長ID詳解(實(shí)現(xiàn)、應(yīng)用及優(yōu)化)的詳細(xì)內(nèi)容,更多關(guān)于MongoDB自動增長ID的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
MongoDB 3.4配置文件避免入坑的注意事項(xiàng)
最近在配置mongodb的時候遇到了一些問題,現(xiàn)總結(jié)出來方便以后需要或同樣遇到該問題的朋友們參考,下面這篇文章主要給大家介紹了關(guān)于MongoDB 3.4配置文件時避免入坑的兩個注意事項(xiàng),需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)下吧。2017-09-09
Robo可視化mongoDb實(shí)現(xiàn)操作解析
這篇文章主要介紹了Robo可視化mongoDb實(shí)現(xiàn)操作解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-12-12
MongoDB數(shù)據(jù)庫forEach循環(huán)遍歷用法
這篇文章主要介紹了MongoDB數(shù)據(jù)庫forEach循環(huán)遍歷用法,需要的朋友可以參考下2014-07-07
MongoDB數(shù)據(jù)庫部署環(huán)境準(zhǔn)備及使用介紹
這篇文章主要為大家介紹了MongoDB數(shù)據(jù)庫部署環(huán)境準(zhǔn)備以及基本的使用介紹,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03
MongoDB中數(shù)據(jù)的替換方法實(shí)現(xiàn)類Replace()函數(shù)功能詳解
這篇文章主要介紹了MongoDB中數(shù)據(jù)的替換方法實(shí)現(xiàn)類Replace()函數(shù)功能詳解,需要的朋友可以參考下2020-02-02
將MongoDB加入到Windows的本地服務(wù)項(xiàng)的方法
下面主要針對MongoDB在Windows下加入本地服務(wù)項(xiàng)做一些簡單的分享。以方便剛接觸MongoDB并在Windows環(huán)境下進(jìn)行開發(fā)的同學(xué)2014-08-08

