Статьи

Сравнение данных в MongoDB, MySQL и TokuMX

Много говорится о различиях в данных между MySQL и MongoDB . Такие вещи, как «MongoDB на основе документов», «MySQL является реляционным», «InnoDB имеет ключ кластеризации» и т. Д. Некоторые могут задаться вопросом, как TokuDB , наш механизм хранения MySQL, и TokuMX , наш продукт MongoDB, соответствуют этим макетам данных , Я не смог найти ничего описывающего различия с помощью простого поиска в Google, поэтому я решил написать пост, объясняющий, как эти технологии сравниваются.

Так кто же здесь игроки? С MySQL пользователи, вероятно, знакомы с двумя механизмами хранения: MyISAM , исходным по умолчанию до MySQL 5.5, и InnoDB , текущим по умолчанию с MySQL 5.5. MongoDB имеет только один механизм хранения, и мы будем называть его «ванильным хранилищем Mongo». И, конечно же, есть TokuDB для MySQL и TokuMX.

Во-первых, давайте разберемся с краткой терминологией. Документы и коллекции в MongoDB можно рассматривать как строки и таблицы в MySQL соответственно. И хотя поля MongoDB не идентичны, они похожи на столбцы в MySQL. Полное сопоставление SQL с MongoDB можно найти здесь . Когда я ссылаюсь на MySQL, то, что я говорю, относится к TokuDB, InnoDB и MyISAM. Когда я говорю MongoDB, то, что я говорю, относится к TokuMX и ванильному хранилищу Mongo.

Как выглядят коллекции и таблицы?

Здесь мы имеем первое существенное различие между MySQL и MongoDB. MySQL имеет схемы, тогда как MongoDB не имеет схем. Давайте посмотрим на пример. В MongoDB мы можем сделать следующее:

> db.createCollection("exampleColl")
{ "ok" : 1 }
> db.exampleColl.insert({ _id : 1 , a : 10 , aa : 100})
> db.exampleColl.insert({ _id : 2 , b : 20 , bb : 200 , bbb : 2000})
> db.exampleColl.find()
{ "_id" : 1, "a" : 10, "aa" : 100 }
{ "_id" : 2, "b" : 20, "bb" : 200, "bbb" : 2000 }

Мы создали коллекцию «exampleColl» и вставили в нее два документа. Обратите внимание, что эти два документа не имеют общих полей, кроме «_id» (мы скоро узнаем, почему). Вот как MongoDB без схемы. Каждый документ может иметь свой собственный набор определенных полей, и эти поля могут быть любого типа. На практике многие документы в коллекции имеют общие поля, но это выбор приложения, а не строгое правило коллекции.

Это не может быть сделано с MySQL. В MySQL есть что-то, похожее на это:

mysql> create table foo (a int, aa int, aaa int) engine=TokuDB;
Query OK, 0 rows affected (0.02 sec)

mysql> insert into foo values (1,10,100);
Query OK, 1 row affected (0.01 sec)

mysql> insert into foo values (2,20,200);
Query OK, 1 row affected (0.00 sec)

mysql> select * from foo;
+------+------+------+
| a    | aa   | aaa  |
+------+------+------+
|    1 |   10 |  100 |
|    2 |   20 |  200 |
+------+------+------+
2 rows in set (0.00 sec)

В MySQL каждая вставленная строка должна иметь значение для каждого столбца или быть NULL. Итак, если я хочу, чтобы в одной строке был только столбец «а», я бы сделал:

insert into foo values (10, NULL, NULL);

тогда как в MongoDB я бы просто вставил документ, который не имеет поля «aaa»:

db.exampleColl.insert({ _id : 3 , a : 10 })

Я уверен, что пользователи замечают столбец «_id» в примерах MongoDB, которых нет в примерах MySQL. Я скоро к этому вернусь.

Что такое идентификатор строки?

Все рассмотренные здесь механизмы хранения сохраняют весь документ или строку ГДЕ-ТО. Не имеет значения, находится ли это местоположение в индексе дерева фракталов , B-дереве или плоском файле. Чтобы найти всю строку, база данных должна иметь некоторый идентификатор для использования. Мы называем это идентификатором строки.

Итак, идентификатор строки — это логически некоторая информация, которую механизм хранения может использовать для извлечения всей строки. Все двигатели хранения имеют один. Мы углубимся в особенности ниже, но они могут быть (первичными) ключами в B-Tree или Fractal Tree, или смещением в плоский файл, или теоретически что-то еще.

Как работают вторичные индексы (заметьте, пока не говорят первичные ключи)?

Вторичные индексы в MongoDB и MySQL очень похожи. Вторичные индексы объявляют поля или столбцы для сортировки отдельно от остальных данных и используют идентификаторы строк для ссылки на оставшуюся часть строки для запроса.

Давайте посмотрим на быстрые примеры с MySQL. Предположим, мы сделали следующее:

mysql> create table foo (a int, b int, c int, key (a)) engine=TokuDB;
mysql> insert into foo values (1, 10, 100), (5, 50, 500), (3, 30, 300), (NULL, 40, 400);

Предположим абстрактно, что идентификатором строки для этих четырех строк являются соответственно «id1», «id2», «id3 ″ и« id4 ». С этой таблицей будут связаны две логические структуры данных: одна для всех данных и одна для вторичного индекса. Основное хранилище данных будет иметь такие данные, как:

+--------+-------+------+------+
| row_id | a     | b    | c    |
+--------+-------+------+------+
|    id1 |     1 |   10 |  100 |
|    id2 |     5 |   50 |  500 |
|    id3 |     3 |   30 |  300 |
|    id4 |  NULL |   40 |  400 |
+--------+-------+------+------+

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

+-------+--------+
| a     | row_id |
+-------+--------+
|  NULL |    id4 |
|     1 |    id1 |
|     3 |    id3 |
|     5 |    id2 |
+-------+--------+

Таким образом, любой запрос на «a» может использовать row_id для получения всей строки.

Аналогичный пример в MongoDB. Предположим, у нас была коллекция «foo», и мы сделали следующее:

db.foo.ensureIndex({ a : 1 })
db.foo.insert([{ a : 1, b : 10, c : 100}, { a : 5, b : 50, c : 500},
               { a : 3, b : 30, c : 300 }, { b : 40, c : 400 }])

Точно так же вторичный будет иметь ту же структуру:

+-------+--------+
| a     | row_id |
+-------+--------+
|  null |    id4 |
|     1 |    id1 |
|     3 |    id3 |
|     5 |    id2 |
+-------+--------+

«Нуль» означает, что поле «а» не существовало в документе, связанном с «id4».

Есть больше различий между вторичными индексами в MongoDB v. MySQL, в которые я не буду здесь вдаваться (например, объявление полей в индексе в обратном порядке или индексация массивов, которые создают несколько записей на документ), но для целей понимания как данные выложены, этого достаточно.

Как выкладываются данные?

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

Что делают TokuDB и InnoDB?

Давайте посмотрим на TokuDB и InnoDB, которые очень похожи по разметке данных. Существует два случая: когда в таблице объявлен первичный ключ, и когда в таблице не объявлен первичный ключ.

Давайте возьмем первый случай. Предположим, мы определили таблицу следующим образом:

mysql> create table foo (a int, b int, c int, primary key (a), key (b)) engine=TokuDB;
mysql> insert into foo values (1,100,100),(2,2000,200),(3,30,300);

TokuDB и InnoDB оба кластеризуют первичный ключ . Таким образом, индекс B-дерева InnoDB или дерева фракталов TokuDB, в котором хранится первичный ключ, также хранит всю строку с ключом. Идентификатор строки, используемый вторичными индексами, является первичным ключом. Итак, в приведенном выше примере TokuDB и InnoDB будут иметь словарь для первичного ключа с данными, отсортированными следующим образом:

(1, 100, 100)
(2, 2000, 200)
(3, 30, 300)

и вторичный ключ для (b) с данными, отсортированными следующим образом:

(30, 3)
(100, 1)
(2000, 2)

Обратите внимание, что вторичный индекс хранит столбцы, объявленные в индексе, за которыми следует первичный ключ.

Если первичный ключ не существует, InnoDB попытается использовать уникальный индекс в качестве своего кластеризованного ключа и идентификатора строки в других вторичных индексах. Если такого уникального индекса не существует, InnoDB автоматически сгенерирует 6-байтовый первичный ключ в монотонно возрастающем порядке. Еще некоторые подробности здесь .

Если первичный ключ не существует в таблице TokuDB, TokuDB автоматически сгенерирует 8-байтовый первичный ключ в монотонно возрастающем порядке. TokuDB никогда не пытается использовать существующий уникальный индекс в качестве своего кластеризованного ключа и идентификатора строки.

Что делает MyISAM?

MyISAM отличается от TokuDB и InnoDB одним гигантским способом. MyISAM не кластеризует свой первичный ключ. Вместо этого MyISAM сохраняет свои строки в плоский файл с расширением .MYD. Индексы хранятся в B-деревьях в файле .MYI. Смещение в файле .MYD становится идентификатором логической строки. Первичный ключ, если он существует, может рассматриваться как еще один уникальный вторичный индекс для таблицы. Индексы (первичные и вторичные) используют смещение для идентификации всей строки.

Что делает ванильное хранилище Mongo?

Давайте вставим некоторые данные в коллекцию:

> db.foo.insert([{ a : 1 }, { a : 10, b : 20 }])
> db.foo.insert({ _id : 1 , a : 100 })
> db.foo.ensureIndex({ a : 1 })
> db.foo.find()
{ "_id" : 1, "a" : 100 }
{ "_id" : ObjectId("51ec4d43c54a36b1fe085265"), "a" : 1 }
{ "_id" : ObjectId("51ec4d43c54a36b1fe085266"), "a" : 10, "b" : 20 }

Первое, на что нужно обратить внимание, — это то, что каждый документ MongoDB ДОЛЖЕН иметь поле _id (есть угловые случаи, такие как ограниченные коллекции, где это не так, но давайте пока проигнорируем это). Если во вставленном документе нет поля _id, то поле _id создается автоматически, как описано здесь .

Теперь давайте посмотрим на определенные индексы:

> db.system.indexes.find()
...
{ "v" : 1, "key" : { "_id" : 1 }, "unique" : true, "ns" : "test.foo", "name" : "_id_" }
{ "v" : 1,  "key" : { "a" : 1 }, "ns" : "test.foo", "name" : "a_1" }
...

Обратите внимание, что в дополнение к определенному индексу в «a» MongoDB также автоматически генерирует уникальный индекс в поле _id. Следовательно, поле _id ДОЛЖНО быть уникальным.

Для хранения данных хранилище vanilla Mongo помещает все документы в плоские файлы. Смещение и файл становятся логическим идентификатором строки. Вторичные индексы хранятся с использованием B-деревьев. Вторичные индексы используют логический идентификатор строки, чтобы идентифицировать всю строку. Индекс «_id» можно рассматривать как автоматически определяемый уникальный вторичный индекс.

Как можно видеть, есть сходство с MyISAM. Индекс _id в ванильном хранилище Mongo можно рассматривать как первичный ключ в таблице MyISAM. Кроме того, обратите внимание на ссылку выше, что автоматическая генерация ключа гарантирует, что поле «_id» «обычно» увеличивается, так как его первые четыре байта являются меткой времени. Таким образом, существует сходство с автоматически сгенерированным скрытым первичным ключом InnoDB и TokuDB (или определяемым пользователем ключом автоинкремента) в том, что вновь сгенерированные поля «_id» располагаются в конце словаря, обеспечивая самый правый шаблон вставка .

Что делает TokuMX?

TokuMX, следуя протоколу MongoDB, заставляет документы иметь уникальное поле «_id» и автоматически генерирует _id, если пользователь не указал его. Индекс «_id» существует. Однако, как и TokuDB и InnoDB, TokuMX делает индекс «_id» кластеризованным индексом, который содержит весь документ. Поле _id используется в качестве идентификатора строки во вторичных индексах.

По сути, в TokuMX индекс «_id» можно рассматривать как первичный ключ таблицы TokuDB.

Резюме

Итак, на высоком уровне, здесь есть сходства и различия между MongoDB и MySQL.

MySQL заставляет строки соответствовать схеме, с некоторым значением для каждого столбца (включая NULL), тогда как документы MongoDB только заставляют документ иметь поле _id. Остальная часть документа может быть любой, какой пожелает пользователь. TokuMX, TokuDB и InnoDB кластеризуют индекс, который можно рассматривать как первичный ключ, и используют этот первичный ключ в качестве идентификатора строки для вторичных индексов. MyISAM и vanilla Mongo хранят данные в файле (или файлах) и используют смещение в файле в качестве идентификатора строки.