MySQL道普請便り

第266回MySQL 9.4に追加されたJSON Duality Viewってなに

第260回 MySQL 9.4と9.5の新機能についてにもある通り、MySQL 9.4にて「JSON Duality View」という機能が追加されています。ドキュメントを確認すると、この機能はリレーショナルテーブルをJSONドキュメントとして扱えるビューだと説明されています。Oracle Databaseにも同様の機能が23aiで追加されているため、知っている方もいるかもしれません。

ただし、MySQLでは一部機能がEnterprise Edition限定で提供されるとも記載されています。ではCommunity Editionではどこまで使えるのでしょうか。そこで今回は、MySQL 9.6.0 Community Editionを実際に構築し、

  • 作成できるのか
  • 参照できるのか
  • 更新できるのか
  • 実行計画はどうなるのか
  • _metadata.etagはどのように振る舞うのか

を一通り検証してみました。

最初に、検証の事前準備として以下のテーブルを作成して、データを入れておきます。

CREATE TABLE authors (
  author_id INT PRIMARY KEY,
  display_name VARCHAR(100) NOT NULL
);

CREATE TABLE posts (
  post_id INT PRIMARY KEY,
  author_id INT NOT NULL,
  title VARCHAR(255) NOT NULL,
  published_at DATETIME NULL,
  CONSTRAINT fk_posts_authors
  FOREIGN KEY (author_id) REFERENCES authors(author_id)
);

CREATE TABLE comments (
  comment_id INT PRIMARY KEY,
  post_id INT NOT NULL,
  commenter VARCHAR(100) NOT NULL,
  body VARCHAR(500) NOT NULL,
  created_at DATETIME NOT NULL,
  CONSTRAINT fk_comments_posts
  FOREIGN KEY (post_id) REFERENCES posts(post_id)
);

INSERT INTO authors VALUES
(1, 'Fukamachi'), (2, 'Kimura'), (3, 'Kitagawa');

INSERT INTO posts VALUES
(10, 1, 'MySQLアップグレードで顕在化したINTと空文字のバグ挙動', '2026-02-25 20:00:00'),
(11, 1, 'MySQL9.4に追加されたJSON Duality Viewってなに?', '2026-02-26 21:00:00'),
(20, 2, '知ってるとちょっと便利なON UPDATE CURRENT_TIMESTAMP', '2026-02-27 19:00:00'),
(30, 3, 'MySQL 9.4と9.5の新機能について', '2026-02-27 19:00:00');

INSERT INTO comments VALUES
(100, 11, 'kim', 'Communityでどこまで動くかが知りたい', '2026-02-26 21:10:00'),
(101, 11, 'fuk',  'こんな機能あったんだ。',  '2026-02-26 21:12:00'),
(200, 10, 'kim',  'こんなbugあったんだ',  '2026-02-25 20:10:00'),
(300, 20, 'kita', 'まとめ助かる',  '2026-02-27 19:10:00');

JSON Duality Viewとは何者か

JSON Duality Viewは、複数のリレーショナルテーブルを1つのJSONドキュメントとして定義するビューです。定義にはJSON_DUALITY_OBJECT()という専用の構文を使います。

ここでまず気付くのは、この関数が通常のJSON関数とは性質が異なる点です。JSON_OBJECT()などとは違い、通常のSELECTでそのまま利用することはできません。下記のようにSQLを実行するとエラーになります。

mysql> SELECT JSON_DUALITY_OBJECT('_id': 1);

ERROR 6484 (HY000): JSON_DUALITY_OBJECT() can only be used to define a JSON duality view.

JSON_DUALITY_OBJECTは、JSONを生成するための関数というよりも、JSON Duality Viewの構造を宣言するための専用構文に近い存在のようです。

たとえば、以下のようにJSON Duality Viewの定義内で利用します。

著者をもとに「投稿一覧」を配列で持つ(authors -> posts)
mysql> CREATE OR REPLACE JSON RELATIONAL DUALITY VIEW author_posts_dv AS
    -> SELECT JSON_DUALITY_OBJECT(
    ->   '_id': author_id,
    ->   'display_name': display_name,
    ->   'posts': (
    ->     SELECT JSON_ARRAYAGG(
    ->       JSON_DUALITY_OBJECT(
    ->         'post_id': post_id,
    ->         'title': title,
    ->         'published_at': published_at
    ->       )
    ->     )
    ->     FROM posts
    ->     WHERE posts.author_id = authors.author_id
    ->   )
    -> )
    -> FROM authors;
Query OK, 0 rows affected (0.006 sec)
投稿をもとに著者+コメントを持つ(posts -> author + comments)
mysql> CREATE OR REPLACE JSON RELATIONAL DUALITY VIEW post_detail_dv AS
    -> SELECT JSON_DUALITY_OBJECT(WITH(INSERT,UPDATE,DELETE)
    ->   '_id': post_id,
    ->   'title': title,
    ->   'published_at': published_at,
    ->
    ->   'author': (
    ->     SELECT JSON_DUALITY_OBJECT(
    ->       'author_id': author_id,
    ->       'display_name': display_name
    ->     )
    ->     FROM authors
    ->     WHERE authors.author_id = posts.author_id
    ->   ),
    ->   'comments': (
    ->     SELECT JSON_ARRAYAGG(
    ->       JSON_DUALITY_OBJECT(
    ->         'comment_id': comment_id,
    ->         'commenter': commenter,
    ->         'body': body,
    ->         'created_at': created_at
    ->       )
    ->     )
    ->     FROM comments
    ->     WHERE comments.post_id = posts.post_id
    ->   )
    -> )
    -> FROM posts;
Query OK, 0 rows affected (0.008 sec)

Community Editionでも作成⁠参照は可能

まず結論から言うと、前述のクエリが実行できるように、Community EditionでもJSON Duality Viewの作成は問題なく実行できます。

先ほど作成したJSON Duality Viewがどのような出力を返すのか、確認してみましょう(※出力を見やすくするためにjqコマンドに渡して出力しています⁠⁠。

# mysql -uroot -p**** -sse "SELECT * FROM post_detail_dv LIMIT 1" dv_test | jq
{
  "_id": 10,
  "title": "MySQLアップグレードで顕在化したINTと空文字のバグ挙動",
  "author": {
    "author_id": 1,
    "display_name": "Fukamachi"
  },
  "comments": [
    {
      "body": "こんなbugあったんだ",
      "commenter": "kim",
      "comment_id": 200,
      "created_at": "2026-02-25 20:10:00.000000"
    }
  ],
  "_metadata": {
    "etag": "b739e056dbe74a9c622c6ae49c624c77"
  },
  "published_at": "2026-02-25 20:00:00.000000"
}

ここで目を引くのが_metadata.etagの存在です。

通常のVIEW + JSON_OBJECT()ではこのようなメタ情報は付きません。JSON Duality Viewは単にJSONを返すだけでなく、JSON Duality Viewの結果として自動的に付与されているように見えます。

少なくとも「テーブル定義を作れる」⁠データを読める」という点では、Community Editionでも利用できることが確認できました。

DML は Enterprise Edition限定

では、JSON経由での更新はどうでしょうか。以下のようにJSON Duality Viewに対してUPDATEを実行してみます。

mysql> UPDATE post_detail_dv
    -> SET data = JSON_SET(
    ->   data,
    ->   '$.title',
    ->   '(修正版)MySQL9.4に追加されたJSON Duality Viewってなに?'
    -> )
    -> WHERE data->'$._id' = 11;
ERROR 6503 (HY000): Feature not available in this edition.

「このエディションでは利用できない」というエラーが返されました。INSERTやDELETEも同様にERROR 6503となります。

興味深い点は、Community EditionでJSON Duality Viewの定義時にテーブルアノテーションとしてWITH(INSERT,UPDATE,DELETE)を指定しても、作成自体は成功する点です。

テーブルアノテーションWITH (INSERT,UPDATE,DELETE)が確認できる
mysql> show create view post_detail_dv\G
*************************** 1. row ***************************
                View: post_detail_dv
         Create View: CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER JSON RELATIONAL DUALITY VIEW `post_detail_dv` AS select json_duality_object( WITH (INSERT,UPDATE,DELETE) '_id':`posts`.`post_id`,'title':`posts`.`title`,'published_at':`posts`.`published_at`,'author':(select json_duality_object('author_id':`authors`.`author_id`,'display_name':`authors`.`display_name`) from `authors` where (`authors`.`author_id` = `posts`.`author_id`)),'comments':(select json_arrayagg(json_duality_object('comment_id':`comments`.`comment_id`,'commenter':`comments`.`commenter`,'body':`comments`.`body`,'created_at':`comments`.`created_at`)) from `comments` where (`comments`.`post_id` = `posts`.`post_id`))) AS `Name_exp_1` from `posts`
character_set_client: utf8mb4
collation_connection: utf8mb4_0900_ai_ci
1 row in set (0.001 sec)

つまり今回の検証では、

  • 構文はエディションに関係なく定義できる
  • ただしJSON経由のDML実行時にエディション差で制御される

という挙動になっているように見えました。少なくともCommunity Editionでは、JSON経由DMLは明確にEnterprise Editionの機能として切り分けられているようです。

_metadata.etagはどう変わるのか

JSON Duality Viewの特徴のひとつが_metadata.etagです。etagの変化の粒度も確認してみましょう。

親テーブル更新

まずは親テーブルの値の更新です。

下記のように authors.display_nameを変更するとetagの値が変更されています。つまり、親テーブルの値の変更によりetagの値が変更されていることが確認できます。

mysql> SELECT
    ->   data->>'$._id'            AS _id,
    ->   data->>'$.display_name'   AS display_name,
    ->   data->>'$._metadata.etag' AS etag
    -> FROM author_posts_dv
    -> WHERE data->'$._id' = 1;
+------+--------------+----------------------------------+
| _id  | display_name | etag                             |
+------+--------------+----------------------------------+
| 1    | Fukamachi    | 51243a50cc232f77f498a4a63f2eb618 |
+------+--------------+----------------------------------+
1 row in set (0.001 sec)

mysql> UPDATE authors
    -> SET display_name = CONCAT(display_name, '_v2')
    -> WHERE author_id = 1;
Query OK, 1 row affected (0.004 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT
    ->   data->>'$._id'            AS _id,
    ->   data->>'$.display_name'   AS display_name,
    ->   data->>'$._metadata.etag' AS etag
    -> FROM author_posts_dv
    -> WHERE data->'$._id' = 1;
+------+--------------+----------------------------------+
| _id  | display_name | etag                             |
+------+--------------+----------------------------------+
| 1    | Fukamachi_v2 | 6739b2c2bd52d66e4e4c201624c4d1c4 |
+------+--------------+----------------------------------+
1 row in set (0.001 sec)

子テーブル更新

小テーブルも同様にposts.titleの値を更新します。こちらも先ほどと同様にetagの値が変わっていることが確認できます。

mysql> SELECT
    ->   data->>'$._id'            AS _id,
    ->   data->>'$._metadata.etag' AS etag
    -> FROM author_posts_dv
    -> WHERE data->'$._id' = 1;
+------+----------------------------------+
| _id  | etag                             |
+------+----------------------------------+
| 1    | 6739b2c2bd52d66e4e4c201624c4d1c4 |
+------+----------------------------------+
1 row in set (0.002 sec)

mysql>
mysql> UPDATE posts
    -> SET title = CONCAT(title, '(追記)')
    -> WHERE post_id = 11;
Query OK, 1 row affected (0.003 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT
    ->   data->>'$._id'            AS _id,
    ->   data->>'$._metadata.etag' AS etag
    -> FROM author_posts_dv
    -> WHERE data->'$._id' = 1;
+------+----------------------------------+
| _id  | etag                             |
+------+----------------------------------+
| 1    | d8cd09f82c51c3cb496181b20feda5a3 |
+------+----------------------------------+
1 row in set (0.001 sec)

同値 UPDATE(Changed: 0)

では同値でUPDATEを行った場合はどうなるでしょうか。同値でUPDATEした場合、⁠Changed:0)ではetagの値は変わりませんでした。

mysql> SELECT
    ->    data->>'$._id'            AS _id,
    ->    data->>'$.display_name'   AS display_name,
    ->    data->>'$._metadata.etag' AS etag
    ->  FROM author_posts_dv
    ->  WHERE data->'$._id' = 1;
+------+--------------+----------------------------------+
| _id  | display_name | etag                             |
+------+--------------+----------------------------------+
| 1    | Fukamachi_v2 | d8cd09f82c51c3cb496181b20feda5a3 |
+------+--------------+----------------------------------+
1 row in set (0.001 sec)

mysql> UPDATE authors SET display_name = 'Fukamachi_v2' WHERE author_id = 1;
Query OK, 0 rows affected (0.001 sec)
Rows matched: 1  Changed: 0  Warnings: 0

mysql> SELECT
    ->    data->>'$._id'            AS _id,
    ->    data->>'$.display_name'   AS display_name,
    ->    data->>'$._metadata.etag' AS etag
    ->  FROM author_posts_dv
    ->  WHERE data->'$._id' = 1;
+------+--------------+----------------------------------+
| _id  | display_name | etag                             |
+------+--------------+----------------------------------+
| 1    | Fukamachi_v2 | d8cd09f82c51c3cb496181b20feda5a3 |
+------+--------------+----------------------------------+
1 row in set (0.001 sec)

どうやらetagは「UPDATE 文が実行されたか」ではなく、⁠ドキュメント内容が実質的に変化したか」に紐づいているようです。

JSON Duality Viewの実行計画を見てみる

例として以下の実行計画を取得してみてみましょう。

mysql> EXPLAIN FORMAT=TREE SELECT data FROM post_detail_dv WHERE data->'$._id' = 11\G
*************************** 1. row ***************************
EXPLAIN: -> Filter: (json_extract(post_detail_dv.`data`,'$._id') = 11)  (cost=2.95 rows=4)
    -> Table scan on post_detail_dv  (cost=2.21..4.12 rows=4)
        -> Materialize  (cost=1.57..1.57 rows=4)
            -> Table scan on posts  (cost=0.65 rows=4)
            -> Select #4 (subquery in projection; dependent)
                -> Single-row index lookup on authors using PRIMARY (author_id = posts.author_id)  (cost=0.35 rows=1)
            -> Select #3 (subquery in projection; dependent)
                -> Aggregate: json_arrayagg(json_duality_object('comment_id':comments.comment_id,'commenter':comments.commenter,'body':comments.body,'created_at':comments.created_at))  (cost=0.774 rows=1)
                    -> Index lookup on comments using fk_comments_posts (post_id = posts.post_id)  (cost=0.467 rows=1.33)

1 row in set, 2 warnings (0.001 sec)

今回の結果を見る限り、data->'$._id' = 11という条件はposts.post_id = 11に単純に書き換えられるのではなく、json_extract()によるフィルタとして評価されているように見えました。さらに、ビュー全体がいったん Materializeされた後、その結果に対してフィルタが適用される形になっています。一方で、ネスト先のauthorsやcommentsについてはインデックスが利用されていました。

小規模なデータでは大きな問題になりませんが、Duality Viewを検索の中心に据える場合は、一度EXPLAINを確認しておくのがよさそうです。

まとめ

ここまで検証してみた結果、Community EditionにおけるJSON Duality Viewの位置づけは、次のように整理できそうです。

  • JSON Duality View自体の作成は可能
  • JSONドキュメントとして参照できる
  • _metadata.etagによる変更検知用のメタ情報を持つ
  • ルート・子テーブルの更新に応じてetagは変化する
  • ただしJSON経由のDML(INSERT / UPDATE / DELETE)はEnterprise Edition限定

子テーブルの更新でもetagが変化することから、JSON Duality Viewは単にJSONを生成しているだけでなく、ルート単位のひとつのドキュメントとして扱われていることがうかがえます。一方で、実行計画を見る限り、JSONパス条件がそのままjson_extract()として評価されるケースもあり、検索用途ではコスト特性の確認が必要になりそうです。

Community EditionではDMLが利用できないため、現実的には「読み取り専用のドキュメント整形レイヤ」として扱うのが無難でしょう。既存のリレーショナル設計を維持したまま、API向けのJSON形状をサーバ側で定義できる点は魅力です。MySQL REST Service(MRS)などと組み合わせれば、リレーショナルとドキュメントの二面性をそのまま外部公開できる構成も考えられます。このあたりは、もう少し遊びがいがありそうです。

おすすめ記事

記事・ニュース一覧