Как работает 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. Перепечатка в интернет-изданиях разрешается только с указанием автора и прямой ссылки на оригинальную статью. Перепечатка в бумажных изданиях допускается только с разрешения редакции.
|