Fun Mongo #1: ObjectId
Как устроен Primary Key
Как устроен Primary Key
После долгой работы с реляционными БД и, в частности, с SQL, переход на документную базу MongoDB не так прост, как кажется. Надо немного перестроить менталитет. Это как с PHP/Python перейти на Node.js — привыкнуть к асинхронности и колбэкам требует времени.
Так и с NoSQL, в частности с MongoDB. Первое что бросается в глаза — это необычные ID. Это не автоинкремент, как в других базах. Более того, из коробки вообще нет автоинкрементов и их нужно реализовывать самому, если они нужны.
Начнем с ObjectId. Кажется что это хеш и как же делать поиски вида:
SELECT * FROM t WHERE id > 1234
На самом деле в MongoDB так же можно строить запросы по _id (поле айди в монге с префиксом):
db.t.find({ _id: { $gt: ObjectId('5e1b270142dcae0010186f02') } })
И так же по этому полю можно сортировать.
Не все знают, но это не просто хеш, это функция от времени, и хеш можно привести к DateTime. И делается это достаточно просто (пример на JS):
function objectIdToDateTime(objectId) {
return new Date(parseInt(objectId.substring(0,8),16)*1e3)
}
Пробуем наш айдишник:
objectIdToDateTime('5e1b270142dcae0010186f02')
// Sun Jan 12 2020 17:02:41 GMT+0300
В самой монге есть возможность получить время от ObjectId:
ObjectId("5e1b270142dcae0010186f02").getTimestamp()
// ISODate("2020-01-12T17:02:41.000+03:00")
Если хочется сгенерить ObjectId, то нужно сделать обратное преобразование:
function generateObjectId() {
return Math.floor((new Date).getTime()/1e3)
.toString(16) + '0'.repeat(16)
}
Тут мы генерим первую часть хеша, но вот конец хеша у нас состоит из нулей. Оригинальный ObjectId во второй части содержит некий рандомный хеш. Мы можем использовать алгоритм для генерации UID:
function generateObjectId() {
return Math.floor((new Date).getTime()/1e3).toString(16) +
(('x'.repeat(16).replace(/x/g,
_=>(Math.random()*16|0).toString(16))
))
}
Варианты получения UID можно прочитать в статье
Прелесть ObjectId в том, что не так просто вычислить следующий ID, в отличие от автоинкремента. Так же нельзя понять сколько в базе записей. Но зато можно получить дату создания записи.
А еще плюсом такой генерации Primary Key заключается в том, что мы можем получить PK до того, как запишем данные в базу. Таким образом удобно строить различные ORM, позволяющие создавать полноценный объект коллекции, до его записи в базу. Конечно в SQL базах так же можно получить last id и все такое, но это будет сложнее и потребует транзакций, чтобы он не успел измениться. Здесь же мы даже не обращаемся к базе.
В следующем посте разберем как создавать автоинкременты в MongoDB.