Перейти к содержанию

Типы данных

MongoDB поддерживает широкий спектр типов данных в качестве значений в документах.

Основные типы данных

Документы в MongoDB можно рассматривать как «JSON-подобные» в том смысле, что они концептуально похожи на объекты в JavaScript. JSON является простым представлением данных: спецификация может быть описана в одном параграфе (веб-сайт, ссылка на который приведена выше, доказывает это) и содержит только шесть типов данных. Это хорошо во многих отношениях: его легко понять, конвертировать и запоминать. С другой стороны, выразительные возможности формата JSON ограничены, потому что единственными типами являются null, логический тип данных, число, строка, массив и объект.

Хотя эти типы обеспечивают впечатляющую степень выразительности, есть пара дополнительных типов, которые имеют решающее значение для большинства приложений, особенно при работе с базой данных. Например, в JSON нет типа даты, что делает работу с датами еще более раздражающей, чем обычно. Существует тип числа, но он только один – нет никакого способа различить числа с плавающей точкой и целые числа, не говоря уже о различии между 32-битными и 64-битными числами. Невозможно обозначить и другие часто используемые типы, такие как регулярные выражения или функции.

MongoDB добавляет поддержку ряда дополнительных типов данных, сохраняя при этом существенную природу пары типа «ключ/значение» в JSON. Как именно представлены значения каждого типа, зависит от языка, но это список часто поддерживаемых типов и то, как они представлены как часть документа в оболочке. Наиболее распространенные типы:

Null
Нулевой тип можно использовать для обозначения как нулевого значения, так и несуществующего поля:
1
{ "x": null }
Логический тип
Существует логический тип данных, который можно использовать для значений true и false:
1
{ "x": true }
Число
По умолчанию оболочка использует 64-битные числа с плавающей точкой. Таким образом, в оболочке эти числа выглядят «нормально»:
1
2
{"x" : 3.14}
{"x" : 3}

В случае с целыми числами используйте классы NumberInt или NumberLong, которые обозначают 4-байтовые или 8-байтовые целые числа со знаком соответственно.

1
2
{"x" : NumberInt ("3")}
{"x" : NumberLong ("3")}
Строка
Любая строка символов в кодировке UTF-8 может быть представлена с использованием типа строки:
1
{ "x": "foobar" }
Дата
MongoDB хранит даты в виде 64-битных целых чисел, обозначающих миллисекунды с момента эпохи Unix (1 января 1970 г.). Часовой пояс не сохраняется:
1
{ "x": new Date() }
Регулярное выражение
Запросы могут использовать регулярные выражения, используя синтаксис регулярных выражений JavaScript:
1
{"x" : / foobar / i}
Массив
Наборы или списки значений могут быть представлены в виде массивов:
1
{ "x": ["a", "b", "c"] }
Встраиваемый документ
Документы могут содержать целые документы, встроенные в качестве значений в родительский документ:
1
{ "x": { "foo": "bar" } }
Идентификатор объекта
Идентификатор объекта – это 12-байтовый идентификатор для документов:
1
{"x" : ObjectId ()}

Есть также несколько менее распространенных типов, которые могут вам понадобиться, в том числе:

Двоичные данные
Двоичные данные – это строка из произвольных байтов. Ими нельзя манипулировать из оболочки. Двоичные данные – единственный способ сохранять строки не в формате UTF-8 в базе данных.
Код
MongoDB также позволяет хранить произвольный код JavaScript в запросах и документах:
1
{"x" : function() { /* ... */ }}

Наконец, существует несколько типов, которые в основном используются внутри (или заменяются другими типами). Их описание будет даваться в тексте по мере необходимости.

Даты

В JavaScript класс Date используется для типа даты MongoDB. При создании нового объекта Date всегда вызывайте метод new Date(), а не просто Date(). Вызов конструктора в качестве функции (т. е. не используя слово new) возвращает строковое представление даты, а не фактический объект Date. Это не выбор MongoDB; так работает JavaScript. Если вы не будете осторожны при использовании конструктора Date, это может привести к мешанине из строк и дат. Строки не совпадают с датами, и наоборот, поэтому это может вызвать проблемы с удалением, обновлением, запросом… практически со всем.

Даты в оболочке отображаются с использованием настроек местного часового пояса. Однако даты в базе данных просто хранятся в миллисекундах с момента начала эпохи, поэтому у них нет никакой информации о часовом поясе, связанной с ними. (Информация о часовом поясе, конечно, может храниться в качестве значения другого ключа.)

Массивы

Массивы – это значения, которые могут использоваться взаимозаменяемо как для упорядоченных (как если бы они были списками, стеками или очередями), так и для неупорядоченных операций (как если бы они были наборами).

В приведенном ниже документе ключ "things" имеет значение массива:

1
{"things" : ["pie", 3.14]}

Как видно из этого примера, массивы могут содержать различные типы данных в качестве значений (в данном случае это строка и число с плавающей запятой). Фактически значения массива могут быть любыми поддерживаемыми типами значений для обычных пар типа «ключ/значение», даже для вложенных массивов.

Одна из замечательных особенностей массивов в документах заключается в том, что MongoDB «понимает» их структуру и знает, как добраться до внутренностей массивов для выполнения операций с их содержимым. Это позволяет нам делать запросы к массивам и создавать индексы, используя их содержимое. Например, в предыдущем примере MongoDB может запрашивать все документы, где 3.14 является элементом массива "things". Если это обычный запрос, можно даже создать индекс для ключа "things", чтобы повысить скорость запроса.

MongoDB также допускает атомарные обновления, которые изменяют содержимое массивов, например доступ к массиву и изменение значения "pie" на pi. Мы будем встречать и другие примеры этих типов операций на протяжении всей книги.

Вложенные документы

Документ может использоваться как значение ключа. Такой документ называется вложенным. Вложенные документы могут использоваться для организации данных более естественным образом, чем просто плоская структура пар типа «ключ/значение».

Например, если у нас есть документ, обозначающий человека, и мы хотим сохранить адрес этого человека, можно вложить эту информацию во вложенный документ "address":

1
2
3
4
5
6
7
8
{
"name" : "John Doe",
"address" : {
"street" : "123 Park Street",
"city" : "Anytown",
"state" : "NY"
}
}

Значением ключа "address" в этом примере является вложенный документ со своими собственными парами типа «ключ/значение» для "street", "city" и "state".

Как и в случае с массивами, MongoDB «понимает» структуру вложенных документов и может использовать их для создания индексов, выполнения запросов или обновлений.

Мы обсудим дизайн схемы более подробно позже, но, даже основываясь на этом базовом примере, можно увидеть, что вложенные документы могут изменить способ работы с данными. В реляционной СУБД предыдущий документ, вероятно, будет смоделирован как две отдельные строки в двух разных таблицах (people и addresses). В MongoDB мы можем встраивать документ "address" непосредственно в документ "person". Таким образом, при правильном использовании вложенные документы могут обеспечить более естественное представление информации.

Обратная сторона состоит в том, что в случае с MongoDB повторений данных может быть больше. Предположим, что addresses были отдельной таблицей в реляционной базе данных и нам нужно было исправить опечатку в адресе. Когда мы выполняли соединение с таблицами people и addresses, то получали обновленный адрес для всех, кто использует его. При работе с MongoDB нам нужно исправлять опечатку в документе каждого человека.

_id и ObjectId

Каждый документ, хранящийся в MongoDB, должен иметь ключ "_id". Значение ключа "_id" может быть любого типа, но по умолчанию используется ObjectId. В одной коллекции каждый документ должен иметь уникальное значение "_id", что гарантирует уникальную идентификацию каждого документа в коллекции. То есть если бы у вас было две коллекции, каждая из них могла бы иметь документ, в котором значение "_id" было бы равно 123. Однако ни одна коллекция не может содержать более одного документа с "_id", равным 123.

ObjectId

ObjectId является типом по умолчанию для "_id". Класс ObjectId разработан так, чтобы быть легковесным, но при этом его легко можно было генерировать глобально уникальным способом на разных машинах. Распределенная природа MongoDB является основной причиной, по которой она использует ObjectId, а не что-то более традиционное, например автоинкрементный первичный ключ: синхронизировать автоинкрементные первичные ключи на нескольких серверах сложно и отнимает много времени. Поскольку MongoDB была спроектирована как распределенная СУБД, было важно иметь возможность генерировать уникальные идентификаторы в разделенной среде.

ObjectId использует 12 байт памяти, что дает ему строковое представление, состоящее из 24 шестнадцатеричных цифр: по две цифры на каждый байт. Из-за этого он кажется больше, чем есть на самом деле, что заставляет некоторых нервничать. Важно отметить, что хотя ObjectId часто представляется в виде гигантской шестнадцатеричной строки, эта строка на самом деле в два раза длиннее сохраняемых данных.

Если вы создадите несколько новых ObjectId в быстрой последовательности, то увидите, что только последние несколько цифр меняются каждый раз. Кроме того, пара цифр в середине ObjectId изменится, если вы разместите творения на пару секунд. Это объясняется манерой создания ObjectId. 12 байт ObjectId генерируются следующим образом:

0 1 2 3 4 5 6 7 8 9 10 11
Временная метка Случайное значение Счетчик (случайное начальное значение)

Первые четыре байта ObjectId – это временная отметка в секундах с момента начала эпохи. Это обеспечивает пару полезных свойств:

  • временная метка в сочетании с последующими пятью байтами (которые будут описаны ниже) обеспечивает уникальность с точностью до секунды;
  • поскольку временная метка идет первой, ObjectId будут сортироваться в порядке грубой вставки. Это не является надежной гарантией, но имеет некоторые приятные свойства, например делает ObjectId эффективными для индексации;
  • в этих четырех байтах существует неявная метка времени, когда был создан каждый документ. Большинство драйверов предоставляют метод для извлечения этой информации из ObjectId.

Поскольку в ObjectId используется текущее время, некоторые пользователи беспокоятся о том, что на их серверах нужно будет синхронизировать часы. Хотя синхронизированные часы являются неплохой идеей по другим причинам, фактическая временная метка не имеет значения для ObjectId, разве только то, что она часто является новой (раз в секунду) и увеличивается.

Следующие пять байт ObjectId – это случайное значение. Последние три байта представляют собой счетчик, который начинается со случайного значения, чтобы избежать создания конфликтующих ObjectId на разных машинах.

Таким образом, эти первые девять байт ObjectId гарантируют его уникальность на разных машинах и процессов в течение одной секунды. Последние три байта – просто инкрементный счетчик, который отвечает за уникальность в течение секунды в одном процессе.

Это позволяет генерировать до 2563 (16 777 216) уникальных ObjectId на процесс в одну секунду.

Автогенерация _id

Как было сказано ранее, если при вставке документа ключ "_id" отсутствует, он будет добавлен автоматически во вставленный документ. Это может быть обработано сервером MongoDB, но обычно делается драйвером на стороне клиента.

Комментарии