新增mysqlx插件讓資料庫能使用X Protocol
MySQL自第一個正式發行版(5.7.9)就加上了新的新的JSON資料型別和JSON涵式,往支援NoSQL的路上邁了一大步。但是如果只能透過SQL介面開發NoSQL應用,還是顯得有些削足適履,我們要的是能完全脫離SQL的影子,直接以NoSQL的方式和資料庫互動。所以MySQLng專案在原有的(預設使用3306端口)SQL介面之外(如下圖2.的部份)再開發了一個X Plugin,使應用端能透過X Plugin預設的33060端口使用X Dav API,直接對MySQL資料庫的JSON文件做增刪改查等操作。所以,現在的MySQL不僅是說SQL語言的關聯式資料庫,也是一個自前端到後端完整支援NoSQL的資料庫。事實上,在MySQL 5.6以後,它就能透過Memcached的介面以key-value查詢的方式和資料庫互動(MySQL memcache的預設端口為11211),當查詢方式不涉及複雜的JOIN和子查詢時,藉由這個介面繞過SQL層,少了處理SQL的解析和優化的工作負荷,能使性能大輻提升,這也是以一個NoSQL的方式和資料庫互動。現在又支援了JSON文件介面,使我們的NoSQL應用能有更高的可攜性。
MySQL X Plugin的安裝
MySQL自 5.7.12版以後以,可在軟體包的lib/plugin目錄中,找到一個名為mysqlx.so(Windows版是mysqlx.dll)的檔案,這就是X插件的程式庫。透過mysql客戶端程式,或MSQL Shell均可以安裝mysqlx插件:
- 以MySQL客戶端程式安裝MySQL X插件的命令如下(不一定要用root帳戶,以任何對mysql.plugin表有插入權的用戶都可以):
- 以MySQL Shell安裝MySQL X插件,在安裝好MySQL Shell的環境中:
- 您可以在mysql客戶端程式下以下命令,在回應的清單中找mysqlx插件是否在其中,以確認MySQL的X插件是否己裝好:
當X插件安裝好了,您就可以準備X protocol的客戶端,正式將MySQL當成一個全然的文件型資料庫。
MySQL Shell - 操作MySQL文件資料的好幫手
正如同mysql客戶端程式為SQL介面(默認3306端口)提供了方便操作的工具一樣,MySQL Shell也為各類使用者(DBA,開發者…)提供一個好用的工具,以NoSQL的方式對資料庫直接操作資料,管理資料庫或做臨時性的查詢。- 取得及安裝MySQL Shell
如下圖所示,在MySQL的下載網頁的左邊的導航列中可選到MySQL Shell的選項。有一點先請讀者們注意的是,目前(2016年底),MySQL Shell還是處於第三個beta(DMR)版,在它正式發行之前或許以後會有一些變動或者會加上更多功能。
進入下載網頁後可依MySQL Shell的執行環境選擇合適的版本
- 執行MySQL Shell
$ mysqlsh
Welcome to MySQL Shell 1.0.4 Development Preview
Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved。
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type '\help', '\h' or '\?' for help。
Currently in JavaScript mode. Use \sql to switch to SQL mode and execute queries.
mysql-js>
- 和mysql客戶端程式一樣,mysqlsh命令也可以加上參數。例如指定以什麼帳戶連入那個資料庫、對話的模式(X session, node session,或classic session)、語法(Javacript,Python,或SQL)、或內含批量作業命令的文件,如果要了解有那些選項,可以用--help或-h選項列示mysqlsh所選項和它們的說明:
$ mysqlsh –u -h -p
看起來是不是很熟悉?或是以下列命令指定URI同樣能登入資料庫:
$ mysqlsh –uri root :<pasword>@localhost :33060/<database>
- MySQL Shell有三種對話(Session)模式:
– X session,這是預設的對話模式,這個模式無法執行SQL命令,只能下JavaScript或Python的命令,MySQL的X Session以後可以發展成同時支援多個資料庫連線和資料庫分片
– Node session, 能執行SQL, JavaScript或Python的命令,但是Node Session只能和一個資料庫連線,以後也不會擴充成同時連多個資料庫。
– Classic session,不和使用x protocol,能用 SQL命令,和傳統的mysql介面類似,但開發者能用的介面有限,不支援NoSQL 的CRUD操作,也不能對collection操作。
- MySQL shell 可以有三種命令模式供您選擇:
– Python : 命令加上--py選項進入Python模式,提示符為mysql-py>,能使mysqlsh接受Python命令,MySQL Shell的指令如下:
$ mysqlsh –u -h -p --py
– SQL:要能在MySQL Shell下SQL命令對話模式必需在MySQL Shell指定classic 或node session,再於命令行加上--sql選項,可進入SQL模式,這個模式的介面和傳統的mysql客戶端的操作方式機乎完全相同,一樣是SQL命令,進入此模式的MySQL Shell命令為:
$ mysqlsh –u –p –h --classic --sql;
– 如同在mysql客戶端程式下鍵入help一樣,查mysql客戶端程式的命令,進入MySQL Shell後可以\?或\help查mysqlsh的操作命令,它所有的mysqlsh命令都以\開頭,例如\c是建立資料庫連線。在MySQL Shell操作JSON文件
MySQL Shell為JSON文件的操作提供所需要的命令集,這些命令都是實施了X Dav API的介面而開發出來的,MySQL Shell的X Dav API的詳情請參考http://dev.mysql.com/doc/dev/mysqlsh-api-javascript/在此以JavaScript命令模式為例,說明以mysqlsh 在JSON文件生命週期中的各種操作:
– 建立Schema和更換Schema:
MySQL Shell有幾個預設的物件,例如session, db…,您可在進入以後鍵入session查 看目前連線對話的內容,下例顯示目前的對話模式為X Session,以root帳戶連線位於127.0.0.1,使用33060端口:
mysql-js> session
<XSession:root@localhost:33060>
<XSession:root@localhost:33060>
mysql-js> session.createSchema('mydemo')
<Schema:mydemo>
<Schema:mydemo>
上列命令相當於在mysql命令程式下達CREATE DATABASE mydemo。
查查看我們之前建立的mydemo schema是否存在?目前資料庫有那些schema?
[
<Schema:information_schema>,
<Schema:acmug>,
<Schema:chtl>,
<Schema:ci17h1>,
<Schema:my_test>,
<Schema:mydemo>,
<Schema:mysql>,
<Schema:mysqlnews>,
<Schema:performance_schema>,
<Schema:sakila>,
<Schema:sys>,
<Schema:test_schema>,
<Schema:wordpress>,
<Schema:world>,
<Schema:world_x>
]
<Schema:information_schema>,
<Schema:acmug>,
<Schema:chtl>,
<Schema:ci17h1>,
<Schema:my_test>,
<Schema:mydemo>,
<Schema:mysql>,
<Schema:mysqlnews>,
<Schema:performance_schema>,
<Schema:sakila>,
<Schema:sys>,
<Schema:test_schema>,
<Schema:wordpress>,
<Schema:world>,
<Schema:world_x>
]
更換使用中的schema,則以mysqlsh命令\use(或者\u)就行了,例如:
mysql-js> \u mydemo
Schema `mydemo` accessible through db.
當然,進入mysqlsh時所指定的參數中也可以指定建立連線後要用那一個database(schema),例如:
$ mysqlsh --uri root@127.0.0.1:33060/mydemo
Creating an X Session to root@127.0.0.1:33060/mydemo
Enter password:********
Default schema `mydemo` accessible through db.
Welcome to MySQL Shell 1.0.4 Development Preview
Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type '\help', '\h' or '\?' for help.
Currently in JavaScript mode. Use \sql to switch to SQL mode and execute queries.
mysql-js>
上例中登入MySQL Shell的參數中,URI的最後一段指定用mydemo為連線的預設schema,進入mysqlsh後回應的內容中有一段"default schema `mydemo` accessible through db"說明了這一點,進入了mysqlsh後還可以鍵入db查證一下mysqlsh的默認物件db是否放在mydemo上:
mysql-js> db
<Schema:mydemo>
<Schema:mydemo>
– 在當前schema下操作Collection和Document:
在說明mysqlsh對collection的操作前,先簡單的說明Document資料庫的基本觀念:
一個collection是一堆JSON文件的集,相當於關聯式資料庫的表,JSON Document代表(或描述)一個事情或是一筆記錄,相當於關聯式資料庫在表中的一個row。
在說明mysqlsh對collection的操作前,先簡單的說明Document資料庫的基本觀念:
一個collection是一堆JSON文件的集,相當於關聯式資料庫的表,JSON Document代表(或描述)一個事情或是一筆記錄,相當於關聯式資料庫在表中的一個row。
- 建立Collection:
mysql-js> db.createCollection('collection1');
<Collection:collection1>
<Collection:collection1>
- 也可以在參數中加上schema名稱,這個功能使用戶可以在任何schema下為mydmo建collection:
mysql-js> db.createCollection('mydemo.mycollection');
只要有權限在指定的schema下加表的帳戶,都可以在位於任何資料庫時執行createCollection以參數
- 加上Document:
mysql-js> db.collection1.add({"name": {"lastname":"Tu","firstname":"Ivan"},"height": 180,"weight": 68,"talents": ["swimming","dancing","programming"]});
Query OK, 1 item affected (0.02 sec)
mysql-js> db.collection1.add({"name": {"lastname": "Wang","firstname": "James"},"height": 170,"weight": 80,"date of birth":"1990-12-30","talents": ["make money","dancing"]});
Query OK, 1 item affected (0.00 sec)
• 現在查現在己加了多少文件到collection1:
mysql-js> db.collection1.find();
[
{
"_id": "64b87d23a162d211645db86b2339c6c8",
"date of birth": "1990-12-30",
"height": 170,
"name": {
"firstname": "James",
"lastname": "Wang"
},
"talents": [
"make money",
"dancing"
],
"weight": 80
},
{
"_id": "b85d1476a062d211645db86b2339c6c8",
"height": 180,
"name": {
"firstname": "Ivan",
"lastname": "Tu"
},
"talents": [
"swimming",
"dancing",
"programming"
],
"weight": 68
}
]
1 documents in set (0.00 sec)
– 如果仔細看上列的輸出您可以發現幾個有趣的現象:
• 雖然前面加Document時,並未在文件中加上”_id”物件,所有的文件都自動加上一個名為”_id”的物件,存放自動產生的uuid值。
• 文件的結構能自由定義不像關聯式資料庫需事先定一個固定的文件結構,上例中第二個文件比第一個文件多了一個”data of birth”物件,”talents”陣列的數目也比第一個少了一個,這代表JSON文件不需事先定義資料結構,物件名稱(identifier)就能表達JSON文件內各物件的含義。
– 另一邊,由mysql客戶端程式查看看資料庫在引擎層面發生了什麼事?
mysql> show create table collection1\G
*************************** 1. row ***************************
Table: collection1
Create Table: CREATE TABLE `collection1` (
`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
1 row in set (0.00 sec)
顯示每一個collection都對應一個InnoDB的表,表有兩個欄位 – doc欄位的資料型別是json,另一個_id欄位是由json_extract函式生成的STORED(自函式算出值實際存放於表中,以後查詢時不需再算一次)欄位,它的值是在doc欄位中找名為_id的物件之值,並且將主鍵加在這個_id欄位上。
– 我們也能為collection的weight加上(非唯一)次要索引:
mysql-js> db.collection1.createIndex("weightIdx").field("weight","INTEGER",false).execute();
Query OK (0.06 sec)
– 另一方面,加上索引後在InnoDB表結構如下所示:
mysql> show create table collection1\G
*************************** 1. row ***************************
Table: collection1
Create Table: CREATE TABLE `collection1` (
`doc` json DEFAULT NULL,
`_id` varchar(32) GENERATED ALWAYS AS (json_unquote(json_extract(`doc`,'$._id'))) STORED NOT NULL,
`$ix_i_2D8FA3E83FC26B3A572C94D67A6F56CAC0CA1EAB` int(11) GENERATED ALWAYS AS (json_extract(`doc`,'$.weight')) VIRTUAL,
PRIMARY KEY (`_id`),
KEY `weightIdx` (`$ix_i_2D8FA3E83FC26B3A572C94D67A6F56CAC0CA1EAB`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec) 由此證明X Dav API會因為這個collection1.createIndex函式會在資料庫的collection1表上多加了一個名為$ix_i_
– 您也可以對collection進行條件查詢,甚至動能挷定值於條件中,下例為自Collection找出weight > 70的文件
mysql-js> db.collection1.find("weight > :kg").bind("kg",70);
[
{
"_id": "64b87d23a162d211645db86b2339c6c8",
"date of birth": "1990-12-30",
"height": 170,
"name": {
"firstname": "James",
"lastname": "Wang"
},
"talents": [
"make money",
"dancing"
],
"weight": 80
}
]
1 document in set (0.00 sec)
由於查詢條件裡有weight物件,執行計畫可能會用到前一段所建weightIdx的索引。
– 也可以指定要由查詢的結果中只傳回指定的物件,下例顯示不需要看整份JSON文件,只要傳回name和height物件
mysql-js> db.collection1.find("weight > 70").fields(["name","height"]);
[
{
"height": 170,
"name": {
"firstname": "James",
"lastname": "Wang"
}
}
– 最後,不需要這個collection時,可以刪除這個collection,drop collection得用session物件的dropCollection函式,它的必要參數有兩個 – schema名稱和collection名稱,刪除後該schema就查不到該collection了
mysql-js> session.dropCollection("mydemo","collection1");
Query OK (0.02 sec)
mysql-js> db.getCollections()
[
]
一如mysql客戶端程式可執行SQL脚本以進行批次作業,我們也可以編一套JavaScript或Python脚本在MySQL Shell 執行批次作業,範例如下:
建立一個名為Transaction-sample.js 的脚本, 這個脚本會執行1000次迴圈,每個迴圈插入5個JSON文件,每個迴圈都是一個交易,也就是一個易交提交5個JSON文件
建立一個名為Transaction-sample.js 的脚本, 這個脚本會執行1000次迴圈,每個迴圈插入5個JSON文件,每個迴圈都是一個交易,也就是一個易交提交5個JSON文件
//匯入mysqlx模組,組區域變數-mysqlx指向mysqlx物件,由mysqlx的getSession函式建立資料庫連線
var mysqlx = require('mysqlx').mysqlx;
// 建立資料庫連線
var session = mysqlx.getSession( {
host: 'localhost', port: 33060,
dbUser: 'root', dbPassword: '>password<' } );
// 移到my_test schema
var db = session.getSchema('my_test');
// 在my_test schema之下建立一個新的 collection – mycollection,
//如果該collection在此之前己存在則先drop它
session.dropCollection('my_test','mycollection');
db.createCollection('mycollection');
//取一個collection物件 – myColl,各JSON文件均以其add函式加入collection該中
var myColl = db.getCollection('mycollection');
var iter;
// 迴圈運行1000次,每次先以session.startTransaction()定事務的起始點
// 迴圈插入5個文件,如果運行中間有錯誤或被強制中斷,
//之前提交的交易會存在資料庫中,做到中間的交易會回滾
//所以collection中存在的文件數目會是5的倍數(實現易交易的資料完整性),
//由此證明X Dav API是支持交易和數據完整性的
// 最後以session.commit()結束並提交交易
try {
for (iter = 0; iter < 1000; iter++) {
session.startTransaction();
myColl.add({name: 'Sunny', age: 23, height: 1.3, weight: 73.3}).execute();
myColl.add({name: 'Albert', age: 24, height: 1.4, weight: 74.3}).execute();
myColl.add({name: 'Bred', age: 25, height: 1.5, weight: 75.3}).execute();
myColl.add({name: 'Connie', age: 26, height: 1.6, weight: 76.3}).execute();
myColl.add({name: 'David', age: 27, height: 1.7, weight: 77.3}).execute();
// Commit the transaction if everything went well
session.commit();
print('Data inserted successfully.\n');
}
}
catch (err) {
// 如果有錯誤,則回滾未完成的交易
session.rollback();
// Printing the error message
print('Data could not be inserted: ' + err.message);
}
將上列脚本送到MySQL Shell執行,結果如下:
$ mysqlsh -f transaction-sample.js
Data inserted successfully.
Data inserted successfully.
…
Data inserted successfully.
登入查看執行的結果:
mysql-js> db.mycollection.find()
[
{
"_id": "ffe5ac80c362d2114072b86b2339c6c8",
"age": 24,
"height": 1.4,
"name": "Albert",
"weight": 74.3
},
{
"_id": "fffaab7ec362d2114072b86b2339c6c8",
"age": 27,
"height": 1.7,
"name": "David",
"weight": 77.3
}
…
]
5000 documents in set (0.00 sec)
上列範例中建資料庫連線不是用Connection物件,而是Session物件,在Connection之上引進Session的概念,這也透露著X Dav API將朝向支援一個Session可能包含多個Connection,每個Connection分別連到不同的資料庫,而能支援資料庫分片(data shard)的方向發展
Connector實施X Dav API支援多種應用程式以JSON和MySQL互動
文行到此,整份MySQL as a Document Store的技術拼圖只剩最後一片- 應用層能透過X Protocol,執行CRUD的函式,直接操作位於資料庫內的JSON文件。如下圖4.所示,MySQL在各種新版本的應用程式連結器(包括Connector/J, Connectior/NET, Connectior/C++等)加上了新的支援X protocol的模組,以,讓應用程式能直接調用這些模組中實現X Dav API的介面的函式,透過X protocol直接使用和更新資料庫的JSON文件。這些連結器還在持續完善中,目前支援X Protocol的連結器有Connector/J,Connector/Python,Connector/NET,Connector/C++ 和Connector/Nodejs.本文的範例以一個Java應用程式 - MySQLStore.java(該Java程式會插入三個JSON文件到mycollection中,並找出name為”Sakila”,age小於20的犬文件,並列示出來)為您演示如何使用Connector/J的X Dav API接口。以下列範例說明這個Java程式的開發和執行環境的準備步驟,供您參考。
- 準備JSON文件資料庫的Java開發環境
- 解壓縮該檔案
- 找到mysql-connector-java-6.0.x
- 並將這個Java .jar文件的位置加到操作系統環境變量 – CLASSPATH,或在Java IDE中加上一個程式庫給這個.jar上檔
- MySQL 文件資料應用的Java程式
package mysqldocstore;
import com.mysql.cj.api.x.*;
import com.mysql.cj.x.MysqlxSessionFactory;
import com.mysql.cj.x.json.*;
//以上import的三個package/class都是MySQL Connetor/J 6.0.x 所新增的
public class MySQLdocStore {
public static void main(String[] args) {
String url = "mysqlx://localhost:33060/my_test?user=itu&password=";
//通過mysqlx建立資料庫對話(Session)物件,資料庫主機位於localhost
//資料庫聽33060端口,用戶名為itu,schema為my_test(可不在URI指定Schema)
XSession mySession = new MysqlxSessionFactory().getSession(url);
//開啟一個交易
mySession.startTransaction();
//將位置移到your_test schema
Schema myDb = mySession.getSchema("your_test");
//找一個名為mycollection的 Collection,並以myColl變易數指向該物件
Collection myColl = myDb.getCollection("mycollection");
//插入三個JSON文件
myColl.add("{\"name\":\"Sakila\", \"age\":15}").execute();
myColl.add("{\"name\":\"Susanne\", \"age\":24}").execute();
myColl.add("{\"name\":\"Mike\", \"age\":39}").execute();
//提交交易
mySession.commit();
//在mycolleciton中找文件,條件是name含’Sakila’字串,且age小於20
DocResult docs = myColl.find(name like :name AND age < :age").bind("name", "Sakila").bind("age", 20).execute();
//取出查出的文件以doc變數指向該物件,如果找不到符合條件的文件,則返回NULL
DbDoc doc = docs.fetchOne();
//列示查到的JSON文件
System.out.println("Age below 20 is "+doc);
}
}
- 執行結果,由於我將MySQLdocStore.class和msql-connector-java-6.0.x
-bin.jar包在jar包中以方便佈署,所以執行時要指定MySQLdocStore.jar。執行後,在資料庫會插入三個JSON文件,最後列示一個JSON文件,其age物件的值小於20。
$ java –jar MySQLdocStore.jar
Age below 20 is {
"_id" : "bb8c3bf0242a44a0a49767e3dcee7cc9",
"age" : 15,
"name" : "Sakila"
}
- 在MySQL Shell查看mycollection Collection,可看到在mycollection中有三個JSON文件,都是在MySQLdocStore.class程式插入的。
mysql-js> db.mycollection.find();
[
{
"_id": "2f5a69049e0744f4b1f3abd9bbbbd2dd",
"age": 39,
"name": "Mike"
},
{
"_id": "bb8c3bf0242a44a0a49767e3dcee7cc9",
"age": 15,
"name": "Sakila"
},
{
"_id": "d9d3e1c86a3643be93e65ac1981c659f",
"age": 24,
"name": "Susanne"
}
]
3 documents in set (0.00 sec)
- 由SQL介面查mycollection表,也可在mycollection表中看到含有個JSON文件的行。
mysql> select * from mycollection\G
*************************** 1. row ***************************
doc: {"_id": "2f5a69049e0744f4b1f3abd9bbbbd2dd", "age": 39, "name": "Mike"}
_id: 2f5a69049e0744f4b1f3abd9bbbbd2dd
*************************** 2. row ***************************
doc: {"_id": "bb8c3bf0242a44a0a49767e3dcee7cc9", "age": 15, "name": "Sakila"}
_id: bb8c3bf0242a44a0a49767e3dcee7cc9
*************************** 3. row ***************************
doc: {"_id": "d9d3e1c86a3643be93e65ac1981c659f", "age": 24, "name": "Susanne"}
_id: d9d3e1c86a3643be93e65ac1981c659f
3 rows in set (0.00 sec)
結論
我認為MySQL成為JSON文件資料庫是MySQL近年來最重要的創新之一,這套技術促成了MySQL將NoSQL和關聯資料庫的優勢融於一爐,讓廣大的MySQL用戶不需要引另外一套資料庫就能在擁抱新的NoSQL技術,因而也不需磨合陌生的新技術。用戶可在他們己經熟悉的MySQL上以Schema less資料結搆支援Dev/Ops的開發方法,以加速應用面市的速度,滿足應用業務上占得市場先機同時,又能享有關聯式資料庫己經很成熟的資料完整性、支援交易、MVCC(多版本并行控制)和崩潰回復的優點。另一方面DBA們也能使用和原來MySQL同樣的工具為資料庫做備份、調優、監控和安控管制。MySQL這項與時俱進的發展使得MySQL所有的從業人員都能輕鬆因應IT大環境的改變。讓MySQL應用開發者、DBA和應用業務擁有者都能同蒙其利。事實上,MySQL除了上述由資料庫核心到MySQL Shell工具和各種應用的Connector上推出支援JSON的功外,我們還需要在應用開發環境上提供更豐富的Framework,以進一步使MySQL的開發者能快速而方便的開發應用。由於許多Connector的技術(例如Java, Node.js等)己經有很多現成的Framework支援JSON,使得我們在這方面的發展有了扎實的基礎。例如使用Node.js的開發者或許知道(或用過)MEAN Framework (MongoDB, Express, Angular.js, Node.js),它能使開發者以更有效率、更容易的開發網頁應用。經過Connector/Node.js開發人員的努力, 現在MySQL也支援MEAN Framework,只是這是組合是MySQL, Ecpress, Angular.js, Node.js了(相關訊息和實作範例可參考http://insidemysql.com/develop-by-example-document-store-working-with-express-js-angularjs-and-node-js/),相信隨著MySQL as a document store的功能持續的完善,將有更多的Framework和IDE(整合開發環境)會支援這個技術堆疊,或許我們會LAMP風潮之后為IT界來另一波高潮。