简介:MySQL Data Access Components (MyDAC) v5.10 源文件包,是一套面向 Delphi 7 至 2007 及 C++Builder 开发者的数据库连接组件源码,旨在帮助快速构建与 MySQL 直接通信的高性能应用,无需额外中间件。组件支持事务、批处理、存储过程及异步操作,并兼容触发器、视图等 MySQL 特性,可满足复杂业务场景。整套资源共 1403 个文件,压缩后仅 1.39MB;其中 360 个 Pascal 单元文件构成核心逻辑,146 个 Delphi 包定义用于组件封装,还包含工程文件、窗体定义、资源脚本、配置文件及不同编译器下的构建脚本,可覆盖从源码编译到安装使用的完整流程。文档方面,附带帮助索引、历史更新、常见问题解答与许可协议,便于了解组件用法、版本演进和授权边界。目前已有 168 人学习下载,适合需要研究 MyDAC 底层访问机制、事务处理与异步操作实现,或有意在项目中定制数据访问层的 Delphi/C++Builder 开发者。借助完整源码,可深入剖析连接池、批量操作、存储过程调用等核心特性,并依据自身需求裁剪或扩展组件,提升开发效率与项目可控性。
1. MyDAC v5.10 源码包:绕开 ODBC 直连 MySQL 的桌面端方案
做 MySQL 桌面客户端的人,多多少少都被 ODBC 驱动坑过:32 位和 64 位装错、目标机器缺运行库、DBA 没开远程权限,程序一换环境就翻车。MyDAC 走的是另一条路,它直接把 MySQL 的客户端-服务器协议做进了组件层,从套接字层面建立连接,客户端机器上不需要额外安装驱动,也没有 ODBC 版本冲突这类玄学问题。v5.10 源码包的价值不只是在“能连上”,而是把协议握手、认证过程、结果集读写都摊开在你面前,适合维护 Delphi 老工程的开发者,也适合想研究直连原理再做二次封装的人。下面这篇我就按拆包落地的顺序写:源码目录怎么分、编译先做什么、核心参数怎么调、容易翻车的地方在哪。
2. 拆源码目录结构:直连协议层和组件层的分工在哪里
拿到源码包之后,我劝你先别急着双击工程文件。源码包不是安装版,它由几十个单元文件组成,并且有明显的分层依赖。先看清楚目录边界,再动手编译,否则你会被一堆“Unit not found”的报错来回折磨。
2.1 源码分区:先从四个目录认清依赖顺序
MyDAC v5.10 这类源码包,结构上通常能分成四个区域:核心协议层、组件封装层、设计期注册层、示例工程目录。我一般会先按这个维度把所有.pas文件归类,而不是直接去翻组件面板。
| 源码分区 | 这里面有什么 | 改动后影响范围 |
|---|---|---|
| Core 核心协议层 | 协议解析、握手认证、Socket 读写、缓存与压缩逻辑 | 影响所有连接和数据集组件,编译优先级最高 |
| Components 组件封装层 | 连接管理、查询、脚本执行、元数据读取、备份恢复组件 | 日常二次开发主要改这里,逻辑与界面相关 |
| Design 设计期注册层 | 组件图标、字段编辑器、连接向导、属性面板关联 | 只影响 IDE 设计期体验,不影响运行期 |
| Demos 示例工程 | 连接示例、批量导入、二进制数据处理、备份恢复脚本 | 验证和参考用,不参与发布编译 |
编译顺序为什么必须是“Core -> Components -> Design”?因为 Pascal 单元之间有强依赖,组件封装层会在 uses 列表里引用核心层单元。反过来编的话,编译器找不到最底层定义的类,报错信息往往又是“找不到某个dcu”而不是直接告诉你是依赖顺序错了。
实际打开源码包时,我建议你先找到类似 MyClasses、MyAccess、MyQuery、MyScript 这些单元文件,用文本编辑器扫一眼它们的 uses 子句。你会发现,越靠近连接底层的单元被使用的次数最多,这些就是整个包的“地基”。
2.2 直连引擎做的事:握手、认证、命令执行和结果集读取
MyDAC 之所以敢说“不用装 MySQL 客户端”,是因为它在源码里自己实现了一套 MySQL 网络协议。连接建立后,它做的事情可以归纳为四步:
第一,建立 TCP 连接,向 MySQL 服务端发送客户端能力标志位,读取服务端的握手初始化包。第二步,根据服务端返回的随机数做密码摘要计算,完成认证请求,这一步源码里通常对应一个专门的密码加密函数,关注它是理解整个认证流程的关键。第三步,认证通过后,把 SQL 文本包装成协议命令包发出去,例如 COM_QUERY 或预处理语句的 COM_STMT_PREPARE。第四步,读取服务端返回的结果集数据包,按照列定义解析字段类型,再逐行填充给数据集组件。
理解了这四个步骤,你再看源码里的参数设置,思路就清晰多了。比如Compression参数,它在建连阶段就要协商好,握手成功后传输的数据包都会被压缩和解压。Timeout参数则控制的是等待响应包的时长,如果某个环节一直没有收到服务端数据包,组件就会按超时时间中断操作。
我把这套逻辑里最常用的连接参数整理成了一张表,后面调连接问题时可以对着检查:
| 参数名 | 作用 | 常用设置 |
|---|---|---|
| Server | 目标 MySQL 主机地址 | 192.168.1.20,填域名也可以 |
| Port | 服务端监听端口 | 3306 或自定义端口 |
| Protocol | 连接协议类型 | mpTCP 为远程 TCP/IP,mpLocal 为本地套接字 |
| Charset | 客户端字符集 | utf8mb4,中文环境必配 |
| Compression | 是否启用协议压缩 | True 适合低带宽网络,False 追求 CPU 性能 |
| ConnectTimeout | 建连阶段超时秒数 | 局域网设 5~10 足够,跨网段设 30 |
| CommandTimeout | 单条命令执行超时秒数 | 长查询设 60 以上 |
2.3 为什么选源码版:可维护性高,但代价是必须自己跟进更新
源码包和安装版最大的区别在于:你能改,但不改也能用。安装版把编译好的 dcu 和运行库给你,出问题时你只能说一句“它就是这么工作的”。源码版则可以把调试器停在认证函数里,看到底是服务端拒绝了请求,还是自己的密码摘要算法算错了。
不过选源码包也有代价。IDE 升级一次,你就要把整个包重新编译一遍,所有 Delphi 版本之间的编译器兼容性问题都会暴露出来。更麻烦的是,Lazarus 和 Delphi 的 RTL 有一定的差异,源码包在两种环境下编译,个别单位可能遇到条件编译指令不匹配的问题。所以我建议,如果你只是想在工程里“拖个控件、连个库”,完全没必要折腾源码包;但如果你是要集成到自己的框架里,或者要应对 MySQL 5.x 到 8.x 的认证方式变化,源码版就是那粒后悔药,出了问题能下手去查,而不是干瞪眼。
3. 编译与接入:从源码包到第一条参数化查询
源码包的落地流程,我建议按“先编译运行期包 -> 再注册设计期包 -> 最后在业务工程里写代码”的顺序走。跳过中间任何一步,都会让你误以为是组件本身有 bug。
3.1 先把源码目录加入 IDE 搜索路径,再编译包
我第一次编译这类源码包时,直接在 IDE 里打开了一个后缀为 dpk 的设计期工程,结果编译报错说着说着就跑到某个底层单元上去了。后来我把编译顺序换过来,先打开运行时包工程,再编译设计期包工程,一次就过了。多出的步骤就是把源码根目录和 Source 子目录加入 IDE 的 Library Path。
在 Delphi 或 Lazarus 里,你可以在工具选项的 Library Path 里追加源码目录,也可以用命令行编译核心运行库。命令行编译的做法大致是这样:
# 先进入源码根目录,确认编译工具链环境变量已经配置好 # 这里是示意,实际参数取决于你用的 IDE 版本和项目文件名 dcc64 -B -Q -U"Source\Core;Source\Components" -D"MyDAC_STATIC_BUILD" MyRuntimePackage.dpk这个命令的意思是用 64 位编译器强制全量编译运行库包,-U后面跟着的是编译器搜索单元文件的路径,-D定义了一个条件编译符号,表示采用静态编译方式,不生成额外的运行时 bpl。这里的MyRuntimePackage.dpk要替换成你实际拿到的包工程文件名,不同版本的命名不一样。
编译过运行期包之后,再打开设计期包注册组件,这时 IDE 的组件面板上才会出现一组“My”开头的组件。如果你打开组件面板找不到新控件,先回头查一下设计期包是否编译成功,这一步并没有玄学,它就是依赖链条没走完。
3.2 建立连接:五个参数写不对,后面全是白忙
连接组件是整包的入口。你要在窗体上放一个 TMyConnection,然后设置参数。我见过不少人在这一步只填 Server、Username、Password,连接失败后才开始怀疑数据库配置,实际上大多数情况是 Protocol 或 Charset 写错了。
下面是一段最基础的单位注册和连接代码,也就是你把 MyDAC 单元引用进业务工程之后的起点:
uses System.SysUtils, MyAccess; var conn: TMyConnection; begin conn := TMyConnection.Create(nil); try conn.Server := '192.168.1.20'; conn.Port := 3306; conn.Username := 'app_user'; conn.Password := 'your_password'; conn.Database := 'orders_db'; conn.Charset := 'utf8mb4'; conn.Protocol := mpTCP; conn.ConnectTimeout := 10; conn.Compression := True; conn.Connect; finally conn.Free; end; end;代码逻辑上,先创建连接对象,赋值服务器信息,最后调用Connect建立物理连接。这里的Charset是 MyDAC 在握手之后自动发送SET NAMES语句来切换客户端字符集的关键,写错或漏写都会让后面中文数据变成乱码。
Protocol参数需要单独强调。mpTCP表示走 TCP/IP 套接字,远程连接和本机连接都可以使用。如果是连本机的 MySQL,你可能想用mpLocal,它走的是系统的 socket 文件或命名管道,这种模式配置了 MySQL 的 socket 路径时必须保证一致。在两台机器之间调试时,使用mpTCP是最稳的,不要用mpLocal去连远程主机。
3.3 参数化查询:比拼接 SQL 更值得养成习惯
连接建立之后,最常见的数据查询就是TMyQuery。它的用法和普通数据集组件类似,但在参数化查询这条线上,MyDAC 对参数绑定有严格的前置要求。
uses System.SysUtils, MyAccess, MyQuery; var conn: TMyConnection; qry: TMyQuery; begin conn := TMyConnection.Create(nil); qry := TMyQuery.Create(nil); try qry.Connection := conn; qry.SQL.Text := 'SELECT id, status, create_time FROM orders WHERE create_time > :dt AND status = :st'; qry.ParamByName('dt').AsDateTime := Now - 7; qry.ParamByName('st').AsString := 'closed'; qry.Open; while not qry.Eof do begin // 在这里读取字段值并做业务处理 qry.Next; end; finally qry.Free; conn.Free; end; end;这段代码的逻辑是:创建数据集组件,绑定连接对象,写入带参数占位符的 SQL,再用ParamByName按名称对参数赋值,最后Open执行查询。MyDAC 在底层会把参数对象传化的值打包进协议命令,而不是简单替换字符串,这样既避免引号转义的麻烦,也让 MySQL 端可以使用预处理语句,性能更高。
参数说明上,SQL 里的占位符用冒号开头,参数名称必须和ParamByName括号里的名称完全一致,大小写不敏感但拼写不能错。AsDateTime赋值时,MyDAC 会根据连接对象的日期格式设置自动转换成 MySQL 接受的字符串;如果你自己拼 SQL,大概率要手动处理YYYY-MM-DD HH:MM:SS格式,一旦时区设置不一致,查询结果就会出现边界问题。
4. 避坑指南:MyDAC v5.10 编译和运行最常踩的五个坑
源码包用久了,坑是固定的几个。下面这五条,我基本在每次答疑或换环境时都能碰到,按“现象 -> 原因 -> 解决”写在下面,能帮你省下不少排查时间。
4.1 坑一:编译时提示找不到某个单元文件
现象:打开包工程编译,弹窗提示Unit not found: MyAccess.pas,或者直接说找不到某个.dcu文件。
原因:最常见的是 IDE 的 Library Path 没有包含源码目录,或者编译顺序反了,设计期包先编译时引用了尚未编译过的 Core 单元。
解决:先把源码目录和子目录全部加入 IDE 搜索路径,再按“Core -> Components -> Design”的顺序编译。如果仍然报错,检查条件编译符号是否匹配,比如有些模块要求在 Debug 和 Release 模式下使用不同的.dcu输出目录,目录不统一照样报找不到单元。
4.2 坑二:能连接能查询,中文读出来全是乱码
现象:英文数据显示正常,中文显示成???或一长串乱码,写入数据库的中文也变成乱码。
原因:连接字符合集和 MySQL 服务端、表字段字符集不一致。MyDAC 如果没设置Charset参数,默认字符集可能不是utf8mb4,服务端返回的数据在转换时就已经丢失信息。
解决:在TMyConnection上设置Charset := 'utf8mb4',同时确认 MySQL 服务端的character_set_server和数据库表默认字符集也是utf8mb4。如果你用的是古老版本的 MySQL 5.5 以下,改成utf8比较稳妥。
4.3 坑三:本机明明装了 MySQL,连接却报“Can't connect through socket”
现象:Server 填localhost,Port 填 3306,用户名密码也正确,但连接失败,错误信息里提到 socket 文件找不到或者权限不对。
原因:MySQL 客户端在连接localhost时倾向于走本地套接字文件,而不是 TCP/IP。MyDAC 的mpLocal模式需要指定 socket 文件路径,很多开发者没有意识到这一点,误以为是账号权限问题。
解决:远程连接和本机连接都统一使用mpTCP协议,Server 填127.0.0.1而不是localhost。如果你确实要走本地套接字,确认my.cnf里socket参数路径,并在连接对象里设置对应的 socket 路径参数。
4.4 坑四:参数化查询的某一个条件永远不生效
现象:SQL 里写了WHERE status = :st,代码里也调用了ParamByName('st'),但查询结果总是把status为其他值的记录也返回了,或者干脆返回 0 行。
原因:参数占位符和ParamByName名称不一致,或者参数对象在 SQL 执行前已经被 IDE 设计器固定成了错误的数据类型。比如设计期内建好的参数是ftString,运行时赋入 Integer 值,MyDAC 可能做了隐式转换,也可能直接丢弃条件。
解决:执行Open之前打印一下qry.ParamCount和每个参数的名称,确认参数确实存在且类型一致。更稳妥的做法是,运行时用qry.ParamByName('st').Clear; qry.ParamByName('st').AsString := ...显式刷新参数,别依赖设计器自动生成的参数定义。
4.5 坑五:开发机器一切正常,发布到客户机器上连不上 MySQL
现象:同样的可执行文件,在自己机器上连接正常,拷到目标机器后报错“连接失败”或“无法解析主机名”。
原因:如果你在工程里勾选了某些依赖外部库的选项,比如加密连接、压缩协议依赖的 zlib 动态库,这些.dll或.so文件没有随程序一起分发。目标机器系统环境也缺,程序在初始化阶段就失败了。
解决:发布前检查依赖清单,把需要的动态库文件复制到可执行文件目录。最好的方式是采用静态链接方式编译运行库,这样客户端机器无需额外安装运行库;如果必须动态加载,就用工具检查一下目标机器缺少哪个文件,补上之后再用同样的步骤复测一遍。
5. 进阶验证:用压测脚本把连接池和资源回收固化到流程里
源码包在自己手里,最该做的不是改一两个函数,而是建立一套可重复的验证流程。我习惯在每次升级 MySQL 小版本或者修改连接参数后,跑一轮简单的连接压测,确认没有资源泄漏,再进入业务功能回归。
压测脚本的核心是循环执行“连接、查询、关闭”三个动作,同时监听进程的句柄数和内存变化。代码结构上,建议在一个循环里重复创建连接对象,查询一条固定 SQL,然后释放连接;如果句柄数持续上涨而不回落,说明连接释放或者数据集对象没有释放干净。
我一般会在脚本里加一个延迟选项,模拟真实业务里的查询间隔,而不是无间隔死循环。无间隔压测虽然能暴力暴露问题,但也容易把问题放大成“并发线程”表现,干扰你判断普通场景下的资源回收。
var conn: TMyConnection; qry: TMyQuery; i: Integer; tick: Int64; begin for i := 1 to 5000 do begin conn := TMyConnection.Create(nil); qry := TMyQuery.Create(nil); try conn.Server := '192.168.1.20'; conn.Port := 3306; conn.Username := 'app_user'; conn.Password := 'your_password'; conn.Database := 'orders_db'; conn.Charset := 'utf8mb4'; conn.Protocol := mpTCP; conn.ConnectTimeout := 10; conn.Connect; qry.Connection := conn; qry.SQL.Text := 'SELECT 1, NOW(), CURRENT_USER()'; qry.Open; qry.Close; finally qry.Free; conn.Free; end; if (i mod 100) = 0 then begin tick := GetTickCount64; WriteLn(Format('第 %d 次循环完成, 当前时间戳: %d', [i, tick])); end; end; end;这段压测脚本里,每轮循环都独立创建和释放连接。重点观察两个指标:一个是程序是否在长时间运行后被系统提示“句柄数过多”,另一个是日志里有没有出现连接超时或资源不足的错误。GetTickCount64在这里只是用来记录时间,不是为了测性能,而是方便你在日志里定位跑挂时是哪一轮循环出的问题。
那一次让我印象深刻的教训是:我改了一个连接池的超时参数,从 10 秒改成 60 秒,原本每天稳定运行的系统开始出现连接堆积。压测脚本跑了一万次之后,句柄数直接翻了一倍,最后查下来是连接池里的连接没有被正确回收,空闲连接也没有按预期释放。从那以后,我每次调整任何连接参数或者升级源码包版本,都强制走一遍这个循环压测流程,确认内存和句柄数呈现水平线而不是上升曲线,才敢发布到生产环境。希望这整套拆包、编译、避坑和验证的思路能帮到你。
本文还有配套的精品资源,点击获取