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