从Object(JSON)迁移到新版JSON类型

更新时间:
复制 MD 格式

云数据库ClickHouse企业版已将实验性的Object(JSON)数据类型替换为生产就绪的新版JSON类型。如果您在25.10或以下版本中使用了Object(JSON)类型,需要在升级到25.12版本前完成迁移。

背景信息

旧的半结构化数据类型Object(JSON)(等价写法Object('json'))已被弃用,并从25.12版本的代码库中彻底移除。所有使用该类型的实例都需要迁移到新版JSON类型。

迁移的核心目标是将所有Object(JSON)列就地迁移为JSON列,且不丢失数据、不长时间中断业务。迁移可以通过一条ALTER TABLE ... MODIFY COLUMN变更完成。

重要

迁移必须在仍支持Object(JSON)的版本(≤25.10)上完成,然后才能升级到25.12。升级到25.12后将无法执行迁移操作。

迁移前的版本与设置

重要

建议先升级到25.10版本再迁移,新的JSON类型表现更稳定。

迁移操作对版本有如下要求:

集群版本

迁移操作说明

25.3~25.10

新版JSON类型默认可用,直接执行迁移语句即可,无需额外设置。

24.10~25.2

需要在执行迁移的会话中显式开启enable_json_type=1

24.8~24.9

需要在执行迁移的会话中显式开启allow_experimental_json_type=1

相关参数说明如下:

参数

作用

默认值演变

迁移时处理方式

enable_json_type

允许使用新版JSON类型

24.10引入(默认false)→ 25.3起默认true → 现已obsolete(恒为true)

≥25.3无需设置;24.10~25.2需在会话中加enable_json_type=1

allow_experimental_json_type

同上(enable_json_type的旧名,24.10起被后者取代)

24.8引入(默认false)→ 25.3起默认true → 现已obsolete

24.8~24.9allow_experimental_json_type=1;更高版本无需设置

use_json_alias_for_old_object_type

JSON别名创建旧Object类型而非新类型

24.8起由true改为false → 现已obsolete(恒为false)

保持默认false,任何时候都不要开启,否则MODIFY COLUMN ... JSON会改回旧Object

allow_experimental_object_type

允许创建或使用旧Object类型

默认false → 现已obsolete

迁移目标是新类型,无需开启

操作步骤

步骤一:查找需要迁移的列

如果您管理多个实例,可以先通过聚合查询统计仍在使用旧类型的实例和表数量:

SELECT
    uniqExact(spoken_name)                         AS instances,
    groupUniqArray(spoken_name),
    uniqExact(database, name)                      AS tables,
    any(create_table_query)
FROM merge('tables.*')
WHERE create_table_query LIKE '%Object(\'json\')%'
  AND spoken_name NOT LIKE '%stress%'
  AND scrape_time_microseconds > now() - INTERVAL 40 DAY
  AND database NOT IN ('system', 'information_schema', 'INFORMATION_SCHEMA')

在单个实例上,执行以下SQL查询获取所有使用旧Object(JSON)类型的列清单:

SELECT database, table, name AS column, type
FROM system.columns
WHERE type LIKE '%Object(%json%)%'
  AND database NOT IN ('system', 'information_schema', 'INFORMATION_SCHEMA')
ORDER BY database, table, name;

查询结果将列出所有需要迁移的数据库名、表名、列名和当前类型。

步骤二:执行迁移

重要

迁移前请注意以下事项:

  • 建议先在测试环境或克隆表上验证迁移后查询的兼容性。新版JSON的路径访问语法与旧Object存在差异,详情请参见步骤三:迁移后查询适配。

  • 变更需要读取整列并重写数据Part,大表的内存和磁盘开销较高。建议选择业务低峰期执行,大表可按分区分批迁移。

您可以根据业务需求选择以下任一方案执行迁移。

方案一:原地修改列类型(推荐)

对每个需要迁移的列,执行ALTER TABLE ... MODIFY COLUMN语句将类型改为新版JSON

ALTER TABLE <db>.<table>
    MODIFY COLUMN <column> JSON
    SETTINGS mutations_sync = 1;
-- 在25.3以下的版本上,会话需额外开启 enable_json_type = 1

参数说明:

  • mutations_sync = 1:同步等待变更在当前副本上完成。

  • 该变更会重写包含该列的所有数据Part,将旧Object存储格式转换为新JSON类型的存储格式。

方案二:新建表并迁移数据

如果您希望通过新建表的方式迁移数据,可以按照以下步骤操作:

1. 创建新表,结构与旧表一致:

CREATE TABLE new_table AS old_table;

2. 修改新表的列类型为新JSON

ALTER TABLE new_table MODIFY COLUMN `your_json_column` JSON;

3. 将旧表数据插入新表:

INSERT INTO new_table SELECT * FROM old_table;

步骤三:迁移后查询适配

迁移完成后,写入操作(INSERT)通常无需修改,但查询操作(SELECT)可能需要适配。核心差异在于:旧Object的路径访问返回的是推断出的具体类型,而新JSON的路径访问返回的是Dynamic类型。

写入操作(无需修改)

新旧类型都接受JSON文档作为输入,插入侧代码通常无需改动。唯一需要留意的是:如果代码里硬编码了类型名字符串(如建表DDL、CAST目标类型里写死了Object('json')),需要把类型名改成JSON。纯数据写入路径不受影响。

写入方式

Object(JSON)

JSON

行格式导入

INSERT INTO t FORMAT JSONEachRow {...}

完全一致

整列当字符串导入

INSERT INTO t FORMAT JSONAsObject / JSONAsString

完全一致

VALUESJSON字符串

INSERT INTO t VALUES ('{"a":1}')

完全一致

StringCAST

CAST(s, 'Object(''json'')')

CAST(s, 'JSON')s::JSON(仅类型名不同)

查询操作(需要逐条检查)

查询操作需要根据以下场景逐一检查和适配:

场景

Object语法

JSON语法

只取值(展示或透传,不做运算)

SELECT col.a.b FROM t;

SELECT col.a.b FROM t;(无需加类型,返回Dynamic直接显示值)

读标量路径并参与运算、函数或比较

SELECT col.a.b * 2 FROM t;col.a.b已是具体类型,直接可算)

col.a.b返回Dynamic,需用.:Type显式取类型:SELECT col.a.b.:Float64 * 2 FROM t;

GROUP BYORDER BY路径

SELECT col.status, count() FROM t GROUP BY col.status;(直接可用)

Dynamic默认禁止分组和排序,需显式开启:... GROUP BY col.status SETTINGS allow_suspicious_types_in_group_by = 1, allow_suspicious_types_in_order_by = 1;

对象数组

SELECT col.items.name FROM t;(旧Object把嵌套数组直接展成Array(...)子列,按点号访问)

Dynamic内的Array(JSON),用path[]语法:SELECT col.items[].name FROM t;[]表示数组层级,可配合ARRAY JOIN

读嵌套子对象

SELECT col.sub.x, col.sub.y FROM t;(只能逐个子字段按路径读)

^把整个子对象作为JSON读出:SELECT col.^sub FROM t;(也可继续col.^sub.path

新语法示例如下:

-- 标量取类型
SELECT json.a.g.:Float64, json.d.:Date FROM t;

-- 分组 / 排序需放开设置
SELECT json.repo.name, count() AS c
FROM t
GROUP BY json.repo.name
ORDER BY c DESC
SETTINGS allow_suspicious_types_in_group_by = 1, allow_suspicious_types_in_order_by = 1;

-- 对象数组:[] 表示数组层级,可配合 ARRAY JOIN
SELECT json.payload.commits[].author.name FROM t;

-- 读整个子对象
SELECT json.^metadata FROM t;

步骤四:验证迁移完成

确认集群中不再存在Object(JSON)类型的列:

SELECT count() FROM system.columns
WHERE type LIKE '%Object(%json%)%'
  AND database NOT IN ('system', 'information_schema', 'INFORMATION_SCHEMA');

确认所有JSON相关变更均已完成:

SELECT count() FROM system.mutations
WHERE command ILIKE '%JSON%' AND is_done = 0;

两者均返回0即视为该实例迁移完成。

常见问题

大表迁移内存超限

大表在把整列转换为JSON时可能触发内存超限错误。典型错误信息如下:

latest_fail_reason: (total) memory limit exceeded: would use 7.20 GiB
    ... while executing 'FUNCTION _CAST(<column> :: 0, 'JSON' :: 1) -> _CAST(<column>, 'JSON') JSON'
    (while reading from part .../all_1_1_0 located on disk s3WithKeeperDisk of type s3)
    While executing MergeTreeSequentialSource
latest_fail_error_code_name: MEMORY_LIMIT_EXCEEDED

可通过以下方式缓解:

  • 扩容或临时提升实例内存上限。

  • 升级到更新的版本(建议升级到25.10)后重试。

  • 对超大表按分区分批迁移,降低单次变更的峰值内存。

说明

变更失败不会丢失数据。修正问题后,mutation会在失败的Part上自动重试,直至is_done = 1

查看迁移进度

可通过以下查询查看正在执行的JSON相关变更及其进度:

SELECT *
FROM system.mutations
WHERE command ILIKE '%JSON%'
  AND is_done = 0;

关注parts_to_do(剩余待处理Part数)、is_donelatest_fail_reason字段。