MongoDB: How to add auto-incrementing field to a collection

Updated: February 3, 2024 By: Guest Contributor Post a comment

Overview

When working with MongoDB, a NoSQL database, there comes a time when you might need an auto-incrementing field within your collection. Unlike SQL databases that provide an AUTO_INCREMENT attribute, MongoDB does not have a built-in feature for auto-incrementing fields. However, this feature can be achieved through various methods, and in this tutorial, we will explore how to implement an auto-incrementing field in a MongoDB collection.

The Requirement for Auto-Increment Fields

In relational databases, the concept of having a unique, auto-incrementing primary key is quite common. This is often used to ensure a unique identifier for each record in a table. In MongoDB, the primary key is the ‘_id’ field, which by default is an ObjectId. The ObjectId fields are unique but are not sequential integers. For certain use cases such as order numbers, invoice IDs, or just ease of readability, there might be a need for a sequentially increasing number as an identifier.

Setup and Basics

First, let’s set up a basic collection we will work with:

db.createCollection("orders")

// Add a few documents
db.orders.insertMany([
  { product: "Laptop", quantity: 10 },
  { product: "Mobile", quantity: 5 }
])

Method 1: Using a counter collection

One popular method of creating an auto-incrementing field in MongoDB is to use a separate collection to maintain a counter for each collection that requires an auto-incrementing field.

Let’s create a counter collection:

db.createCollection("counters")
db.counters.insert({ _id: "order_id", seq: 0 })

We can then define a function to atomically increment and return the next sequence value:

function getNextSequence(name) {
  var ret = db.counters.findAndModify(
    {
      query: { _id: name },
      update: { $inc: { seq: 1 } },
      new: true
    }
  );
  return ret.seq;
}

Each time you add a new order, you would use this function to get the next order ID:

let nextOrderId = getNextSequence("order_id");
db.orders.insert({ _id: nextOrderId, product: "Keyboard", quantity: 4 });

Method 2: Transaction with an update and insert operation

An alternative approach is to use transactions to ensure that the incrementing of the counter and the insertion of the document happen atomically thus preventing concurrent transactions from creating duplicate IDs.

// MongoDB must be running as a replica set to support transactions.
// Start a session and a transaction
const session = db.getMongo().startSession();
const orderCol = session.getDatabase('mydatabase').orders;

session.startTransaction();
const counter = db.counters.findOneAndUpdate(
  { _id: 'order_id' },
  { $inc: { seq: 1 } },
  { new: true, session }
);

orderCol.insertOne({ _id: counter.value.seq, product: 'Mouse', quantity: 7 }, { session });
session.commitTransaction();

Method 3: Handling Concurrency with Optimistic Loop

Instead of using transactions, we can implement our incremental logic in an optimistic loop that keeps trying the insert operation until it succeeds.

function insertWithAutoIncrement(orderDocument) {
  while (true) {
    let sequenceDoc = db.counters.findOne({ _id: "order_id" });
    let nextId = sequenceDoc.seq + 1;
    try {
      db.orders.insert({ _id: nextId, ...orderDocument });
      db.counters.update({ _id: "order_id" }, { $set: { seq: nextId } });
      break;
    } catch(e) {
      // If duplicate key error, retry
      if (e.code === 11000) continue;
      throw e;
    }
  }
}

Precautions and Considerations

Auto-incrementing IDs have their own set of challenges, especially in terms of scalability and performance in distributed systems. Before implementing such a system, consider the following:

  • The need for truly unique IDs versus unique-enough IDs, which can be handled by ObjectId.
  • Enforcing sequential IDs makes sharding more complex, as it introduces hotspots.
  • Using auto-incrementing sequences may lead to guessable IDs, which might be undesirable for security reasons.

Conclusion

To sum up, while MongoDB does not support built-in auto-incrementing fields, the database’s flexibility allows you to implement custom solutions that fit your use case. Whether you opt for a separate counter collection or a transaction-based approach, consider the trade-offs in terms of performance, complexity, and scalability.