MySQL 新技术系列第三期

📢 MySQL 新技术系列第三期

各位数据库同行、开发者朋友们,大家好!

欢迎回到「MySQL 新技术」系列。在第一期,我们全景解读了 MySQL 9.x 系列的版本策略与核心新特性;在第二期,我们聚焦于向量检索技术,剖析了 MySQL 如何从传统关系型数据库向「AI-Ready 数据平台」转型。

这一期,我们将深入探讨贯穿整个 9.x 系列的另一项革命性能力——JavaScript 存储程序多语言引擎(MLE)。从 9.0 引入基础支持,到 9.2 的 LIBRARY 模块化,再到 9.3 的 DECIMAL 与国际化 API 增强,JavaScript 正在重写数据库编程的规则。本文将带你系统解读 JavaScript 存储程序的技术演进、实战应用,以及开源社区如何突破 Oracle 企业版限制,将这一能力推向更广阔的应用空间。

一、为什么数据库需要 JavaScript?

1.1 SQL/PSM 的局限性

在过去二十年中,MySQL 存储过程只能使用 SQL/PSM(Persistent Stored Modules)语言编写。这是一种声明式语言,在处理复杂算法、字符串操作、JSON 处理等任务时显得笨拙且低效。正如 Percona 的工程师所言:SQL/PSM 难以调试、表现力有限,对于算法密集型任务更是「糟糕的选择」。

1.2 将逻辑推向数据的价值

JavaScript 存储程序的核心价值在于减少数据库与应用程序之间的数据移动
问题JavaScript 存储程序的解决方案网络开销数据处理在数据库内完成,无需往返传输延迟累积减少应用层与数据库之间的频繁交互内存/存储成本避免在中间层处理大量数据安全风险数据留存在数据库内,降低暴露面云出口费用减少跨服务的数据传输,降低云成本

1.3 JavaScript 的天然优势

MySQL 官方选择 JavaScript 作为首门多语言引擎支持的编程语言,理由充分:超过 1700 万 JavaScript 开发者的庞大技能池、npm 上数百万个可复用的 ECMAScript 模块、ECMAScript 2021 标准带来的现代语言特性,以及 GraalVM 运行时带来的高性能执行。

二、技术全景:从 MLE 到 JavaScript 存储程序

2.1 多语言引擎组件(MLE)

MLE(Multilingual Engine Component)是 MySQL 企业版的一部分,在存储函数和存储过程中提供对 SQL 以外语言的支持。

核心特征:

  • URNfile://component_mle
  • 平台支持:MySQL 企业版支持的所有平台(Solaris 除外)
  • 运行时:JavaScript 代码通过 GraalVM 执行,用户可免费使用 GraalVM 企业版(EE)的所有功能,包括编译器优化、性能和安全特性
  • 安装要求:MLE 组件需要通过 INSTALL COMPONENT 安装

安装注意事项: 无法从已经创建或执行过任何 JavaScript 存储程序的用户会话中卸载 MLE 组件。建议在不同于安装 MLE 组件的会话中创建和执行 JavaScript 存储程序。

2.2 版本演进时间线

版本JavaScript 能力演进9.0首次引入 JavaScript 存储程序,支持基础存储函数和存储过程9.1支持 JavaScript 程序使用 VECTOR 类型9.2LIBRARY 模块化CREATE LIBRARY / DROP LIBRARY;事务 API(START TRANSACTION / COMMIT / ROLLBACK);ENUM/SET 类型识别;MySQL 内置函数调用(通过全局 Mysql 对象)9.3DECIMAL 类型支持;国际化 API(Intl);await 动态加载库;ALTER LIBRARY / SHOW LIBRARY STATUS9.6+持续优化与稳定性增强

2.3 数据类型的无缝映射

MySQL 在 JavaScript 存储程序中对数据类型的支持覆盖了绝大部分常用类型:

整数类型: TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT 全系列,支持 SIGNED 和 UNSIGNED。BOOL 和 SERIAL 作为整数类型处理。

字符串类型: CHAR、VARCHAR、TEXT、BLOB 均受支持,使用 utf8mb4 或 binary 字符集。最大 LONGTEXT 值支持 1,071,741,799 字符;最大 LONGBLOB 支持 2,147,483,639 字节。

JSON 类型: 完整支持,JavaScript 代码中直接作为对象处理。

浮点数: FLOAT、DOUBLE、REAL 受支持,但 UNSIGNED FLOAT 和 UNSIGNED DOUBLE 已弃用。

DECIMAL 类型: 9.3 版本新增对 DECIMAL 的完整支持,默认以 JavaScript 字符串形式存储以保证精度,同时也提供数值处理的灵活选项。

时间类型: DATE、DATETIME、TIMESTAMP、TIME、YEAR 均受支持。

三、核心语法与实战

3.1 基础:创建 JavaScript 存储函数

最简单的一个加法函数,展示 JavaScript 存储程序的基本结构:

CREATE FUNCTION add_nos(arg1 INT, arg2 INT) 
RETURNS INT 
LANGUAGE JAVASCRIPT 
AS 
$$
    return arg1 + arg2
$$;

mysql> SELECT add_nos(12, 52);
+----------------+
| add_nos(12,52) |
+----------------+
|             64 |
+----------------+

上述示例的关键点:LANGUAGE JAVASCRIPT 声明语言类型;$$ 分隔符包裹 JavaScript 代码;参数名在函数体内直接引用,SQL 类型与 JavaScript 类型自动转换。

3.2 存储过程:使用 OUT 参数

下面是一个带 OUT 参数的存储过程,将当前日期时间以可读格式返回:

CREATE PROCEDURE get_current_datetime(OUT res VARCHAR(25))
LANGUAGE JAVASCRIPT
AS
$$
    res = new Date().toLocaleString();
$$;

mysql> CALL get_current_datetime(@dt);
mysql> SELECT @dt;
+----------------------+
| @dt                  |
+----------------------+
| 4/23/2026, 10:30:45  |
+----------------------+

3.3 表列作为参数

JavaScript 存储函数可以直接以表列的值作为参数调用,并在 SQL 表达式的任意位置使用(WHERE、HAVING、ORDER BY、JOIN 子句):

CREATE FUNCTION gcd(a INT, b INT) 
RETURNS INT 
NO SQL 
LANGUAGE JAVASCRIPT 
AS
$$
    let x = Math.abs(a);
    let y = Math.abs(b);
    while (y) {
        let t = y;
        y = x % y;
        x = t;
    }
    return x;
$$;

mysql> SELECT c1, c2, gcd(c1, c2) AS G FROM t1 WHERE gcd(c1, c2) > 1;
+----+----+---+
| c1 | c2 | G |
+----+----+---+
| 12 | 70 | 2 |
| 81 | 9  | 9 |
+----+----+---+

当参数类型不匹配时,MySQL 会尝试自动转换。例如传入浮点数 500.3,会使用 Math.round() 舍入为整数后再处理。

3.4 调试:console.log 的使用

像其他 JavaScript 环境一样,console.log() 在 MySQL 存储函数中同样可用,但需要通过 mle_session_state() 函数查看输出:

CREATE FUNCTION division(a INT, b INT) 
RETURNS DOUBLE 
LANGUAGE JAVASCRIPT 
AS
$$
    function validDenominator(num) {
        console.log("Validating input value: ", num);
        return num !== 0;
    }
    return validDenominator(b) ? a / b : null;
$$;

mysql> SELECT division(5, 2), division(2, 0);
+--------+--------+
| demo_1 | demo_2 |
+--------+--------+
|    2.5 |   NULL |
+--------+--------+

-- 查看调试输出
mysql> SELECT mle_session_state('stdout');
+--------------------------------------------------+
| mle_session_state('stdout')                      |
+--------------------------------------------------+
| Validating input value: 2nValidating input value: 0n |
+--------------------------------------------------+

mle_session_state() 支持以下键值:is_activestdoutstderrstack_tracestored_functionsstored_proceduresstored_programs

四、LIBRARY 对象:JavaScript 代码的模块化管理

4.1 从单体到模块化

随着项目规模的增大,代码复用成为刚需。MySQL 9.2 引入了 LIBRARY 对象,允许开发者将 JavaScript 和 WebAssembly 代码封装成可复用的库模块。

4.2 创建和使用库

-- 创建数据库并切换到该库
CREATE DATABASE IF NOT EXISTS jslib;
USE jslib;

-- 创建库 lib1,导出一个函数 f
CREATE LIBRARY IF NOT EXISTS jslib.lib1 
LANGUAGE JAVASCRIPT 
AS
$$
    export function f(n) {
        return n;
    }
$$;

-- 创建库 lib2,导出一个函数 g
CREATE LIBRARY IF NOT EXISTS jslib.lib2 
LANGUAGE JAVASCRIPT 
AS
$$
    export function g(n) {
        return n * 2;
    }
$$;

-- 在存储函数中使用库
CREATE FUNCTION use_lib1(n INT) 
RETURNS INT 
LANGUAGE JAVASCRIPT 
USING jslib.lib1
AS
$$
    return lib1.f(n);
$$;

被导入的函数必须使用 export 关键字声明。可以将一个函数声明为 export default,此时导入方需通过 libname.default() 调用。

4.3 库管理 SQL 语句

语句说明CREATE LIBRARY创建库,创建时会解析并检查 JavaScript 代码有效性ALTER LIBRARY修改库定义(9.3+)DROP LIBRARY删除库SHOW CREATE LIBRARY查看库的源代码SHOW LIBRARY STATUS查看库状态信息(9.3+)

4.4 信息 Schema 表

  • LIBRARIES:列出所有 JavaScript 库的详细信息(库名、定义、创建者、创建时间等)
  • ROUTINE_LIBRARIES:记录使用了 JavaScript 库的存储例程信息

4.5 为什么 LIBRARY 很重要

  1. 一次编写,处处复用:避免重复代码,降低维护成本
  2. 内存效率:代码被缓存一次,无论被导入多少次都不会造成内存膨胀
  3. 依赖管理:库可以导入其他库,支持依赖链,钻石依赖和循环依赖都能正确处理
  4. 生态集成:可以直接使用 npm 上的 ECMAScript 模块(ESM)和 Wasm 库

五、高级特性

5.1 JavaScript 事务 API(9.2)

MLE 组件提供 JavaScript MySQL 事务 API,支持:
操作说明START TRANSACTION启动事务COMMIT提交事务ROLLBACK回滚事务SET AUTOCOMMIT设置自动提交模式事务保存点支持 SAVEPOINT 操作SqlError 对象用于处理 SQL 语句执行错误

5.2 MySQL 内置函数调用(9.2)

通过全局 Mysql 对象,可以直接在 JavaScript 中调用 MySQL 内置函数:

// 在 JavaScript 存储程序中
let rand = Mysql.rand();      // 调用 RAND()
Mysql.sleep(1);               // 调用 SLEEP(1)
let uuid = Mysql.uuid();      // 调用 UUID()

5.3 会话变量访问(9.2)

通过 JavaScript 的全局 Session 对象直接访问 MySQL 用户变量:

// 读取和设置用户变量
let myVar = Session.my_var;   // 读取 @my_var
Session.my_var = 42;          // 设置 @my_var = 42

5.4 国际化 API(9.3)

MySQL 9.3 集成了 JavaScript 的国际化 API(Intl),让本地化和国际化操作更加顺畅:

CREATE FUNCTION format_currency(amount DECIMAL(10,2), locale VARCHAR(10))
RETURNS VARCHAR(255)
LANGUAGE JAVASCRIPT
AS
$$
    return new Intl.NumberFormat(locale, {
        style: 'currency',
        currency: 'USD'
    }).format(amount);
$$;

mysql> SELECT format_currency(1234.56, 'en-US');
+------------------------------------+
| format_currency(1234.56, 'en-US')  |
+------------------------------------+
| $1,234.56                          |
+------------------------------------+

5.5 动态库加载(9.3)

通过 await 关键字实现 JavaScript 库的动态加载,优化异步操作:

// 动态导入库,在运行时而非编译时加载
const lib = await import('libname');
return lib.someFunction();

六、HeatWave GenAI:JavaScript 与 AI 的深度融合

在 MySQL HeatWave 中,JavaScript 存储程序与 GenAI 能力实现了深度集成。

6.1 JavaScript GenAI API

MySQL 提供 JavaScript GenAI API,允许 JavaScript 开发者使用大语言模型(LLM)执行自然语言搜索,而无需直接操作底层的 SQL 存储过程。

LLM 在 JavaScript API 中体现为 LLM 类及其相关方法,开发者可以用 JavaScript 的惯用方式调用 HeatWave GenAI 能力。

6.2 调用 HeatWave GenAI 例程

JavaScript 存储程序中包含 GenAI API,可以使用 JavaScript 函数调用不同的 MySQL HeatWave GenAI 例程。

在 HeatWave 中,JavaScript 函数和存储过程可以通过 GraalVM 执行,它们可以透明地操作 MySQL 数据,无论底层存储引擎是 InnoDB 还是 HeatWave。

6.3 实战案例:AI 搜索引擎

在实际应用中,JavaScript 存储程序用于在 HeatWave 内部实现完整的 AI 搜索引擎流程:生成查询向量嵌入、执行余弦相似性搜索、对结果进行重排序以优化相关性。

七、Percona Server:打破企业版壁垒的开源实现

7.1 为什么需要开源替代?

Oracle 的 JavaScript 存储程序仅作为 MySQL 企业版或 Oracle Cloud 的一部分提供,免费开源的 MySQL 社区版用户无法在生产环境中使用这一功能。

7.2 Percona 的 V8 引擎方案

Percona Server for MySQL 提供了实验性的 JavaScript 存储程序支持,其核心差异在于:
对比项Oracle MySQL(企业版)Percona ServerJavaScript 引擎GraalVMGoogle V8目标版本9.0 创新版首发8.4 LTS 系列(更稳定)开源状态仅企业版/云版可用完全开源免费JSON 处理标准支持自动将 MySQL JSON 类型编组为 JS 对象

7.3 V8 引擎带来的独特优势

Percona 选择 V8 引擎带来了独特的优势:

原生正则表达式支持: 可以使用 JavaScript 的正则表达式进行复杂数据验证,例如电子邮件地址格式校验:

CREATE FUNCTION validate_email_js(email VARCHAR(255)) 
RETURNS BOOLEAN 
LANGUAGE JS 
AS
$$
    const emailRegex = /^[^s@]+@[^s@]+.[^s@]+$/;
    return emailRegex.test(email);
$$;

原生 JSON 处理: MySQL JSON 类型自动编组为 JavaScript 对象,无需使用冗长的 JSON_EXTRACTJSON_SET 等函数:

CREATE FUNCTION normalize_order(raw_json JSON) 
RETURNS JSON 
LANGUAGE JS 
AS
$$
    return {
        id: raw_json.id,
        total: raw_json.items.reduce((acc, item) => acc + item.price, 0),
        tags: raw_json.metadata.tags.map(t => t.toLowerCase())
    };
$$;

7.4 获取与安装

Percona Server for MySQL 的 JavaScript 支持目前处于实验阶段,二进制包可在 Percona 的 experimental 仓库中获取,源代码在 Percona 的 GitHub 仓库 js-lang 分支中可用。

八、生产环境考量

8.1 复制兼容性

MySQL 复制与 JavaScript 存储程序兼容,前提是拓扑结构中的每个服务器上都安装了 MLE 组件

关键注意事项:

  1. 如果从库没有安装 MLE 组件,虽然能接受来自主库的 CREATE FUNCTIONCREATE PROCEDURE 语句,但无法执行这些存储程序
  2. CREATE LIBRARYDROP LIBRARY 以及包含 USING 子句的 CREATE FUNCTION/PROCEDURE 语句,在未安装 MLE 组件的服务器上会被直接拒绝
  3. 不支持混合配置的复制(即部分服务器安装了 MLE 组件,部分没有)

安装建议: 如果要在复制拓扑中使用 JavaScript 存储程序,建议先停止复制,在拓扑中的每个服务器上安装(或卸载)MLE 组件,然后才允许复制恢复。

8.2 性能考量

JavaScript 存储程序在 CPU 密集型计算场景下比 SQL/PSM 有显著的性能优势。Oracle 的 GraalVM 提供了 JIT 编译技术,而 Percona 的 V8 引擎同样具备高效的 JIT 能力。

对于大型代码库,LIBRARY 对象的设计确保代码只被处理和缓存一次,避免重复加载造成的内存膨胀和性能损耗。

8.3 会话隔离

每个 JavaScript 存储程序首次执行时,会与当前 MySQL 会话的时区关联,并在后续执行中持续使用该时区。这意味着在跨时区的环境中使用时需要特别注意。

8.4 安全性考量

JavaScript 代码在 GraalVM 的沙箱环境中运行,具备完善的隔离和安全机制。通过 MLE 组件执行的 JavaScript 存储程序,无法访问 MySQL 核心内存结构,也不会影响数据库的稳定运行。

九、实战案例:构建智能数据清洗管道

以下是一个完整的实战案例,展示如何利用 JavaScript 存储程序构建智能数据清洗管道:

-- 1. 创建数据清洗函数库
CREATE LIBRARY IF NOT EXISTS data_cleaner 
LANGUAGE JAVASCRIPT 
AS
$$
    // 邮箱验证
    export function isValidEmail(email) {
        const regex = /^[^s@]+@[^s@]+.[^s@]+$/;
        return regex.test(email);
    }

    // URL 规范化
    export function normalizeUrl(url) {
        try {
            const urlObj = new URL(url);
            return urlObj.hostname + urlObj.pathname;
        } catch (e) {
            return null;
        }
    }

    // 字符串去重和清理
    export function cleanTags(tagsJson) {
        if (!tagsJson || !Array.isArray(tagsJson)) return [];
        const cleaned = [...new Set(tagsJson.map(t => 
            t.trim().toLowerCase().replace(/[^a-z0-9]/g, '_')
        ))];
        return cleaned.filter(t => t.length > 0);
    }
$$;

-- 2. 创建数据验证函数
CREATE FUNCTION validate_user_data(
    email VARCHAR(255), 
    homepage VARCHAR(500)
)
RETURNS JSON
LANGUAGE JAVASCRIPT
USING data_cleaner
AS
$$
    return {
        email_valid: data_cleaner.isValidEmail(email),
        homepage_normalized: data_cleaner.normalizeUrl(homepage)
    };
$$;

-- 3. 在表上使用
CREATE TABLE users (
    id INT PRIMARY KEY,
    email VARCHAR(255),
    homepage VARCHAR(500),
    validation_result JSON 
        GENERATED ALWAYS AS (validate_user_data(email, homepage)) STORED
);

这个案例展示了 LIBRARY 模块化、数据验证、JSON 类型使用以及生成列(Generated Column)的综合应用。

十、限制与注意事项

限制项说明缓解方案企业版限制Oracle MLE 仅限企业版,社区版用户无法使用考虑 Percona Server 的开源实现复制要求拓扑中所有节点必须安装 MLE 组件升级前统一规划,停止复制后批量安装字符集限制字符串参数仅支持 utf8mb4 或 binary 字符集确保数据库默认字符集为 utf8mb4会话隔离存储程序会绑定创建时的时区跨时区使用时明确处理时区转换调试复杂度console.log 输出需要通过 mle_session_state 查看结合日志表记录关键调试信息性能调优大量 JavaScript 代码需关注编译开销使用 LIBRARY 缓存避免重复编译

十一、未来展望

11.1 版本路线图

MySQL 9.x 系列之后,将迎来 MySQL 10.0 创新版分支。JavaScript 能力将持续演进,预计将在以下方向深化:

  • 更多编程语言支持(如 Python、R 等)
  • WebAssembly(Wasm)的深度集成
  • 与 HeatWave 向量检索的更紧密联动

11.2 社区版开放趋势

在社区长期呼吁下,Oracle 承诺将部分企业版功能下放到社区版,包括向量函数、Hypergraph 优化器、JSON Duality Views 等。随着 9.7 LTS 的发布,Oracle 正逐步兑现这一承诺。虽然 JavaScript 存储程序尚未在开放列表中,但可以期待未来 JavaScript 能力也有可能进入社区版。

11.3 开源生态的演进

Percona 正在积极推动 MySQL 开源生态的发展。在 2026 年 FOSDEM 会议上,Percona 展示了基于 V8 引擎的 JS 存储程序实现。与此同时,社区发起了「MySQL 生态系统未来」的公开信讨论,寻求建立不依赖单一供应商的治理模式。这些努力有望让 JavaScript 存储程序能力惠及更广泛的 MySQL 用户。

11.4 开发体验的提升

随着 MySQL 9.7 LTS 的发布,MySQL 开发体验将得到进一步提升。Oracle 承诺将发布发展路线图,促进社区贡献,包括公开工作日志和错误报告,以及邀请社区共同构建下一代 MySQL 工具和扩展。这对于 JavaScript 存储程序的工具链建设(如 IDE 插件、调试工具、测试框架)具有重要意义。

十二、总结

本期我们全景解读了 MySQL 9.x 系列中的 JavaScript 存储程序与多语言引擎技术:

  • MLE 组件是 MySQL 企业版的核心组件,通过 GraalVM 运行时执行 JavaScript 代码,提供了 SQL/PSM 之外的编程能力
  • 数据类型支持覆盖了整数、字符串、JSON、DECIMAL、时间类型等主要 MySQL 类型,映射关系清晰自然
  • LIBRARY 对象实现了 JavaScript 代码的模块化管理,支持 ES 模块语法,可以复用 npm 生态
  • 事务 API、会话变量、国际化 API、动态加载等高级特性在 9.2/9.3 版本中持续完善
  • HeatWave GenAI 将 JavaScript 与 AI 能力深度融合,提供了 JavaScript GenAI API
  • Percona Server 提供了基于 V8 引擎的开源替代方案,将 JavaScript 存储程序能力带入开源领域
  • 生产环境部署需特别注意 MLE 组件的复制兼容性要求,拓扑中所有节点必须统一安装

JavaScript 存储程序的引入,标志着 MySQL 从传统的「数据存储工具」向「数据计算平台」的演进。它不仅降低了数据库编程的门槛,让超过 1700 万 JavaScript 开发者能够将现有技能应用到数据库端,更重要的是,它为在数据库内部实现复杂的数据处理逻辑提供了全新的可能性。

下一期预告:MySQL 新技术系列第四期——「MySQL 9.x 高可用架构进化:Group Replication 新特性与多源复制实践」。我们将深入探讨 9.3/9.7 LTS 中引入的智能主节点选举、自动驱逐与重加入机制,以及多源复制在现代化数据架构中的应用。敬请期待!
欢迎大家在评论区留言交流,告诉我你最想深入了解的主题,我会在后续系列中优先安排!

📚 参考资料

  • MySQL 9.6 Reference Manual - JavaScript Stored Programs
  • MySQL 9.6 Reference Manual - Multilingual Engine Component (MLE)
  • Oracle Blog: Modular Magic: Reusing Code in MySQL with the New LIBRARY Feature
  • Oracle Blog: Debugging JavaScript Stored Functions in MySQL
  • Percona Blog: JavaScript Stored Routines in Percona Server for MySQL
  • Percona Blog: Introducing Experimental Support for Stored Programs in JS in Percona Server for MySQL
  • The Register: Oracle vows 'new era' for MySQL as users sharpen their forks
  • MySQL HeatWave User Guide - GenAI Routines
  • MySQL 9.2.0 Release Notes
  • MySQL 9.3 Release Notes
  • MySQL 9.7 LTS Announcement

No comments yet