简介这份文档资料面向使用 MetaTrader 平台进行量化开发的程序员聚焦 MQL5MQL4无法直接与数据库交互这一痛点讲解如何借助接口库访问 MySQL 数据库。内容围绕 MQLMySQL.mqh、MQLMySQL.dll 与 libmysql.dll 三层结构展开涵盖连接打开与关闭、DML/DDL 查询执行、游标式数据检索等核心操作并对比了 MySQL 包装、MySQL 日志、EAX_Mysql 等既有方案的兼容性与内存管理缺陷同时给出兼容性、性能、错误处理与连接复用等选型建议。资源包为单个 doc 文档约 147KB适合具备一定 MQL 编程基础、希望实现价格历史存储、跨平台交易复制或远程账户监控的开发者参考。目前已有 227 人学习可帮助读者理解游标机制、连接超时重连等排错思路快速搭建可维护的数据库访问方案。1. 从 MQL5 访问 MySQL为什么这件事比你想的更值得做很多做 MT4/MT5 自动交易的朋友策略逻辑写得飞起订单管理也顺但一到「把交易记录、账户状态、指标快照存到外部数据库」就卡住了。MQL5 自带的文件写入和全局变量做单机小打小闹还行一旦要多终端汇总、要跟 Web 后台或报表系统对接、要做历史数据分析就完全不够用。这时候把 MySQL 接进来等于给 EA 装了一个真正的关系型数据库后端——增删改查、事务处理、并发锁、存储过程这些能力全都能用上。这篇笔记讲的就是怎么用 MQL5MQL4 思路一致通过libmysql.dll访问 MySQL从环境准备、DLL 导入、连接封装到参数配置和踩坑排查一步步走通。适合已经有 EA 开发基础、想把数据落到外部库的从业者。2. 打通链路MQL5 调 MySQL 的三种常见方案与选型2.1 为什么主流做法是 libmysql.dll 直连MQL5 本身没有内置 MySQL 客户端它对外部通信的支持主要靠三种途径WebRequest、文件交换、DLL 调用。WebRequest 需要中间层 HTTP 服务文件交换延迟高且并发差真正能做到「EA 里直接执行 SQL」的只有 DLL 导入这条路。而 MySQL 官方提供的 C 客户端库libmysql.dll正好暴露了标准的 C 接口MQL5 的#import机制可以直接加载它。常见做法是把 MySQL 安装目录下的libmysql.dll复制到 MT5 的MQL5/Libraries目录64 位终端要放 64 位 DLL然后在 MQL5 代码里导入mysql_init、mysql_real_connect、mysql_query、mysql_store_result、mysql_fetch_row等函数。这套接口稳定、文档全是社区里最成熟的方案。选它而不是自己写 socket 协议理由很直接MySQL 客户端协议涉及握手、认证插件、字符集协商、包分片自己实现等于重造轮子而且每次 MySQL 小版本升级都可能踩到协议细节。用官方 DLL这些脏活它替你干了。2.2 环境准备MySQL 装好、DLL 放对、位数匹配第一步是把 MySQL 服务端跑起来。Windows 上可以用安装版也可以下 ZIP 解压版手动配置。装完之后确认服务在跑用命令行能登进去mysql -u root -p -e SELECT VERSION();能返回版本号就说明服务端没问题。接着找libmysql.dll它通常在 MySQL 安装目录的lib或bin下。注意位数MT5 终端如果是 64 位现在基本都是就必须用 64 位的libmysql.dll32 位终端配 32 位 DLL混了会直接加载失败。把 DLL 复制到MT5数据目录/MQL5/Libraries/libmysql.dll数据目录可以在 MT5 里点「文件 → 打开数据文件夹」找到。放好后在 MQL5 里写导入声明#import libmysql.dll ulong mysql_init(ulong mysql); int mysql_real_connect(ulong mysql, string host, string user, string password, string db, uint port, string unix_socket, uint client_flag); int mysql_query(ulong mysql, string query); ulong mysql_store_result(ulong mysql); ulong mysql_fetch_row(ulong result); int mysql_num_fields(ulong result); ulong mysql_fetch_lengths(ulong result); int mysql_close(ulong mysql); string mysql_error(ulong mysql); #import这里有几个参数要说明。mysql_init传 0 会返回一个新句柄mysql_real_connect的host填127.0.0.1或localhostport默认 3306client_flag一般填 0mysql_fetch_row返回的是一个指向行数据的指针MQL5 里不能直接当字符串用需要配合mysql_fetch_lengths和内存读取函数来取字段值。这是整个方案里最容易翻车的地方后面避坑章节会细讲。2.3 最小可跑通示例连接、建表、插入一条记录先别急着封装大框架用最小代码验证链路通不通。下面这段在 MT5 的脚本里跑连上库、建一张表、插一条记录、查出来打印#import libmysql.dll ulong mysql_init(ulong mysql); int mysql_real_connect(ulong mysql, string host, string user, string password, string db, uint port, string unix_socket, uint client_flag); int mysql_query(ulong mysql, string query); ulong mysql_store_result(ulong mysql); ulong mysql_fetch_row(ulong result); int mysql_num_fields(ulong result); int mysql_close(ulong mysql); string mysql_error(ulong mysql); #import void OnStart() { ulong conn mysql_init(0); if(conn 0) { Print(mysql_init 失败); return; } // 连接本地 MySQL库名 testdb端口 3306 int ok mysql_real_connect(conn, 127.0.0.1, root, 你的密码, testdb, 3306, , 0); if(ok 0) { Print(连接失败: , mysql_error(conn)); mysql_close(conn); return; } Print(连接成功); // 建表如果不存在 string sql CREATE TABLE IF NOT EXISTS trade_log ( id INT AUTO_INCREMENT PRIMARY KEY, symbol VARCHAR(20), price DOUBLE, ts DATETIME); if(mysql_query(conn, sql) ! 0) Print(建表失败: , mysql_error(conn)); // 插入一条 sql INSERT INTO trade_log (symbol, price, ts) VALUES (EURUSD, 1.0850, NOW()); if(mysql_query(conn, sql) ! 0) Print(插入失败: , mysql_error(conn)); else Print(插入成功); mysql_close(conn); }逻辑说明mysql_init(0)拿到连接句柄mysql_real_connect返回非 0 表示成功mysql_query返回 0 表示执行成功非 0 要用mysql_error看原因。参数上host用127.0.0.1比localhost更稳避免走命名管道密码里如果有特殊字符注意 MQL5 字符串转义。这段跑通说明 DLL 加载、网络、认证、权限都没问题可以往下做封装了。3. 把访问封装成可复用模块连接池、查询与结果解析3.1 连接管理别每次查询都重连最小示例里每次操作都mysql_initmysql_real_connect在脚本里没问题但在 EA 的OnTick里这么干性能会崩。每次连接都要 TCP 握手、认证、字符集协商几十毫秒起步高频 tick 下根本扛不住。正确做法是维护一个长连接在OnInit里建立OnDeinit里关闭中间所有查询复用同一个句柄。如果担心连接被 MySQL 服务端超时断开默认wait_timeout是 8 小时可以在每次查询前用mysql_ping探活断了就重连。常见做法是封装一个CMySQLConn类class CMySQLConn { private: ulong m_conn; string m_host, m_user, m_pass, m_db; uint m_port; public: CMySQLConn(string host, string user, string pass, string db, uint port3306); bool Connect(); bool Query(string sql); void Close(); string LastError(); };Connect里做mysql_init和mysql_real_connectQuery里做mysql_query并判断返回值。这样 EA 里只持有类实例不用关心底层句柄。参数上wait_timeout可以在 MySQL 服务端调大但更稳的是客户端主动探活。3.2 结果集解析MQL5 里怎么把行数据取出来mysql_store_result返回结果集句柄mysql_fetch_row返回一行数据的指针。问题在于 MQL5 没有直接的指针解引用语法取字段值要靠mysql_fetch_lengths拿到每个字段的长度再用MemRead类函数从指针地址读内存。这是整个方案里最「玄学」的部分很多人卡在这里。常见做法是导入mysql_fetch_lengths和一个内存读取函数然后按字段偏移逐个取#import libmysql.dll ulong mysql_fetch_lengths(ulong result); // 用 ArrayFromPointer 或自定义内存读取 #import // 简化示意假设已拿到 row 指针和 lengths 数组 string GetFieldValue(ulong rowPtr, ulong lengthsPtr, int fieldIndex, int offset) { // 实际实现需要根据指针地址和长度做内存拷贝 // 这里只说明思路rowPtr 是字段指针数组lengthsPtr 是长度数组 return ; }参数说明mysql_fetch_row返回的指针指向一个char**数组每个元素是字段值的起始地址mysql_fetch_lengths返回unsigned long*数组对应每个字段的字节长度。取第 i 个字段就是读rowPtr[i]地址开始的lengths[i]个字节。MQL5 里可以用ArrayFromPointer或者导入memcpy来做。这块代码量不小建议直接找社区里成熟的mysql.mqh封装别自己从零写。3.3 参数化查询与字符集防注入和乱码拼接 SQL 字符串有两个坑一是注入风险二是中文乱码。EA 里虽然输入源相对可控但品种名、注释字段如果带特殊字符拼接就可能出错。更稳的做法是用mysql_real_escape_string对字符串做转义或者用预处理语句mysql_stmt_*系列但预处理在 MQL5 里封装成本更高多数人还是用转义。字符集方面连接建立后立刻执行SET NAMES utf8mb4;这样客户端和服务端协商用 utf8mb4中文、emoji 都不会乱。如果发现存进去的中文变成问号先查这一句有没有执行再查表和字段的字符集是不是 utf8mb4。参数上mysql_real_connect之后、任何业务查询之前这句必须跑。4. 避坑与排查MQL5 连 MySQL 最常见的 5 个翻车点4.1 现象DLL 加载失败报「cannot load library」原因位数不匹配或者 DLL 放错目录。64 位 MT5 配了 32 位libmysql.dll或者 DLL 放在了MQL5/Files而不是MQL5/Libraries。解决确认 MT5 终端位数帮助 → 关于下载对应位数的 MySQL 客户端库DLL 必须放MQL5/Libraries且文件名和#import里写的一致。如果还不行用 Dependency Walker 看 DLL 依赖是否齐全libmysql.dll可能依赖libssl、libcrypto等缺了也要一起放。4.2 现象连接返回失败mysql_error提示认证插件不支持原因MySQL 8.0 默认认证插件是caching_sha2_password而老版本libmysql.dll只支持mysql_native_password。解决要么把用户认证方式改成mysql_native_passwordALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;要么换用和 MySQL 8.0 配套的新版libmysql.dll。生产环境建议后者但要注意新版 DLL 的依赖更多。4.3 现象查询执行成功但取出来的字段值是乱码或空原因结果集解析时指针读取方式不对或者字符集没设对。解决先确认SET NAMES utf8mb4执行了再检查mysql_fetch_row返回的指针是否被正确解引用。MQL5 里指针操作容易出错建议用mysql_num_fields确认字段数用mysql_fetch_lengths确认每字段长度逐字段打印调试。如果长度对但内容是乱码基本是字符集问题如果长度是 0说明字段本身为空或指针偏移算错了。4.4 现象EA 跑一段时间后查询变慢甚至卡死原因长连接被服务端超时断开但客户端没探活每次查询都在等超时或者结果集没释放内存泄漏。解决每次查询前用mysql_ping探活断了就重连mysql_store_result拿到的结果集用完必须mysql_free_result释放。另外wait_timeout和interactive_timeout可以在 MySQL 配置里适当调大但客户端探活才是根本。4.5 现象多终端同时写入出现死锁或重复键原因并发写入时事务隔离和锁竞争或者自增主键冲突。解决EA 写入尽量用INSERT ... ON DUPLICATE KEY UPDATE或REPLACE INTO做幂等如果有多终端汇总需求考虑用队列表 单消费者模式或者给每个终端分配独立的表/库。事务处理上短事务优先别在事务里做网络等待。5. 进阶技巧用存储过程做批量写入与数据校验链路跑通、封装做好之后真正影响性能的是写入方式。逐条INSERT在 tick 级别数据量下会很慢常见优化是把批量写入逻辑放到 MySQL 存储过程里EA 只调一次。比如建一个存储过程接收品种、价格数组、时间戳数组在服务端循环插入DELIMITER // CREATE PROCEDURE batch_insert_trade( IN p_symbol VARCHAR(20), IN p_prices TEXT, IN p_ts TEXT ) BEGIN DECLARE i INT DEFAULT 1; DECLARE n INT; -- 这里用逗号分隔的字符串模拟数组实际可用 JSON 或临时表 SET n LENGTH(p_prices) - LENGTH(REPLACE(p_prices, ,, )) 1; WHILE i n DO INSERT INTO trade_log (symbol, price, ts) VALUES (p_symbol, SUBSTRING_INDEX(SUBSTRING_INDEX(p_prices, ,, i), ,, -1), NOW()); SET i i 1; END WHILE; END // DELIMITER ;EA 侧只需要拼一次调用string sql CALL batch_insert_trade(EURUSD, 1.0850,1.0851,1.0852, ); mysql_query(conn, sql);这样网络往返从 N 次降到 1 次服务端循环比客户端循环快得多。参数上p_prices用逗号分隔字符串是最简单的做法数据量大时建议改用 JSON 类型或临时表。验证方法插入后立刻SELECT COUNT(*)对比预期条数再用SELECT * FROM trade_log ORDER BY id DESC LIMIT 5看内容对不对。我自己的习惯是任何批量写入上线前先用小批量比如 10 条跑一遍确认条数和内容都对再放大到生产量级。这个后悔药吃过一次就再也不想省了。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?