如何为MySQL中的JSON字段设置索引
ztj100 2025-09-13 13:08 2 浏览 0 评论
背景
MySQL在2015年中发布的5.7.8版本中首次引入了JSON数据类型。自此,它成了一种逃离严格列定义的方式,可以存储各种形状和大小的JSON文档,例如审计日志、配置信息、第三方数据包、用户自定义字段等。
虽然MySQL提供了读写JSON数据的函数,但你很快会发现一个显著的缺失:直接给JSON列建立索引的能力。
在其他数据库中,直接索引JSON列的最佳方法通常是使用一种叫做广义倒排索引(Generalized Inverted Index,简称GIN)的类型。然而,由于MySQL没有提供GIN索引,我们无法直接对整个存储的JSON文档建立索引。不过不必担心!MySQL确实为我们提供了一种间接索引存储在JSON文档中特定部分的方式。
根据所使用的MySQL版本,有两个选项可以给JSON建立索引:
- 如果使用MySQL 5.7,需要创建一个中间生成列(Generated Column) 。
- 从MySQL 8.0.13开始,可以直接创建函数索引(Functional Index) 。
接下来,我们以一个示例表为例,该表用于记录应用程序中的各种操作日志:
CREATE TABLE `activity_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`properties` json NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
)
在该表的properties字段中插入如下结构的JSON文档:
{
"uuid": "e7af5df8-f477-4b9b-b074-ad72fe17f502",
"request": {
"email": "little.bobby@tables.com",
"firstName": "Little",
"formType": "vehicle-inquiry",
"lastName": "Bobby",
"message": "Hello, can you tell me what the specs are for this vehicle?",
"postcode": "75016",
"townCity": "Dallas"
}
}
在本例中,我们将尝试索引request对象内的email键,这可以让用户快速找到由特定人员提交的表单。
方法一:通过“生成列”索引JSON
生成列(Generated Column) 可以视为计算列、派生列或公式列。它的值是某个表达式的运算结果,而不是直接的数据输入。表达式可以包含常量值、内置函数或对其他列的引用。表达式的结果必须是定量的(Scalar)且具有确定性(Deterministic)。
由于我们试图索引properties列中的request.email字段,生成列将使用JSON的解引用(Unquoting Extraction)运算符来提取该值。
首先,运行一个SELECT语句来验证表达式是否正确:
mysql> SELECT properties->>"$.request.email" FROM activity_log;
+--------------------------------+
| properties->>"$.request.email" |
+--------------------------------+
| little.bobby@tables.com |
+--------------------------------+
符号->>是解引用运算符,它等价于如下的写法:
mysql> SELECT JSON_UNQUOTE(JSON_EXTRACT(properties, "$.request.email"))
-> FROM activity_log;
+-----------------------------------------------------------+
| JSON_UNQUOTE(JSON_EXTRACT(properties, "$.request.email")) |
+-----------------------------------------------------------+
| little.bobby@tables.com |
+-----------------------------------------------------------+
上述两种写法,具体使用哪种方式可完全取决于个人偏好。
确认表达式的有效性和准确性后,我们使用它创建一个生成列:
ALTER TABLE activity_log ADD COLUMN email VARCHAR(255)
GENERATED ALWAYS as (properties->>"$.request.email");
这条ALTER语句的前半部分非常熟悉,添加了一个名为email的列,并将其定义为VARCHAR(255)类型。而后半部分声明该列为生成列,并定义它始终等于表达式properties->>"$.request.email"的结果。
我们可以像其他列一样查询它,确认生成列已被成功添加:
mysql> SELECT id, email FROM activity_log;
+----+-------------------------+
| id | email |
+----+-------------------------+
| 1 | little.bobby@tables.com |
+----+-------------------------+
从结果可以看到,MySQL将动态维护这个列。如果我们更新了JSON数据,生成列的值也会随之改变。
接下来,我们像其他普通列一样为这生成列添加索引:
ALTER TABLE activity_log ADD INDEX email (email) USING BTREE;
现在已经成功为JSON中request.email键建立了索引。可以通过EXPLAIN验证索引是否会被用于查询:
mysql> EXPLAIN SELECT * FROM activity_log WHERE email = 'little.bobby@tables.com';
结果显示MySQL计划使用email索引来满足该查询。
索引生成列与优化器(Optimizer)
MySQL的优化器是一个强大但神秘的组件。当我们给MySQL下达命令时,它理解的是我们想要什么,而不是我们明确指定如何实现。通常,MySQL会稍微改写我们的查询,这通常是一件好事。
对于生成列上的索引,优化器能“透过”不同的访问模式以确保使用索引。例如,在以下查询中,我们通过JSON提取运算符访问数据,而不是直接使用生成的email列:
mysql> EXPLAIN SELECT * FROM activity_log
-> WHERE properties->>"$.request.email" = 'little.bobby@tables.com';
结果可以看到优化器仍然使用了email索引。哪怕使用长写的表达式,也可以看到优化器仍然“穿透”表达式并利用了索引,甚至可以通过SHOW WARNINGS查看优化器改写后的查询:
mysql> SHOW WARNINGS;
显示结果表明查询被改写为直接参考了索引的列。
方法二:函数索引(Functional Index)
从MySQL 8.0.13开始,可以跳过创建生成列的中间步骤,直接创建表达式索引(Function Index)。例如:
ALTER TABLE activity_log
ADD INDEX email ((properties->>"$.request.email")) USING BTREE;
然而,当你尝试运行上述语句时会遇到错误:
ERROR: Cannot create a functional index on an expression that returns a BLOB or TEXT. Please consider using CAST.
这是因为MySQL自动推断JSON解引用操作返回LONGTEXT类型,而无法对其直接建立索引。可通过CAST将值转化为MySQL可索引的数据类型:
ALTER TABLE activity_log
ADD INDEX email ((CAST(properties->>"$.request.email" AS CHAR(255)))) USING BTREE;
此外还需要解决字符集不匹配的问题,需要显式设置排序规则为utf8mb4_bin:
ALTER TABLE activity_log
ADD INDEX email ((
CAST(properties->>"$.request.email" AS CHAR(255)) COLLATE utf8mb4_bin
)) USING BTREE;
运行EXPLAIN后可以确认函数索引已成功被使用。
总结
尽管MySQL无法直接对JSON列建立索引,但通过生成列和函数索引的方式间接索引特定字段能够满足绝大多数场景。同时这种方式不仅适用于JSON,还适用于其它复杂或难以索引的模式。
相关推荐
- sharding-jdbc实现`分库分表`与`读写分离`
-
一、前言本文将基于以下环境整合...
- 三分钟了解mysql中主键、外键、非空、唯一、默认约束是什么
-
在数据库中,数据表是数据库中最重要、最基本的操作对象,是数据存储的基本单位。数据表被定义为列的集合,数据在表中是按照行和列的格式来存储的。每一行代表一条唯一的记录,每一列代表记录中的一个域。...
- MySQL8行级锁_mysql如何加行级锁
-
MySQL8行级锁版本:8.0.34基本概念...
- mysql使用小技巧_mysql使用入门
-
1、MySQL中有许多很实用的函数,好好利用它们可以省去很多时间:group_concat()将取到的值用逗号连接,可以这么用:selectgroup_concat(distinctid)fr...
- MySQL/MariaDB中如何支持全部的Unicode?
-
永远不要在MySQL中使用utf8,并且始终使用utf8mb4。utf8mb4介绍MySQL/MariaDB中,utf8字符集并不是对Unicode的真正实现,即不是真正的UTF-8编码,因...
- 聊聊 MySQL Server 可执行注释,你懂了吗?
-
前言MySQLServer当前支持如下3种注释风格:...
- MySQL系列-源码编译安装(v5.7.34)
-
一、系统环境要求...
- MySQL的锁就锁住我啦!与腾讯大佬的技术交谈,是我小看它了
-
对酒当歌,人生几何!朝朝暮暮,唯有己脱。苦苦寻觅找工作之间,殊不知今日之事乃我心之痛,难道是我不配拥有工作嘛。自面试后他所谓的等待都过去一段时日,可惜在下京东上的小金库都要见低啦。每每想到不由心中一...
- MySQL字符问题_mysql中字符串的位置
-
中文写入乱码问题:我输入的中文编码是urf8的,建的库是urf8的,但是插入mysql总是乱码,一堆"???????????????????????"我用的是ibatis,终于找到原因了,我是这么解决...
- 深圳尚学堂:mysql基本sql语句大全(三)
-
数据开发-经典1.按姓氏笔画排序:Select*FromTableNameOrderByCustomerNameCollateChinese_PRC_Stroke_ci_as//从少...
- MySQL进行行级锁的?一会next-key锁,一会间隙锁,一会记录锁?
-
大家好,是不是很多人都对MySQL加行级锁的规则搞的迷迷糊糊,一会是next-key锁,一会是间隙锁,一会又是记录锁。坦白说,确实还挺复杂的,但是好在我找点了点规律,也知道如何如何用命令分析加...
- 一文讲清怎么利用Python Django实现Excel数据表的导入导出功能
-
摘要:Python作为一门简单易学且功能强大的编程语言,广受程序员、数据分析师和AI工程师的青睐。本文系统讲解了如何使用Python的Django框架结合openpyxl库实现Excel...
- 用DataX实现两个MySQL实例间的数据同步
-
DataXDataX使用Java实现。如果可以实现数据库实例之间准实时的...
- MySQL数据库知识_mysql数据库基础知识
-
MySQL是一种关系型数据库管理系统;那废话不多说,直接上自己以前学习整理文档:查看数据库命令:(1).查看存储过程状态:showprocedurestatus;(2).显示系统变量:show...
- 如何为MySQL中的JSON字段设置索引
-
背景MySQL在2015年中发布的5.7.8版本中首次引入了JSON数据类型。自此,它成了一种逃离严格列定义的方式,可以存储各种形状和大小的JSON文档,例如审计日志、配置信息、第三方数据包、用户自定...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)
- vmware17pro最新密钥 (34)
- mysql单表最大数据量 (35)