SQLinfo.ru - Все о MySQL

Как работает X Plugin — внутреннее устройство

Дата: 30.12.2016

Данная статья является переводом статьи Мигеля Анхель Ньето (Miguel Angel Nieto). Курсивом даны комментарии переводчика.

В этой статье мы увидим, что происходит внутри MySQL при использовании X Plugin для того, чтобы превратить NoSQL запросы в SQL (и затем хранить данные в транзакционном хранилище InnoDB).

X Plugin позволяет MySQL функционировать как документоориентированное хранилище. Нам не нужно определять какие-либо базы и использовать язык SQL, но при этом по-прежнему гарантируются все свойства ACID. Звучит как магия - но мы знаем, что из-за магии только самолеты летают!

Александр уже писал в блоге обзор того, как работает X Plugin, с некоторыми примерами. В этой статье я приведу больше примеров и покажу как они преобразуются внутри сервера MySQL.

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

Создание первой коллекции

Запустим MySQL shell и создадим нашу первую коллекцию:

$ mysqlsh -u root --py
Creating an X Session to root@localhost:33060
No default schema selected.
[...]
Currently in Python mode. Use sql to switch to SQL mode and execute queries.
mysql-py> db.createCollection("people")

Что такое коллекция в терминах SQL? Таблица. Давайте проверим, что делал MySQL, посмотрев в журнал медленных запросов:

CREATE TABLE `people` (
  `doc` json DEFAULT NULL,
  `_id` varchar(32) GENERATED ALWAYS AS (json_unquote(json_extract(`doc`,'$._id'))) STORED NOT NULL,
  PRIMARY KEY (`_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

Как мы правильно догадались, была создана таблица с двумя колонками. Одна называется “doc” и хранит JSON документ. Вторая колонка называется “_id” и создается как виртуальная колонка из данных, извлеченных из JSON документа. _id используется в качестве первичного ключа, и если мы не определяем это значение, то MySQL будет выбирать случайный UUID каждый раз, когда мы создаем документ.

Итак, основы понятны:

  • Все данные хранятся внутри столбца типа JSON.
  • Индексы создаются на виртуальных столбцах, которые получаются путем извлечения данных из формата JSON. Каждый раз, когда мы добавляем новый индекс, будет сгенерирован новый столбец. Обратите внимание: в режиме SQL мы можем создать дополнительные индексы и при поиске нам не нужно будет ссылаться на новые поля - оптимизатор MySQL достаточно умен, чтобы самостоятельно при использовании JSON_EXTRACT() проводить сканирование по виртуальным колонкам.

Давайте выполним getCollections, что аналогично “SHOW TABLES” в терминах SQL:

mysql-py> db.getCollections()
[
    <Collection:people>
]

То, что на самом деле при этом выполняется в MySQL:

SELECT C.table_name AS name, IF(ANY_VALUE(T.table_type)='VIEW', 'VIEW', IF(COUNT(*) = COUNT(CASE WHEN (column_name = 'doc' AND data_type = 'json') THEN 1 ELSE NULL END) + COUNT(CASE WHEN (column_name = '_id' AND generation_expression = 'json_unquote(json_extract(`doc`,''$._id''))') THEN 1 ELSE NULL END) + COUNT(CASE WHEN (column_name != '_id' AND generation_expression RLIKE '^(json_unquote[[.(.]])?json_extract[[.(.]]`doc`,''[[.$.]]([[...]][^[:space:][...]]+)+''[[.).]]{1,2}$') THEN 1 ELSE NULL END), 'COLLECTION', 'TABLE')) AS type FROM information_schema.columns AS C LEFT JOIN information_schema.tables AS T USING (table_name) WHERE C.table_schema = 'test' GROUP BY C.table_name ORDER BY C.table_name;

На этот раз запрос более сложный. Таблица information_schema.tables соединяется (join) с information_schema.columns для поиска тех таблиц, которые содержат столбцы “doc” и “_id”.

Добавление и чтение документов

Я собираюсь начать добавлять данные в нашу коллекцию. Давайте добавим наш первый документ:

mysql-py> db.people.add(
      ...  {
      ...     "Name": "Miguel Angel",
      ...     "Country": "Spain",
      ...     "Age": 33
      ...   }
      ... )

При этом MySQL добавляет JSON объект и автоматически присваивает значение первичного ключа:

INSERT INTO `test`.`people` (doc) VALUES (JSON_OBJECT('Age',33,'Country','Spain','Name','Miguel Angel','_id','a45c69cd2074e611f11f62bf9ac407d7'));

Хорошо, у нас допускается хранение слабо структурированных данных. Давайте добавим кого-нибудь ещё, используя отличающиеся поля:

mysql-py> db.people.add(
      ...  {
      ...     "Name": "Thrall",
      ...     "Race": "Orc",
      ...     "Faction": "Horde"
      ...   }
      ... )

Так же, как и раньше, MySQL просто пишет другой JSON объект (с соответствующими ему полями):

INSERT INTO `test`.`people` (doc) VALUES (JSON_OBJECT('Faction','Horde','Name','Thrall','Race','Orc','_id','7092776c2174e611f11f62bf9ac407d7'));

Теперь мы будем читать данные, которые мы только что вставили. Во-первых, мы хотим получить все документы, хранящиеся в коллекции:

mysql-py> db.people.find()

MySQL преобразует это в:

SELECT doc FROM `test`.`people`;

И теперь как преобразуются условия фильтрации:

mysql-py> db.people.find("Name = 'Thrall'")

Используется оператор SELECT с условием WHERE на данные, извлеченные из JSON объекта:

SELECT doc FROM `test`.`people` WHERE (JSON_EXTRACT(doc,'$.Name') = 'Thrall');

Обратите внимание: атрибуты JSON объекта регистро-зависимые.

Обновление документов

Тралл решил, что он не хочет больше состоять в Орде. Он хочет присоединиться к Альянсу. Нам нужно обновить документ:

mysql-py> db.people.modify("Name = 'Thrall'").set("Faction", "Alliance")

MySQL выполняет оператор UPDATE снова используя условие WHERE на данные, извлеченные из JSON объекта. Затем обновляет “Faction”:

UPDATE `test`.`people` SET doc=JSON_SET(doc,'$.Faction','Alliance') WHERE (JSON_EXTRACT(doc,'$.Name') = 'Thrall');

Теперь я хочу удалить документ о себе:

mysql-py> db.people.remove("Name = 'Miguel Angel'");

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

DELETE FROM `test`.`people` WHERE (JSON_EXTRACT(doc,'$.Name') = 'Miguel Angel');

Заключение

Волшебство, которое позволяет нам работать с MySQL как с документоориентированной NoSQL базой данных это:

  • Создание обычной InnoDB таблицы с колонкой типа JSON. Можно также использовать другие механизмы хранения.
  • Автоматическая генерация первичного ключа, используя UUID, и представление его в виде виртуальной колонки.
  • Все поиски осуществляются путем извлечения данных с помощью JSON_EXTRACT в части WHERE соответствующего оператора.

Я полагаю это решение действительно умным и простым. Поздравляю Oracle!

Дата публикации: 30.12.2016

© Все права на данную статью принадлежат порталу SQLInfo.ru. Перепечатка в интернет-изданиях разрешается только с указанием автора и прямой ссылки на оригинальную статью. Перепечатка в бумажных изданиях допускается только с разрешения редакции.

Статьи :
 Установка и настройка MySQL
 Коды ошибок в MySQL
 Программирование в MySQL
 Оптимизация производительности
 Кодировка символов в MySQL
>Хранение данных в MySQL
 MySQL Cluster
См. также:
 Оптимизация производительности MySQL
 Услуги по оптимизации MySQL