10月19日にMySQL5.7がGAになったので、新規追加されたJSONデータ型の確認を行いました。
JSONはXMLと同じように、スマートフォンアプリ用のAPIやB2BやB2C連携でJSON APIが多く使われているようです。
基本的な動作検証
1. 基本的な構文バリデーション機能
2. JSONデータ型とTEXT型のパフォーマンス差
3. JSONとGenerated Columnの連携によるインデックス利用とパフォーマンス
4. JSONドキュメントの部分アップデート
ちなみに、ザックリとGoogleで検索してみると以下のように多くのサイトがJSON用のAPIを提供していました。
Twitter
https://dev.twitter.com/rest/reference/get/statuses/user_timeline
原子力規制委員会
http://radioactivity.nsr.go.jp/data/ja/real/area_24000/2401_trend.json
Google API
https://storage.googleapis.com/maps-devrel/google.json
ぐるナビ
http://api.gnavi.co.jp/api/tools/?apitype=ver1_RestSearchAPI
シカゴ市
https://data.cityofchicago.org/resource/alternative-fuel-locations.json?$limit=100&$offset=50
1. 基本的な構文バリデーション機能
検証テーブル (TEXT型)
CREATE TABLE `employees_txt` ( `id` int(11) NOT NULL AUTO_INCREMENT, `data` text NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4
検証テーブル (JSON型)
CREATE TABLE `employees_json` ( `id` int(11) NOT NULL AUTO_INCREMENT, `data` json NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4
それぞれのテーブルに、構文ミスしたJSONデータを入れてみると以下のようになる。
JSONデータ型はINSERT時のバリデーションが正常に効いている。
root@localhost [NEW57]> INSERT INTO NEW57.employees_txt(data) VALUES ('{"id": 1, "name": "Jane"}'); Query OK, 1 row affected (0.00 sec) root@localhost [NEW57]> INSERT INTO NEW57.employees_txt(data) VALUES ('{"id": 2, "name": "Joe"'); Query OK, 1 row affected (0.01 sec) root@localhost [NEW57]> SELECT * FROM NEW57.employees_txt; +----+---------------------------+ | id | data | +----+---------------------------+ | 1 | {"id": 1, "name": "Jane"} | | 2 | {"id": 2, "name": "Joe" | +----+---------------------------+ 2 rows in set (0.00 sec) root@localhost [NEW57]> INSERT INTO NEW57.employees_json(data) VALUES ('{"id": 1, "name": "Jane"}'); Query OK, 1 row affected (0.01 sec) root@localhost [NEW57]> INSERT INTO NEW57.employees_json(data) VALUES ('{"id": 2, "name": "Joe"'); ERROR 3140 (22032): Invalid JSON text: "Missing a comma or '}' after an object member." at position 23 in value (or column) '{"id": 2, "name": "Joe"'. root@localhost [NEW57]> SELECT * FROM NEW57.employees_json; +----+---------------------------+ | id | data | +----+---------------------------+ | 1 | {"id": 1, "name": "Jane"} | +----+---------------------------+ 1 row in set (0.00 sec) root@localhost [NEW57]>
2. JSONデータ型とTEXT型のパフォーマンス差
セミナーでも実施しましたが、以下のようなシンプルなテーブルに対して、
ダウンロードしてきた、20万6千件のJSONデータをインサートして参照レスポンス比較。
それぞれの参照クエリにて同じコマンドと同じデータでレスポンス比較
SELECT distinct json_extract(feature,’$.type’) as feature FROM NEW57.features_txt;
SELECT distinct json_extract(feature,’$.type’) as feature FROM NEW57.features_json;
結果としては、JSON型の方がTEXT型に比べて圧倒的に早い事が確認出来る。
[root@misc01 SOD2015]# ./json_and_text_without_index.sh INDEXの無いテーブルに対するSELECTを、TEXT型とJSONデータ型で比較します。 SELECT distinct json_extract(feature,'$.type') as feature FROM 各テーブル 【TEXT型】 mysql: [Warning] Using a password on the command line interface can be insecure. +-----------+ | feature | +-----------+ | "Feature" | +-----------+ real 0m9.724s user 0m0.005s sys 0m0.002s 【JSON型】 mysql: [Warning] Using a password on the command line interface can be insecure. +-----------+ | feature | +-----------+ | "Feature" | +-----------+ real 0m1.506s user 0m0.004s sys 0m0.004s [root@misc01 SOD2015]#
3. JSONとGenerated Columnの連携によるインデックス利用とパフォーマンス
JSONドキュメントのオブジェクトの中からSTREET名の部分からデータを抽出して列を作成しIndexを付与。
列: json_extract(feature,’$.properties.STREET’))
インデックス: KEY `feature_street` (`feature_street`)
JSONデータ型のサンプルを入れたテーブル mysql: [Warning] Using a password on the command line interface can be insecure. *************************** 1. row *************************** id: 12250 feature: {"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[-122.39836263491878, 37.79189388899312, 0], [-122.39845248797837, 37.79233030084018, 0], [-122.39768507706792, 37.7924280850133, 0], [-122.39836263491878, 37.79189388899312, 0]]]}, "properties": {"TO_ST": "388", "BLKLOT": "0265003", "STREET": "MARKET", "FROM_ST": "388", "LOT_NUM": "003", "ST_TYPE": "ST", "ODD_EVEN": "E", "BLOCK_NUM": "0265", "MAPBLKLOT": "0265003"}} feature_type: "Feature" feature_street: "MARKET" Press [Enter] key to resume. JSONデータ型とGenerated Columnを利用したテーブル mysql: [Warning] Using a password on the command line interface can be insecure. *************************** 1. row *************************** Table: features Create Table: CREATE TABLE `features` ( `id` int(11) NOT NULL AUTO_INCREMENT, `feature` json NOT NULL, `feature_type` varchar(30) GENERATED ALWAYS AS (json_extract(feature,'$.type')) VIRTUAL, `feature_street` varchar(30) GENERATED ALWAYS AS (json_extract(feature,'$.properties.STREET')) VIRTUAL, PRIMARY KEY (`id`), KEY `feature_type` (`feature_type`), KEY `feature_street` (`feature_street`) ) ENGINE=InnoDB AUTO_INCREMENT=206561 DEFAULT CHARSET=utf8mb4 Press [Enter] key to resume. JSONドキュメントに対して、INDEX検索が利用出来るか確認 -> where json_extract(feature,'$.properties.STREET') = '"MARKET"' mysql: [Warning] Using a password on the command line interface can be insecure. *************************** 1. row *************************** id: 1 select_type: SIMPLE table: features partitions: NULL type: ref possible_keys: feature_street key: feature_street key_len: 123 ref: const rows: 808 filtered: 100.00 Extra: NULL [root@misc01 SOD2015]#
4. JSONドキュメントの部分アップデート
JSONドキュメントの一部をUPDATEする場合の処理方法確認。
TEXT型だと、全ての列データを入れ替える必要がありますが、JSONだと一部のみ変更可能です。
select id,body,json_extract(body,"$.price") as extract from T_JSON_DOC where id = 3; +----+------------------------------------------------------------------------+---------+ | id | body | extract | +----+------------------------------------------------------------------------+---------+ | 3 | {"id": 3, "name": "冷蔵庫", "price": 50000, "Conditions": ["NEW", 2015]}| 50000 | +----+------------------------------------------------------------------------+---------+ update T_JSON_DOC set T_JSON_DOC.body = JSON_REPLACE(body,"$.price",15000) where id = 3; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 select id,body,json_extract(body,"$.price") as extract from T_JSON_DOC where id = 3; +----+----------------------------------------------------------------------- +---------+ | id | body | extract | +----+------------------------------------------------------------------------+---------+ | 3 | {"id": 3, "name": "冷蔵庫", "price": 15000, "Conditions": ["NEW", 2015]}| 15000 | +----+------------------------------------------------------------------------+---------+
文字入れ替えに便利なJSONファンクション(例)
JSON_SET() 既存の値を置き換え、存在しない値を追加
JSON_INSERT() 既存の値を置き換えずに値を挿入
JSON_REPLACE() 既存の値のみを置き換えます
参照:
11.6 The JSON Data Type
https://dev.mysql.com/doc/refman/5.7/en/json.html
MySQL5.7セミナー資料
https://www-jp.mysql.com/news-and-events/seminar/downloads.html
PlanetMySQL Voting: Vote UP / Vote DOWN