已开启
Add node-mysql2 development guide and TOC entry #8894
gcw_yAyk0TI9创建于 27 天前
Add node-mysql2 development guide and TOC entry #8894
已开启
共 2 个文件变更+211-0
| @@ -27,3 +27,5 @@ sections: | |||
| 27 | href: ./django_development.md | 27 | href: ./django_development.md |
| 28 | - label: 基于PyMySQL开发 | 28 | - label: 基于PyMySQL开发 |
| 29 | href: ./pymysql_development.md | 29 | 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) | ||