已开启
Add node-mysql2 development guide and TOC entry #8894
Add node-mysql2 development guide and TOC entry #8894
已开启
gcw_yAyk0TI9创建于 27 天前
共 2 个文件变更+211-0
@@ -27,3 +27,5 @@ sections:
27 href: ./django_development.md27 href: ./django_development.md
28 - label: 基于PyMySQL开发28 - label: 基于PyMySQL开发
29 href: ./pymysql_development.md29 href: ./pymysql_development.md
30+ - label: 基于node-mysql2开发
31+ href: ./node_mysql2_development.md
@@ -0,0 +1,209 @@
1+# node-mysql2 连接示例
2+ 
3+node-mysql2 可以通过 Dolphin 的 MySQL 协议连接 openGauss B 兼容模式数据库,使用 Promise、callback、文本查询、服务端预处理和连接池接口。
4+ 
5+openGauss 的连接名称有两个层次:`dolphin.default_database_name` 指物理 B 库,mysql2 的 `database` 参数指该 B 库中的 schema。本指南中的 `database` 均按 schema 说明。
6+ 
7+## 准备工作
8+ 
9+### 配置数据库
10+ 
11+先完成[配置 MySQL 协议兼容](./mysql_protocol_compatibility_config.md)。本例使用物理 B 库 `issue39`、schema `issue39`、用户 `issue39_app`。将占位口令替换为实际口令,不要把口令保存到公开代码或日志。
12+ 
13+通过 gsql 管理连接创建应用账号、B 库及 schema:
14+ 
15+```sql
16+CREATE USER issue39_app WITH PASSWORD '<应用口令>';
17+CREATE DATABASE issue39 OWNER issue39_app DBCOMPATIBILITY 'B';
18+\c issue39
19+SELECT set_native_password('issue39_app', '<应用口令>', '');
20+CREATE SCHEMA issue39 AUTHORIZATION issue39_app;
21+ALTER DATABASE issue39 SET standard_conforming_strings = off;
22+```
23+ 
24+按协议配置文档设置以下参数,并重启使协议监听配置生效:
25+ 
26+```sql
27+ALTER SYSTEM SET enable_dolphin_proto TO on;
28+ALTER SYSTEM SET dolphin_server_port TO 3308;
29+ALTER SYSTEM SET dolphin.default_database_name TO issue39;
30+```
31+ 
32+`dolphin_server_port` 应与 openGauss 原生端口不同。应用连接 MySQL 协议监听端口。本轮真实验证的本机容器曾将该端口映射为 `127.0.0.1:13379`,这是本轮映射值,不是 openGauss 的固定端口;部署时请改为实际地址和端口。
33+ 
34+mysql2 的 `query(sql, values)` 在客户端生成带反斜杠转义的 SQL 文本,需要 `standard_conforming_strings=off`,并且不能启用 `NO_BACKSLASH_ESCAPES`。新连接才会读取上述数据库默认值。`execute(sql, values)` 使用服务端预处理,不经过这一文本转义过程。
35+ 
36+### 大小写与 schema 选库边界
37+ 
38+`dolphin.lower_case_table_names` 是 Dolphin 的 USERSET 参数。应用应为同一组选库请求保持一致的设置,并在新连接中确认实际值。当前适配路径的语义如下:
39+ 
40+| 值 | 选库语义 |
41+| --- | --- |
42+| `0` | 大小写敏感。协议握手和 `COM_INIT_DB` 按输入的字面大小写查找;例如 `Issue39` 与 `issue39` 是不同请求。模式 `0` 下,真实存在且字面名称为 `$USER` 的 schema 可以被协议选中。 |
43+| `1` | 大小写不敏感。协议选库目标先按 Dolphin 现有 lowercase 规则归一化,再查找并设置有效 schema。 |
44+| `2` | 在本适配的协议选库路径中与 `1` 一样大小写不敏感,并按 Dolphin 现有 lowercase 规则归一化;不要据此推断完整 MySQL 的存储和比较语义。 |
45+ 
46+模式 `1` 和 `2` 使用既有 Dolphin 的普通 ASCII lowercase 行为,不等同于完整 Unicode case fold,也不覆盖全部 MySQL 命名兼容规则。SQL `USE` 仍按 SQL 引用和既有系统名规则解析;模式 `0` 不表示所有 SQL 系统名都无条件按字面值处理。
47+ 
48+数据库选库目标必须在有效大小写归一化后同时满足以下条件:schema 已存在、当前用户具有 `USAGE` 权限,并且不是选库路径不支持的特殊或临时 namespace。选库失败时不会回退到其他 schema,也不会以有效 schema 为 `NULL` 返回成功。
49+ 
50+schema 名受 openGauss 63 字节标识符上限约束。
51+ 
52+- `$user`、`pg_temp` 和已经存在的真实临时 namespace 均不支持作为数据库选库目标;真实临时 namespace 包括由服务端 API 识别为 temp 或 toast 的 namespace。此类请求返回 MySQL errno `1235`、SQLSTATE `42000`。
53+- 模式 `0` 下,协议输入按字面匹配:真实存在的 `$USER` 可以选择;字面 `$user` 仍是特殊名称,会被拒绝。模式 `1` 和 `2` 会把 `$USER` 归一化为 `$user`,因此也会被拒绝。这不是任意大小写都无条件拒绝。
54+- 目标不存在时返回 errno `1049`、SQLSTATE `42000`;没有 `USAGE` 权限时返回 errno `1044`、SQLSTATE `42000`。
55+- `COM_INIT_DB` 的 payload 是一个 schema 标识符,逗号属于名称的一部分,不表示名称列表。空 payload、包含 NUL 或超过 openGauss 63 字节标识符限制时,返回 errno `1102`、SQLSTATE `42000`。
56+- SQL `USE` 中的反引号按 SQL 标识符规则转义,标识符内部的反引号写成两个连续反引号:
57+ 
58+ ```sql
59+ USE `schema``with``backtick`;
60+ ```
61+ 
62+原生 `SET search_path` 仍按 openGauss 的规则处理 `$user` 和 `pg_temp` 等特殊项。上面的限制描述的是当前 Dolphin 适配层的握手、`COM_INIT_DB` 和 `USE` 选库路径。
63+ 
64+如果应用需要大小写不敏感的选库行为,可以按角色设置 Dolphin 参数,再建立新连接:
65+ 
66+```sql
67+ALTER ROLE issue39_app SET dolphin.lower_case_table_names = 1;
68+```
69+ 
70+### 安装驱动
71+ 
72+本轮验证使用 Node.js 24.19.0、mysql2 3.24.4;测试工程要求 Node.js 22 及以上。
73+ 
74+```shell
75+npm install --save-exact mysql2@3.24.4
76+```
77+ 
78+复现测试工程时使用其 `package-lock.json` 并运行 `npm ci --ignore-scripts`。
79+ 
80+## 连接和参数查询
81+ 
82+保存以下代码为 `connect.mjs`。环境变量 `OG39_HOST`、`OG39_PORT`、`OG39_USER`、`OG39_DATABASE` 和 `OG39_PASSWORD` 必须由运行环境提供。本例使用小写 schema `issue39`,因此三种模式下都使用同一成功选库名称。
83+ 
84+```javascript
85+import mysql from 'mysql2/promise';
86+ 
87+const required = ['OG39_HOST', 'OG39_PORT', 'OG39_USER', 'OG39_DATABASE', 'OG39_PASSWORD'];
88+for (const key of required) {
89+ if (!process.env[key]) throw new Error(`${key} is required`);
90+}
91+const port = Number(process.env.OG39_PORT);
92+if (!Number.isInteger(port) || port < 1 || port > 65535) {
93+ throw new Error('OG39_PORT must be an integer from 1 to 65535');
94+}
95+ 
96+const config = {
97+ host: process.env.OG39_HOST,
98+ port,
99+ user: process.env.OG39_USER,
100+ password: process.env.OG39_PASSWORD,
101+ database: process.env.OG39_DATABASE,
102+ charset: 'utf8mb4',
103+ connectTimeout: 5000,
104+ supportBigNumbers: true,
105+ bigNumberStrings: true,
106+ decimalNumbers: false,
107+ dateStrings: true,
108+ timezone: 'Z',
109+ multipleStatements: false,
110+};
111+const connection = await mysql.createConnection(config);
112+ 
113+let operationError;
114+try {
115+ const [[identity]] = await connection.query(
116+ 'SELECT DATABASE() AS db, CURRENT_USER() AS usr'
117+ );
118+ if (identity.db !== config.database) {
119+ throw new Error(`Unexpected schema: ${identity.db}`);
120+ }
121+ const [rows] = await connection.execute(
122+ 'SELECT CAST(? AS SIGNED) AS value, ? AS note',
123+ ['9007199254740993', "中文 ' 参数"]
124+ );
125+ console.log({ identity, rows });
126+} catch (error) {
127+ operationError = error;
128+}
129+ 
130+let closeError;
131+try {
132+ closeError = await connection.end();
133+} catch (error) {
134+ closeError = error;
135+}
136+if (operationError && closeError) {
137+ throw new AggregateError(
138+ [operationError, closeError],
139+ 'Operation and connection close failed'
140+ );
141+}
142+if (operationError) throw operationError;
143+if (closeError) throw closeError;
144+```
145+ 
146+`query` 和 `execute` 均返回 `[rows, fields]`。DML 的第一项是执行结果头,可读取 `affectedRows`、`insertId`;字段名等元数据位于 `fields`。mysql2 3.24.4 的 Promise 连接 `end()` 返回 Promise,完成值可能是 `Error`;应先保留主体错误,再检查关闭结果,主体和关闭都失败时用 `AggregateError` 同时保留两者。不要用空 `catch` 吞掉业务异常。
147+ 
148+`BIGINT` 和 `DECIMAL` 采用字符串保持精度。不要先把大整数或高精度小数转换为 JavaScript `Number`。无类型上下文的 `SELECT ?` 在不同服务器上可能返回不同字段类型;需要稳定类型时使用明确的 SQL cast 或表列类型。
149+ 
150+## 事务和连接池
151+ 
152+事务内所有语句必须使用同一条借出的连接。提交或回滚完成后再归还连接;默认归还连接不会清除未提交事务。
153+ 
154+```javascript
155+const pool = mysql.createPool({ ...config, connectionLimit: 2 });
156+let borrowedConnection;
157+try {
158+ borrowedConnection = await pool.getConnection();
159+ await borrowedConnection.beginTransaction();
160+ try {
161+ await borrowedConnection.execute(
162+ 'INSERT INTO example_table (id, note) VALUES (?, ?)',
163+ [1, '事务写入']
164+ );
165+ await borrowedConnection.commit();
166+ } catch (error) {
167+ try {
168+ await borrowedConnection.rollback();
169+ } catch (rollbackError) {
170+ throw new AggregateError(
171+ [error, rollbackError],
172+ 'Transaction and rollback failed'
173+ );
174+ }
175+ throw error;
176+ }
177+} finally {
178+ borrowedConnection?.release();
179+ await pool.end();
180+}
181+```
182+ 
183+其中 `config` 使用上一节连接配置,`example_table` 由应用预先创建。完整可运行版本见 examples 仓库 `NodeMysql2ConnectOpenGaussB/scripts/demo.mjs`,包含建表、预处理写入、精确小数读取、事务、连接池与清理。
184+ 
185+## 数据类型和版本边界
186+ 
187+| 项目 | 建议与已验证行为 |
188+| --- | --- |
189+| 二进制 | 传入 `Buffer`;回读用 `Buffer.equals()` 或逐字节比较;空 Buffer 与 NULL 分别处理 |
190+| 时间 | `dateStrings:true` 返回字符串;验证覆盖 DATE、DATETIME(0)、DATETIME(3),业务应明确存储时区 |
191+| 元数据 | 默认 `FOUND_ROWS` 下同值 UPDATE 返回匹配行数;生成主键应将 `insertId` 与存储值比对 |
192+| 错误 | 保留 `code`、`errno`、`sqlState`,不要记录口令;事务错误后显式 rollback |
193+| 资源 | 直连使用 `end()`,池连接用 `release()`,程序退出时 `pool.end()` |
194+ 
195+本轮本地验证的身份和范围如下:
196+ 
197+- 产品候选 tree:`3f504baa24e9fa4de731646dc4cc81413bbb169a`。
198+- 测试使用的 server master snapshot:`c4f7748e4f264c4141f42710b7dc8d40eb68ed42`;Plugin master snapshot:`7882a8a00900572789d7022f753cf1bf6f6b4b04`。
199+- openGauss 8.0.0-RC1 debug build,openEuler 20.03 LTS-SP4 x86_64,Node.js 24.19.0,mysql2 3.24.4。
200+- 45/45 冒烟、209/209 选库安全矩阵、65/65 既有回归和 10/10 独立边界检查通过,清理通过。验证容器在完成后已停止。
201+ 
202+上述结果是单一本地 TCP 环境和该 server/Plugin 候选的验证记录。7.0.0-LTS 发行包、aarch64、分布式 PGXC、TLS、长期压力、正式社区 CI 和导师验收均未验证;本地结果不替代这些环境、官方 CI、PR 合入或导师验收。
203+ 
204+## 参考
205+ 
206+- [MySQL2 官方文档](https://sidorares.github.io/node-mysql2/docs)
207+- [mysql2 3.24.4 源码](https://github.com/sidorares/node-mysql2/tree/v3.24.4)
208+- [openGauss MySQL 协议兼容](https://docs.opengauss.org/zh/docs/latest/extension_reference/dolphin_mysql_protocol_compatibility.html)
209+- [Dolphin GUC 参数](https://docs.opengauss.org/zh/docs/latest/extension_reference/dolphin_guc_parameters.html)