Rails MySQL JSON Index

#jackoo

原始目標

假設結構長得像:

1
2
3
4
5
6
7
+-----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+----------------+
| id | bigint | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | | NULL | |
| linked_id | int | NO | | NULL | |
+-----------+--------------+------+-----+---------+----------------+

如果常用 linked_id 篩選,基本上就會給他加個 Index。

Rails 端遷移長得像:

1
add_index(:targets, ["linked_id"], name: "linked_id_index")

需求改變後

linked_id 欄位變成了 linked_ids,是個 JSON ARRAY

1
2
3
4
5
6
7
+-----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+----------------+
| id | bigint | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | | NULL | |
| linked_ids| json | NO | | NULL | |
+-----------+--------------+------+-----+---------+----------------+

不能直接 add_index json 欄位,所以會用到 Sub Index,的方式:

1
CREATE INDEX linked_ids_index ON targets ((CAST(linked_ids->'$[*]' AS unsigned ARRAY)));

在 Rails 內遷移就是:

1
add_index(:targets, "(CAST(linked_ids->'$[*]' AS unsigned ARRAY))", name: "linked_ids_index")

查找對象

篩選的時候要用到 JSON_CONTAINS,例如這邊查找 linked_ids 陣列含有 4target

1
SELECT `targets`.* FROM `targets` WHERE (JSON_CONTAINS(linked_ids->'$[*]', '4'));

在 Rails 內就得將 JSON_CONTAINS 寫進 where 參數內:

1
Target.where("JSON_CONTAINS(linked_ids->'$[*]', ?)", 4)

對照 Index 前後

可以用 explain 來比對加上 Index 前後的效果:

1
Target.where("JSON_CONTAINS(linked_ids->'$[*]', ?)", 4).explain

加 Index 前:

1
2
3
4
5
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | targets | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100.0 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+

加 Index 後:

1
2
3
4
5
+----+-------------+---------+------------+-------+------------------+------------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+------------------+------------------+---------+------+------+----------+-------------+
| 1 | SIMPLE | targets | NULL | range | linked_ids_index | linked_ids_index | 9 | NULL | 2 | 100.0 | Using where |
+----+-------------+---------+------------+-------+------------------+------------------+---------+------+------+----------+-------------+
留言