1. 为什么要在鸿蒙上直连 MySQL——方案选型背后的考量
1.1 移动端直连数据库:适用场景与边界
先说一个很多人会问的问题:移动端到底该不该直接连数据库?常规的架构建议都是“客户端发 HTTP 请求到后端 API,后端再访问数据库”,但我们团队这次在鸿蒙项目的实际业务里,确实遇到了被迫直连 MySQL 的场景——一个内部工具型 App,需要在弱网环境下以极低延迟读取设备状态数据,临时搭建的后端服务还没就绪,而业务方要求先跑通移动端到数据库的完整链路。这种时候,用 Flutter 生态里的 mysql_dart 库在鸿蒙设备上直连 MySQL,就成了权衡后的最短路。
但直连并不等于“乱连”。我画一条边界给各位参考:如果你的应用是面向公网用户的,或者数据库里有敏感隐私数据,建议老老实实走后端代理;如果场景是内网工具、车间设备、现场运维这类可控网络环境,且对端到端延迟敏感,那么移动端直连是具备合理性的。mysql_dart 这个库选择直连方案,恰好能承担这个角色——它不依赖任何原生通道,走的就是 TCP 3306 端口。
核心原则:直连数据库的方案只适用于受控网络环境。公网 App 千万不要把 MySQL 暴露给客户端,这是底线。
1.2 为什么是 mysql_dart 而不是 HTTP 中转或 ORM
在调研阶段,我们其实对比过三条路线。第一条是架设轻量 HTTP 网关,请求走 JSON API 转发——这是最稳妥的做法,但多一跳网络延迟,而且服务端还得维护。第二条是用 objectbox、drift 这类本地数据库做缓存,再把数据同步到服务端,这种适合离线优先场景,但我们当时需要的是实时读取服务端 MySQL 里的最新台账,本地缓存帮不上忙。第三条就是今天的主角 mysql_dart。
选它有三个具体理由。第一,mysql_dart 纯 Dart 实现,底层只依赖 dart:io 的 Socket、SecureSocket 和 DNS 相关能力,这让它在鸿蒙 NEXT 这种非 Android、非 iOS 的第三方 Flutter 平台上有了被适配的基础——没有 C++ 原生代码要重编,没有 JNI 要重绑。第二,它实现了 MySQL 原生协议,从握手认证到 COM_QUERY、COM_STMT_PREPARE 都覆盖了,支持 SQL 预处理、事务、SSL 连接,这些对生产级别交互来说不可或缺。第三,包体积可控,没有把整个 JDBC 或 ODBC 那种重型实现搬过来,移动端集成成本低。
当然,mysql_dart 也有一些“原罪”需要正视:它社区维护活跃度不算高,对 MySQL 8.0 的 caching_sha2_password 认证插件支持是在后续版本才完善的,所以选版本时不能闭眼装最新版,要看 Release 记录里是否明确写了你所用 MySQL 版本的认证方式支持。我们这边数据库是 MySQL 8.0.28,用的 mysql_dart 版本是 0.8.0,实测握手没问题。
1.3 鸿蒙 NEXT 对 Flutter 生态的兼容现状
聊到鸿蒙适配,得先摊开一个现实:鸿蒙 NEXT(HarmonyOS NEXT)彻底移除了 Android 兼容层,以前那种“APK 直接跑在鸿蒙上”的路子走不通了。Flutter 在鸿蒙上的支持主要来自 OpenHarmony 社区的移植版本——通过适配后的 Flutter SDK 打鸿蒙包,再用 DevEco Studio 做应用打包和签名。这个链路最麻烦的地方在于,三方 Flutter 插件是否能在鸿蒙上跑,取决于它依赖的 dart:ui 和 dart:io 能力在鸿蒙运行时里是否被完整实现。
mysql_dart 恰好落在“依赖 dart:io 较浅”的区间里。它主要用到 Socket.connect、SecureSocket.connect、InternetAddress.lookup,以及基础的 Stream/Uint8List 字节操作。鸿蒙的开源 Flutter 移植分支已经把这些底层 API 映射到了鸿蒙的 socket 能力上,所以 mysql_dart 理论上不需要改一行 Dart 代码就能跑。但“理论”和“实测”之间永远有距离,后面我会把实际踩的坑一个个列出来。
2. 适配前置条件——环境搭建与依赖引入
2.1 Flutter 鸿蒙开发环境搭建要点
要在一个新平台上跑 Flutter 应用,环境永远是第一道坎。我们用的组合是:
- DevEco Studio 5.0(对应 API 12+)
- OpenHarmony 社区的 Flutter SDK(基于 Flutter 3.22 分支移植)
- 鸿蒙真机或模拟器(API 12 及以上)
- 项目构建工具 hvigor,配合 DevEco Studio 的 Gradle 同步机制
这里有个容易栽跟头的地方:OpenHarmony 的 Flutter SDK 和官方 Flutter SDK 不能混用。如果你机器上原本装的是官方 Flutter,执行 flutter doctor 时它会指向官方 SDK,这时候打的鸿蒙包会在引擎初始化阶段直接崩溃。我们的做法是把鸿蒙版 Flutter SDK 单独解压到ohos_flutter_sdk目录,然后用环境变量切换,或者干脆在项目根目录写一个.metadata指定 SDK 路径,避免团队里其他人拉到错误 SDK 编译出问题。
在鸿蒙上跑 Flutter 应用,还需要在ohos工程目录下做一层壳工程。流程大致是:先用 Flutter 命令生成标准 Flutter 工程,再用 DevEco Studio 打开生成的ohos文件夹,补上应用包名、签名配置,最后用 hvigor 打 HAP 包。整个过程不算复杂,但每一步都要求版本对齐,我建议把版本号钉死记录在 README 里,否则一个月后你大概率会忘记是哪组版本能跑通的。
2.2 引入 mysql_dart 依赖与版本选择
在pubspec.yaml里加入 mysql_dart 依赖这一步很简单,但版本选择有讲究。
dependencies: flutter: sdk: flutter mysql_dart: ^0.8.0这里有三个注意点。第一,mysql_dart 的 0.7.x 和 0.8.x 之间 API 有变动,特别是 ResultSet 的行取值方式,0.8.x 改成了result.rows[i].colByName('xxx')这种访问形式,早期版本是result[i]['xxx'],升级后如果不改代码会直接抛 NoSuchMethodError。第二,如果你连的 MySQL 是 8.0 以上版本,务必确认所用版本支持 caching_sha2_password,否则会卡在握手阶段报Auth plugin not supported。第三,要注意 mysql_dart 传递依赖的crypto包版本,鸿蒙 Flutter SDK 自带加密库实现,版本冲突可能导致 SHA256 握手签名计算出错。
依赖装完之后,先别急着写业务代码。我会习惯性地跑一个最小连接测试——在鸿蒙设备上写一行代码连到开发机 MySQL,能通再往深处做。这一步能节省大量排查时间,因为如果你把业务代码和连接逻辑混在一起写,出了问题压根不知道是网络不通、依赖冲突、还是业务代码的锅。
2.3 鸿蒙网络权限与明文传输配置
移动端直连数据库,网络安全配置是绕不开的一环。鸿蒙应用默认不允许任意 socket 连接,你必须在模块配置里显式声明网络权限。在 DevEco Studio 生成的module.json5里,要加上:
{ module: { requestPermissions: [ { name: "ohos.permission.INTERNET" } ] } }这里有一个非常重要的细节:鸿蒙对明文流量(非 TLS 加密的 TCP/UDP)有默认限制策略。mysql_dart 如果走明文 3306 端口,默认策略下可能直接连接失败或者收到SocketException: Permission denied。解决方式有两个:一是给 MySQL 配置 TLS 证书,让 mysql_dart 启用加密连接,这也是生产环境推荐的方案;二是在鸿蒙工程的网络安全配置里放开明文流量(仅限内网调试用,绝不建议拿到生产环境)。
我的建议是:开发联调阶段放开明文,发布版本强制 TLS。放开明文的方法是在ohos工程里找到 src/main/ets/entryability 相关的网络配置文件,或者直接查 DevEco Studio 中 Network Security Config 的设置项,把cleartextTrafficPermitted置为 true。等调通了,再切回 TLS 模式验证一遍,确保加密链路没被权限策略拦死。
3. mysql_dart 核心原理与鸿蒙适配改造
3.1 mysql_dart 的工作机制:从握手到查询
想要在鸿蒙上把一个纯 Dart 的 MySQL 客户端用好,最好先把它内部的工作机制摸清楚。mysql_dart 本质上是 MySQL 客户端协议的 Dart 实现,整个交互过程分四个阶段。
第一阶段是 TCP 连接建立。mysql_dart 通过Socket.connect(host, port)建立到 MySQL 服务器的 TCP 通道。第二阶段是握手认证。服务端先发送握手包,包含协议版本、认证插件名和随机盐值;客户端根据服务端指定的认证插件做响应,老版本 MySQL 用mysql_native_password,8.0 默认是caching_sha2_password。第三阶段是命令交互。认证通过后,客户端发送COM_QUERY(普通查询)或COM_STMT_PREPARE(预处理)指令,服务端返回结果集或影响行数。第四阶段是连接关闭或复用。
这个机制里,和鸿蒙适配关系最紧密的是前两个阶段。因为鸿蒙的 Socket 行为和 Android 有细微差异——比如 DNS 解析的时机、IPv6 回退策略、超时语义。mysql_dart 的ConnectionSettings里有一个resolveDns参数,默认走的是InternetAddress.lookup。在鸿蒙上如果遇到解析失败,可以手动换成InternetAddress(host, type: InternetAddressType.IPv4)绕过 DNS,这个技巧后面排查部分会专门讲。
3.2 鸿蒙下的 Socket 实现差异与适配点
直接说结论:mysql_dart 在鸿蒙 NEXT 上的主要适配点不在 Dart 业务代码,而在运行时依赖的 dart:io Socket 实现是否完整。OpenHarmony 的 Flutter 移植引擎实现了大部分 dart:io Socket API,但在几个细节上和标准实现有出入。
第一个差异是Socket.connect的超时行为。标准 Dart 实现里,Socket.connect(host, port, timeout: Duration(seconds: 5))超时后会抛出SocketException;但鸿蒙移植引擎早期版本对 timeout 参数支持得不好,超时事件不触发或者延迟很久才触发。这会导致连接 MySQL 时万一网络不通,客户端会“卡死”很久。解决办法是在业务层包一层Future.timeout,用自己的超时策略兜底。
第二个差异是SecureSocket.connect的证书校验。鸿蒙内置的证书信任库和 Android 不完全一致,如果你用自签名证书给 MySQL 开 TLS,可能会遇到HandshakeException: CERTIFICATE_VERIFY_FAILED,而且错误信息里不告诉你具体是哪个证书链断掉。这个问题的解法是给ConnectionSettings.useTLS配上自定义的sslOptions.certificates,手动传入你信任的根证书内容,绕过系统信任库的差异。
第三个差异是流式读取的 chunk 大小。mysql_dart 用socket.listen接收数据包,鸿蒙引擎的分片策略偶尔会把一个大包拆成多个小包到达。mysql_dart 内部自带协议帧缓冲,按理说能处理这个问题,但如果你的数据包特别大(比如一次查询返回几万行),还是有概率触发解析错位。我的经验是:生产环境查询务必分页,或者用游标式读取,不要指望客户端一次吞下百万行结果。
3.3 改造代码实战:连接、查询、事务
下面给出一套可以直接参考的最小可运行代码。先看连接与基础查询。
import 'package:mysql_dart/mysql_dart.dart'; Future<MySqlConnection> createConnection() async { final settings = ConnectionSettings( host: '10.10.10.5', // 内网 MySQL 地址 port: 3306, user: 'app_reader', password: 'your_password', db: 'iot_prod', // 生产环境务必启用 TLS useTLS: true, sslOptions: SslOptions( // 如果 MySQL 用的是受信任的 CA 签发证书,可留空走系统链 // 如果用自签名,在这里传入根证书内容 ), ); try { return await MySqlConnection.connect(settings).timeout( const Duration(seconds: 10), ); } on TimeoutException { throw '连接 MySQL 超时,请检查网络或防火墙'; } on MySqlException catch (e) { throw 'MySQL 连接失败: ${e.message}'; } }连接拿到手之后,就可以执行查询了。
Future<List<Map<String, dynamic>>> queryDeviceData( MySqlConnection conn, { required int siteId, int limit = 100, }) async { final result = await conn.query( 'SELECT id, device_name, temperature, updated_at ' 'FROM devices ' 'WHERE site_id = ? ' 'ORDER BY updated_at DESC ' 'LIMIT ?', [siteId, limit], // 参数绑定,杜绝 SQL 拼接 ); // 0.8.x 版本通过 rows 访问数据 return result.rows.map((row) { return { 'id': row.valByName('id'), 'deviceName': row.valByName('device_name'), 'temperature': row.valByName('temperature'), 'updatedAt': row.valByName('updated_at'), }; }).toList(); }注意这里用了参数化查询而非字符串拼接。mysql_dart 会把?占位符替换成转义后的参数值,这个过程中对单引号、反斜杠、空字符做了安全处理,能有效抵御常见 SQL 注入。我第一次用这个库的时候偷懒写过${siteId}直接拼 SQL,结果被测试组一个1 OR 1=1参数打脸。从那以后凡是动态参数一律走预处理,这是生产级代码的死规矩。
事务操作在 mysql_dart 里有专门的 API 支持。
Future<void> updateDeviceAndLog( MySqlConnection conn, { required int deviceId, required String newName, }) async { await conn.startTransaction(); try { await conn.execute( 'UPDATE devices SET device_name = ? WHERE id = ?', [newName, deviceId], ); await conn.execute( 'INSERT INTO operate_log (device_id, action) VALUES (?, ?)', [deviceId, 'rename_device'], ); await conn.commit(); } catch (e) { await conn.rollback(); rethrow; } }这套代码在鸿蒙真机上跑通后,我再强调一个容易忽略的细节:写操作必须注意返回的 affectedRows 和 insertId。mysql_dart 的execute返回的是Result对象,包含affectedRows和insertId字段,你在做数据一致性校验时会用得上。比如插入一条日志后,如果 insertId 一直是 0,说明你的表没有自增主键——这种问题在 Android 上不会暴露,但到了鸿蒙端因为日志上报模块的差异,可能变成诡异的“数据丢了吗”的崩溃现场。
4. 生产级 SQL 交互实战——安全防护与性能优化
4.1 防护 SQL 注入:参数化只是第一层
刚才提到了参数化查询,但必须说清楚:参数化只是防注入的第一道闸,不代表你可以放宽心。mysql_dart 的参数绑定机制能防止绝大多数“值注入”,但不能防止表名/字段名注入,因为表名和字段名不能作为绑定参数,你只能把它们拼进 SQL 字符串。这会带来一个隐患:如果业务上有动态排序字段、动态表名,而这些字段来自客户端请求,那就有被注入的风险。
我这边有个项目就遇到过类似的需求——用户在工具类 App 里可以选择按“温度”或“湿度”排序。最开始的实现是直接ORDER BY ${sortField},如果 sortField 被恶意传成id; DROP TABLE devices; --,整张表就没了。生产级的解法有两种:一是白名单映射,在代码里维护一个可排序字段的合法枚举,客户端传来的值必须经过映射才能拼进 SQL;二是用重命名别名,让外部传入的字段名不会直接对应真实表结构。
const _sortableFields = { 'temp': 'temperature', 'hum': 'humidity', }; String safeSortField(String userInput) { return _sortableFields[userInput] ?? 'temperature'; }这种“白名单映射”虽然土,但非常有效。我见过太多团队过度依赖 ORM 框架而忽略这类不起眼的拼凑点,结果就是测试环境 SQL 注入测试一打一个准。记住:参数化解决的是“值”,白名单解决的是“标识符”,两者缺一不可。
4.2 轻量连接池设计与实现
生产环境直连 MySQL,如果每个页面请求都新建一个MySqlConnection,MySQL 服务端的线程开销会很快被打满。移动端并发量虽不如服务端,但用户在列表页来回滑动时产生的瞬时并发还是不小。mysql_dart 本身不提供连接池,得自己做一个轻量的。
我的做法是写一个简单的连接池,核心是“空闲复用 + 最小并发控制”。大概思路:
class MySqlPool { final List<MySqlConnection> _idle = []; final int maxSize; final ConnectionSettings settings; MySqlPool({ required this.settings, this.maxSize = 5, }); Future<MySqlConnection> acquire() async { if (_idle.isNotEmpty) { return _idle.removeLast(); } if (_inUse >= maxSize) { throw '连接池已满,请稍后重试'; } _inUse++; try { return await MySqlConnection.connect(settings); } catch (_) { _inUse--; rethrow; } } void release(MySqlConnection conn) { if (conn.isClosed) { _inUse--; return; } _idle.add(conn); } }这里有几个注意点。第一个是连接空闲检测:MySQL 服务端有wait_timeout默认 8 小时,空闲太久的连接会被服务端主动断开,但从客户端角度看 Socket 还是活的。从池里拿出的连接可能已经失效,执行第一条 SQL 时才报MySQL server has gone away。所以 acquire 时最好做一次心跳检查,最简单的实现是SELECT 1,成本极低。第二个是池上限要和 MySQL 的max_connections匹配,不要开 50 个客户端各建 10 连接,把服务端撑爆。移动端 App 场景,连接池保持 3~5 个就足够。
4.3 慢查询定位与网络层面优化
直连 MySQL 有一个好处:你可以直接在手机上观测 SQL 的耗时分布。mysql_dart 的ResultSet对象在查询完成后可以用Duration计时,但更精细的耗时定位得靠自己在业务层打点。
我习惯在查询语句外层做三层计时:第一层是业务侧发起查询到拿到结果的整段时间;第二层是连接到获取连接(如果走连接池);第三层是 SQL 真正执行的时间(可以在 MySQL 侧开slow_query_log配合检查)。某次我在鸿蒙设备上排查一个诡异问题——同一个 SQL 在电脑上用 Navicat 执行只要 30ms,在鸿蒙 App 里跑要 2 秒。最后定位到根本不是 SQL 慢,而是鸿蒙设备的网络协议栈和 IPv6 有关:MySQL 主机配置了 IPv6 地址,InternetAddress.lookup优先解析到了 IPv6,但路由器/AP 对 IPv6 转发支持不好,走了回退超时。解决方式很简单,在ConnectionSettings里改成强制 IPv4:
final settings = ConnectionSettings( host: '10.10.10.5', port: 3306, // 直接给 IPv4 地址,避免 lookup 解析到 IPv6 resolveDns: false, );还有一个优化点是查询字段的裁剪。移动端内存宝贵,直连查询时如果SELECT *把几百个字节的日志字段全拉回来,MySQL 到手机的带宽和解析时间都会浪费。生产级写法是明确列出需要的字段,同时用LIMIT控制结果集大小,配合ORDER BY和索引解决排序。这个规则虽然简单,但我在 code review 里看到太多SELECT *了——尤其在移动端直连场景,它不只是规范问题,是实打实的性能问题。
5. 常见问题排查与踩坑实录
5.1 鸿蒙编译与运行报错速查表
适配过程中,我把遇到的高频报错整理成了一张表,按“报错信息 → 原因 → 解法”三列列出来:
| 报错信息 | 常见原因 | 解决办法 |
|---|---|---|
SocketException: Permission denied | 鸿蒙应用未声明 INTERNET 权限,或明文流量被拦 | 在 module.json5 中加ohos.permission.INTERNET;调试期放行明文流量 |
MySqlException: Auth plugin not supported | mysql_dart 版本不支持 MySQL 8.0 的 caching_sha2_password | 升级 mysql_dart 到 0.8.0+,或改 MySQL 用户插件为 mysql_native_password(仅限兼容场景) |
HandshakeException: CERTIFICATE_VERIFY_FAILED | 鸿蒙系统信任库不包含 MySQL 所用证书链 | 在 SslOptions 里加载自签名证书或指定 CA 根证书 |
Connection timed out | 网络不通、防火墙拦 3306、IPv6 解析回退慢 | 用 ping/nc 定位于主机可达性;强制 IPv4 或用 timeout 兜底 |
FormatException: Unexpected packet | TCP 分片/粘包解析失败,常见于超大结果集 | 分页查询、控制单次返回行数;避免流式中途断连 |
NoSuchMethodError: colByName | mysql_dart 版本 API 不匹配(旧 API 是下标访问) | 统一版本,按 0.8.x 的 ResultRow API 改写取值逻辑 |
这张表解决了我大半的鸿蒙适配问题。其中很多报错信息看起来像“网络不通”,实际是权限配置或版本兼容的问题——排查时不要贸然改一大片代码,先按表里最可能的项快速试,能省出大量时间。
5.2 排查实录:TLS 握手失败、连接超时与内存抖动
挑两个印象最深的排查过程说说。
第一是 TLS 握手失败。鸿蒙设备 A 能连,设备 B 连不上,报错都是HandshakeException。两边固件版本不一样,鸿蒙的证书信任库版本也有差异。我当时的排查思路是抓包看 TLS 握手内容,对比两台设备的 ClientHello 支持套件列表——发现设备 B 不信任某个中间 CA。最后方案是在sslOptions.certificates里显式传入完整链的根证书,问题立刻消失。这件事给我一个教训:鸿蒙上的系统信任链和开发者预期可能不同,凡是涉 TLS 的联调,务必把证书链信息打印出来核对,不要靠“看起来应该通”的直觉。
第二是连接超时。现象是应用退到后台再回到前台后,第一次查询必超时。原因是鸿蒙对后台 App 的网络策略比较激进,App 挂后台后 socket 被系统回收,但 mysql_dart 连接池里的连接对象不知道已经失效,再次复用时就卡在已断开的 socket 上。我们的解法是在AppLifecycleListener里监听页面回到前台,强制清空连接池,让所有连接重新建立。这个修复只加了十行代码,却解决了线上最常见的超时反馈。
另外补充一个内存相关的经验:mysql_dart 查询大结果集时,ResultSet会把所有行缓存在内存里。我在鸿蒙上跑过一个返回 50 万行的查询,结果应用直接 OOM 崩溃。后来改成流式遍历——mysql_dart 0.8.x 支持MySqlConnection.startStreamingQuery,可以用 Stream 方式逐行消费。如果你的业务确实有大结果集需求,别犹豫,直接上流式方案。
5.3 针对鸿蒙 Flutter 工程的独家避坑技巧
最后分享几个只有实际在鸿蒙上跑过 mysql_dart 才容易踩中的细节。
第一个是关于part文件的。Flutter 项目里有些人喜欢用part 'xx.dart'组织代码。在 Android 平台这没问题,但在鸿蒙的 Flutter 编译链路里,part 文件如果没被正确包含,编译期可能报“undefined class”之类的怪错。排查技巧是检查part指令对应的文件路径是否和实际文件名一致,注意大小写,鸿蒙侧的文件系统对路径匹配比 Android 更严格。所以我个人建议在鸿蒙工程里尽量少用part,公共类用import足够清晰。
第二个是关于 EventChannel 和 MethodChannel 的干扰。鸿蒙 Flutter 应用里如果有原生与 Flutter 双向通信的需求(比如调用鸿蒙原生能力),channel 的注册时机要小心。mysql_dart 的初始化如果和 channel 注册同时在 main 函数里启动,可能会因为渲染线程抢占导致连接建立慢几十毫秒。这听起来不是大问题,但在低端鸿蒙设备上,这几十毫秒会被用户感知为“卡一下”。我的建议是:把 mysql_dart 的初始化放到真正需要首屏数据之后,用懒加载方式触发连接建立。
第三个是关于 Gradle 插件版本的。很多人觉得 Flutter 工程里settings.gradle里的插件版本不影响鸿蒙编译,这是误区。如果你在android/app/build.gradle里配了旧版的 Gradle 插件,DevEco Studio 在同步时可能把鸿蒙壳工程也带崩。我们曾遇到过“鸿蒙 HAP 打包失败,报 Gradle 插件不兼容”的问题,最后发现是 flutter 主工程的 Android 配置里的插件版本太老,影响了 Ohos 构建。处理方式是保证 Flutter 官方 Android Gradle 插件和 DevEco 侧 hvigor 版本都尽量新且兼容。
第四个是关于日志与调试。鸿蒙设备上跑 Flutter 应用,日志输出有时会有延迟,尤其是print和debugPrint。如果你用 mysql_dart 的 SQL 日志排查问题,建议直接落盘或者通过 channel 上报到ohos原生日志系统,不要依赖 Flutter 控制台输出。我遇到过日志漏打导致误判“SQL 没执行”的窘境,加上文件日志后才发现 SQL 执行成功但返回的数据被业务层异常吞掉了。
结束语:直连 MySQL 之后的下一步
按我个人的实践体会,mysql_dart 在鸿蒙上跑通只是起点,真正难的是后续维护和扩展。当你的 Flutter 鸿蒙应用真的开始直连 MySQL,第一件事是监控 MySQL 服务端的连接数和慢查询日志——移动端直连的流量特征和服务端 API 完全不同,突发性更强,连接生命周期更短,会给数据库的 connection pool 层带来额外压力。我建议在 MySQL 侧把max_connections调高一些,同时设置wait_timeout缩短到 60 秒左右,让失效连接快速回收。
如果后续要扩展功能,可以考虑在这个基础上封装一个轻量 ORM,把表结构和 Dart 模型做映射,减少手写 SQL 的重复劳动。但记住,封装的厚度一定要克制——集成越多抽象层,鸿蒙的编译和运行就越容易踩到兼容问题。mysql_dart 本身已经帮你完成了最复杂的协议部分,剩下的事,七分靠规范,三分靠经验。希望这篇指南能让你少走几步弯路,踩过的坑就别再踩了。