PingCAP
  • 文档
  • 案例
  • 博客
  • 关于
  • 下载
PingCAP
  • 文档
  • 案例
  • 博客
  • 关于
  • 下载

Contact

  • 微信扫一扫
    微信ID:pingcap2015

English
文档
  • 关于 TiDB
    • TiDB 简介
    • TiDB 整体架构
    • TiDB 核心特性
  • TiDB 快速入门
    • 快速入门指南
    • SQL 基本操作
  • TiDB 用户文档
    • TiDB 数据库管理
      • TiDB 服务
      • TiDB 进程启动参数
      • TiDB 数据目录
      • TiDB 系统数据库
      • TiDB 系统变量
      • TiDB 专用系统变量和语法
      • TiDB 服务器日志文件
      • TiDB 访问权限管理
      • TiDB 用户账户管理
      • 使用加密连接
    • SQL 优化
      • 理解 TiDB 执行计划
      • 统计信息
    • 语言结构
      • 字面值
      • 数据库、表、索引、列和别名
      • 关键字和保留字
      • 用户变量
      • 表达式语法
      • 注释语法
    • 字符集和时区
      • 字符集支持
      • 字符集配置
      • 时区
    • 数据类型
      • 数值类型
      • 日期和时间类型
      • 字符串类型
      • JSON 数据类型
      • 枚举类型
      • 集合类型
      • 数据类型默认值
    • 函数和操作符
      • 函数和操作符概述
      • 表达式求值的类型转换
      • 操作符
      • 控制流程函数
      • 字符串函数
      • 数值函数与操作符
      • 日期和时间函数
      • 位函数和操作符
      • Cast 函数和操作符
      • 加密和压缩函数
      • 信息函数
      • JSON 函数
      • GROUP BY 聚合函数
      • 其他函数
      • 精度数学
    • SQL 语句语法
      • 数据定义语句 (DDL)
      • 数据操作语句 (DML)
      • 事务语句
      • 数据库管理语句
      • Prepared SQL 语句语法
      • 实用工具语句
      • TiDB SQL 语法图
    • JSON 支持
    • Connectors 和 API
    • TiDB 事务隔离级别
    • 错误码与故障诊断
    • 与 MySQL 兼容性对比
    • TiDB 内存控制
    • 慢查询日志
    • 高级功能
      • 历史数据回溯
      • 垃圾回收 (GC)
  • TiDB 运维文档
    • 软硬件环境需求
    • 部署集群
      • Ansible 部署方案(强烈推荐)
      • 离线 Ansible 部署方案
      • Docker 部署方案
      • Docker Compose 部署方案
      • 跨机房部署方案
    • 配置集群
      • 参数解释
      • TiDB 配置项解释
      • 使用 Ansible 变更组件配置
      • 开启 TLS 验证
      • 生成自签名证书
    • 监控集群
      • 整体监控框架概述
      • 重要监控指标详解
      • 组件状态 API & 监控
    • 扩容缩容
      • 集群扩容缩容方案
      • 使用 Ansible 扩容缩容
    • 升级
      • 升级组件版本
      • TiDB 2.0 升级操作指南
    • 性能调优
    • 备份与迁移
      • 备份与恢复
      • 数据迁移
        • 数据迁移概述
        • 全量导入
        • 增量导入
    • 故障诊断
  • TiDB 周边工具
    • Syncer
    • Loader
    • TiDB-Binlog
    • PD Control
    • PD Recover
    • TiKV Control
    • TiDB Controller
  • TiSpark 文档
    • TiSpark 快速入门指南
    • TiSpark 用户指南
  • 常见问题与解答(FAQ)
  • 最佳实践
  • 版本发布历史
    • 2.1 RC3
    • 2.1 RC2
    • 2.0.7
    • 2.1 RC1
    • 2.0.6
    • 2.0.5
    • 2.1 Beta
    • 2.0.4
    • 2.0.3
    • 2.0.2
    • 2.0.1
    • 2.0
    • 2.0 RC5
    • 2.0 RC4
    • 2.0 RC3
    • 2.0 RC1
    • 1.1 Beta
    • 1.1 Alpha
    • 1.0
    • Pre-GA
    • RC4
    • RC3
    • RC2
    • RC1
  • TiDB 路线图
  • 用户案例
    • 北京银行
    • 海航
    • 今日头条
    • 转转
    • Mobike
    • 饿了么(一)
    • 饿了么(二)
    • 爱奇艺
    • 易果生鲜
    • 同程旅游
    • 去哪儿
    • G7
    • 一面数据
    • 凤凰网
    • 猿辅导
    • Mobikok
    • 二维火
    • 客如云
    • Ping++
    • 乐视云
    • 零氪科技
    • 威锐达测控
    • 盖娅互娱
    • 游族网络
    • 西山居
    • FUNYOURS JAPAN
    • 特来电
    • 万达网络
    • 360金融
    • 中国电信翼支付
    • 某电信运营商
  • 更多资源
    • 常用工具
    • PingCAP 团队技术博客
    • 知乎专栏
    • Weekly
    • 英文文档

JSON 函数及 Generated Column

概述

为了在功能上兼容 MySQL 5.7 及以上,同时更好地支持文档类型存储,我们在最新版本的 TiDB 中加入了 JSON 的支持。TiDB 所支持的文档是指以 JSON 为编码类型的键值对的组合。用户可以在 TiDB 的表中使用 JSON 类型的字段,同时以生成列(generated column)的方式为 JSON 文档内部的字段建立索引。基于此,用户可以很灵活地处理那些 schema 不确定的业务,同时不必受限于传统文档数据库糟糕的读性能及匮乏的事务支持。

JSON 功能介绍

TiDB 的 JSON 主要参考了 MySQL 5.7 的用户接口。例如,可以创建一个表,包含一个 JSON 字段来存储那些复杂的信息:

CREATE TABLE person (
    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    address_info JSON
);

当我们向表中插入数据时,便可以这样处理那些模式不确定的数据了:

INSERT INTO person (name, address_info) VALUES ("John", '{"city": "Beijing"}');

就这么简单!直接在 JSON 字段对应的位置上,放一个合法的 JSON 字符串,就可以向表中插入 JSON 了。TiDB 会解析这个文本,然后以一种更加紧凑、易于访问的二进制形式来保存。

当然,你也可以将其他类型的数据用 CAST 转换为 JSON:

INSERT INTO person (name, address_info) VALUES ("John", CAST('{"city": "Beijing"}' AS JSON));
INSERT INTO person (name, address_info) VALUES ("John", CAST('123' AS JSON));
INSERT INTO person (name, address_info) VALUES ("John", CAST(123 AS JSON));

现在,如果我们想查询表中所有居住在北京的用户,该怎么做呢?需要把数据全拉回来,然后在业务层进行过滤吗?不需要,和 MongoDB 等文档数据库相同,我们有在服务端支持用户各种复杂组合查询条件的能力。你可以这样写 SQL:

SELECT id, name FROM person WHERE JSON_EXTRACT(address_info, '$.city') = 'Beijing';

TiDB 支持 JSON_EXTRACT 函数,该函数与 MySQL 5.7 中 JSON_EXTRACT 的用法完全相同。这个函数的意思就是,从 address_info 这个文档中取出名为 city 这个字段。它的第二个参数是一个“路径表达式”,我们由此可以指定到底要取出哪个字段。关于路径表达式的完整语法描述比较复杂,我们还是通过几个简单的例子来了解其用法:

SET @person = '{"name":"John","friends":[{"name":"Forest","age":16},{"name":"Zhang San","gender":"male"}]}';

SELECT JSON_EXTRACT(@person,  '$.name'); -- gets "John"
SELECT JSON_EXTRACT(@person,  '$.friends[0].age'); -- gets 16
SELECT JSON_EXTRACT(@person,  '$.friends[1].gender'); -- gets "male"
SELECT JSON_EXTRACT(@person,  '$.friends[2].name'); -- gets NULL

除了插入、查询外,对 JSON 的修改也是支持的。总的来说,目前我们支持的 MySQL 5.7 的 JSON 函数如下表所示:

  • JSON_EXTRACT
  • JSON_ARRAY
  • JSON_OBJECT
  • JSON_SET
  • JSON_REPLACE
  • JSON_INSERT
  • JSON_REMOVE
  • JSON_TYPE
  • JSON_UNQUOTE
  • JSON_MERGE
  • JSON_CONTAINS
  • JSON_CONTAINS_PATH
  • JSON_LENGTH

直接从名字上,我们便能得出这些函数的大致用途,而且它们的语义也与 MySQL 5.7 完全一致,因此,想要查询它们具体的用法,我们可以直接查阅 MySQL 5.7 的相关文档。MySQL 5.7 的用户可以无缝迁移至 TiDB。

熟悉 MySQL 5.7 的用户会发现,TiDB 尚未完全支持 MySQL 5.7 中所有的 JSON 函数。通过 TiDB #7546 可查看 TiDB 中添加新函数的进度。

使用生成列对 JSON 建索引

在有了上述的知识铺垫后,您可能会发现我们在查询 JSON 中的一个字段时,走的是全表扫描。使用 TiDB 的 EXPLAIN 语句时,一个比 MySQL 完备得多的结果会告诉我们,的确是全表扫描。那么,我们能否对 JSON 字段进行索引呢?

首先,这种索引是错误的:

CREATE TABLE person (
    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    address_info JSON,
    KEY (address_info)
);

这并非是因为技术上无法支持,而是因为对 JSON 的直接比较,本身就是没有意义的 —— 尽管我们可以人为地约定一些比较规则,比如 ARRAY 比所有的 OBJECT 都大 —— 但是这并没有什么用处。因此,正如 MySQL 5.7 所做的那样,我们禁止了直接在 JSON 字段上创建索引,而是通过生成列的方式,支持了对 JSON 文档内的某一字段建立索引:

CREATE TABLE person (
    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    address_info JSON,
    city VARCHAR(64)  AS (JSON_UNQUOTE(JSON_EXTRACT(address_info, '$.city'))) VIRTUAL,
    KEY (city)
);

这个表中,city 列就是一个 生成列。顾名思义,该列由表中其他的列生成,而不能显式地在插入或更新时为它赋一个值。对于生成列,用户还可以指定其为 VIRTUAL 来避免它被显式地保存在记录中,而是在需要地时候再由其他列来生成,这对于列比较宽且需要节约存储空间地情况尤为有用。有了这个生成列,我们就可以在它上面建立索引了,在用户看来与常规的列便没什么两样,是不是很简单呢?而查询的时候,我们可以:

SELECT name, id FROM person WHERE city = 'Beijing';

这样,便可以走索引了!

另外,需要注意的是,如果 JSON 文档中指定路径下的字段不存在,那么 JSON_EXTRACT 的结果会是 NULL ,这时,带有索引的生成列的值也就为 NULL 了。因此,如果这是用户不希望看到的,那也可以在生成列上增加 NOT NULL 约束,这样,当插入新的纪录算出来的 city 字段为 NULL 时,便可以检查出来了。

目前的一些限制

目前 JSON 及生成列仍然有一些限制:

  • 不能 ALTER TABLE 增加 STORED 存储方式的生成列;
  • 不能 ALTER TABLE 在生成列上增加索引;

这些功能,包括其他一些 JSON 函数的实现尚在开发过程中。

"JSON 函数及 Generated Column" 更新于 Sep 28 2018: Address comment (8c4731e)
修改本文

本页导航

产品

  • TiDB
  • TiSpark
  • TiDB 路线图

文档

  • 快速入门
  • 最佳实践
  • 常见问题解答
  • TiDB 周边工具
  • 版本发布说明

资源

  • 博客
  • GitHub
  • 知乎专栏

公司

  • 关于我们
  • 招贤纳士
  • 新闻报道

联系我们

  • Twitter
  • LinkedIn
  • Reddit
  • Google Group
  • Stack Overflow
  • 微信公众号

    微信扫一扫
    微信ID:pingcap2015

© 2018 北京平凯星辰科技发展有限公司

English