简介:MySQL Data Access Components (MyDAC) v5.10 源文件包是一套面向 Delphi 7~2007 与 C++Builder 开发者的 MySQL 数据库连接组件源码,用来实现数据读写、事务控制、存储过程与异步操作,且不依赖第三方中间件,适合需要直接连接 MySQL 的桌面或服务器应用开发,覆盖从简单查询到复杂事务的常见场景。整个压缩包共 1403 个文件、1.39MB,主体为 360 个 Pascal 源文件与 33 个 C++ 源文件,另含 146 个组件包定义、107 个窗体布局、95 个工程配置以及图标位图、说明文档等,既可用于源码级定制,也可按需编译安装。目前已有 167 人学习浏览。包内目录结构清晰,自带帮助文档、FAQ、历史更新记录和许可条款,并提供了跨构建器的编译脚本,方便在 Delphi/C++Builder 中快速编译、注册组件。对于想研究数据库访问组件底层实现、或因项目需要修改扩展 MyDAC 功能的开发者,这份源文件能有效缩短二次开发周期。
1. 老 Delphi 程序连 MySQL 的刚需:为什么我要拆 MyDAC 5.10 源码
接手一套 Delphi 老项目时,最先让我上火的往往不是业务逻辑,而是数据库连接层动不动就翻车。数据表在 MySQL 里,客户端机器上那份 libmysql.dll 换一个版本,查询直接报错,换回老版本又怕安全补丁缺失。后来把目光落到 MyDAC 5.10 这套组件源码上,才算是把连接主动权拿回手里:它本身是 MySQL 访问组件的完整源码包,既能直连 MySQL,也能选择走官方客户端库,最关键的是源码在手,可以自己改、自己编,不受预编译安装包限制。这篇文章按我实际拆包验证的顺序,聊组件结构、编译安装、参数设置和排错经验,适合准备维护 Delphi 老系统、或者想深度定制数据访问层的开发者。
2. 组件包结构拆解:TMyConnection、TMyQuery 与两种连接模式怎么选
解压之后不要急着往 IDE 里装,我就是跳过这一步吃过亏。先把源码目录的层级看一眼,MyDAC 不是一个单一单元,而是一组相互依赖的组件包。至少得分清哪些是运行时包、哪些是设计时包,不然装完想拆很麻烦。
2.1 组件家族:从连接管理到批量脚本的五个常用类
这套组件里,平时真正频繁打交道的主要是五个类。TMyConnection 负责连接参数、连接生命周期和事务边界,一个工程通常只需要一个实例;TMyQuery 负责执行 SELECT 和增删改,返回数据集;TMyScript 负责一次性执行多条 SQL,初始化库表结构时很好用;TMySQLMonitor 负责记录客户端与服务端之间的通信内容,排查慢查询和莫名丢数据时最有价值;TMyDataSource 负责把 TMyQuery 接到 DBGrid、DBEdit 这一类数据感知控件上。
| 组件名 | 主要职责 | 使用频率 |
|---|---|---|
| TMyConnection | 管理连接、事务、登录参数 | 每个工程至少一个 |
| TMyQuery | 查询、增删改、返回数据集 | 日常使用最多 |
| TMyScript | 批量执行多条 SQL | 初始化、数据迁移 |
| TMySQLMonitor | 记录 SQL 与通信日志 | 调试期常用 |
| TMyDataSource | 连接 TMyQuery 与界面控件 | 做界面绑定用 |
这五个类各自独立,但都依赖连接实例。拆包时最容易忽略的是 TMyDataSource:直接写代码取数据可以不关心它,但凡是拖 DBGrid 做界面的老项目,几乎都会用到。我一般先看一遍 Demos 目录里最简单的查询示例,确认当前版本这些组件在对象监视器里的属性名,再动手写业务代码,能少踩很多“属性不存在”的编译坑。
2.2 两种连接模式:直连模式与客户端库模式的原理和选型
MyDAC 最值得理解的特性是两种连接模式。第一种叫直连模式,它用组件内部自行实现的 socket 协议直接和 MySQL 服务端对话,客户端机器不需要安装 MySQL 官方客户端库,只要 3306 端口能通就能连。第二种是客户端库模式,通过 libmysql.dll 调用 MySQL 官方客户端库来完成握手、认证和协议解析。两种模式在源码里和连接参数上都有对应入口,常见做法是展开 TMyConnection 的 Options 属性,找到 Protocol 字段切换,mpDirect 对应直连,mpMySQL 对应客户端库。
选型不能只看心情。直连模式部署最简单,可执行文件丢过去就能跑,不需要额外分发 DLL,很适合给内部工具、单机版程序用;但它在 MySQL 8 之后的新认证、新压缩协议、LOAD DATA LOCAL INFILE 这类较新特性上支持有限。客户端库模式把协议细节交给官方 DLL,兼容性更好,但要求目标机器有正确版本的 libmysql.dll,而且 DLL 位数必须和程序位数一致。我自己在维护老系统时默认用直连模式,只有在需要新认证或复杂传输特性时才切到客户端库模式,并且把 DLL 和 exe 放在同一目录,避免系统目录里的旧版本干扰。
2.3 解压后先看哪几个目录:一份可对照的文件清单
源码包解压之后的目录结构,直接影响你能不能一次装成功。常见结构是按 IDE 版本分目录,因为不同 Delphi 版本的包格式和编译器指令不一样。我习惯先看 Source 目录,再确认 Demos 目录里有没有对应版本的示例工程。
| 路径 | 常见内容 | 用途 |
|---|---|---|
| Source\Delphi7 | 对应 IDE 的运行时包与设计时包 | 编译生成 bpl / dcu |
| Source\Delphi2009 | 对应版本代码 | 旧版本 IDE 安装 |
| Source\DelphiXE | 后续版本代码 | 新一点的环境 |
| Demos | 示例工程与连接配置 | 照抄连接参数最快 |
| Docs | 帮助文档、版本说明 | 查属性说明 |
这里最容易搞错的是 Delphi 7 和 Delphi 2009 的目录不能混用。曾经有同事图省事,把 Delphi 2009 目录下的 dpk 拿给 Delphi 7 编译,结果报了一堆 Pascal 语法不兼容。源码包里的版本目录基本是按编译器语法特性划分的,跨版本硬编可以碰运气,但不值得浪费时间。我先按目标 IDE 选好目录,然后把对应目录加进 IDE 的 Library Path,再开始编译安装。
3. 从源码编译安装:Delphi 环境里的安装步骤与定制的三个入口
拿到源码包之后,接下来要解决的是怎么把这堆 pas 文件变成 IDE 里能拖拽的组件。这个过程说简单也简单,说坑也多,核心是掌握编译顺序和路径设置。
3.1 设计期安装:先编译运行时包,再注册设计时包
安装 MyDAC 源码包的正确顺序,是先编译运行时包,再编译并注册设计时包。运行时包提供实际功能代码,生成的是 bpl 和 dcu;设计时包负责把组件注册到 IDE 的组件面板上,它依赖运行时包。常见步骤是这样:
- 关闭 Delphi,备份当前 IDE 的 bpl 配置和 Library Path;
- 用文件管理器打开 Source 下对应 IDE 版本的目录;
- 先打开不带 dcl 前缀的 dpk 文件,执行 Build,确认生成 bpl;
- 再打开带 dcl 前缀的 dpk 文件,执行 Build,然后点 Install;
- 重启 IDE,在 Component > Install Packages 里勾选新注册的包,组件面板出现 My 开头的组件页。
如果你更习惯命令行,也可以写一个批处理来做,我一般这样写:
@echo off rem 以 Delphi 7 为例,其他版本请替换 dcc32 路径和目录名 set MYDAC_SRC=D:\libs\MyDAC5.10\Source\Delphi7 set D7_BIN=C:\Delphi7\bin rem 1. 先编运行时包 "%D7_BIN%\dcc32.exe" -B -Q "%MYDAC_SRC%\MyDAC70.dpk" if errorlevel 1 goto fail rem 2. 再编设计时包 "%D7_BIN%\dcc32.exe" -B -Q "%MYDAC_SRC%\dclMyDAC70.dpk" if errorlevel 1 goto fail echo 编译完成,请到 IDE 中执行 Install goto end :fail echo 编译失败,先检查 Library Path 是否包含 Source 目录 :end这段批处理的核心是 -B 参数,它表示全量重建,避免旧的 dcu 缓存干扰;-Q 是安静模式,只输出错误信息。注意 dcc32.exe 是 Delphi 7 自带的命令行编译器,新版 IDE 的命令行工具名称可能不同,要按实际环境替换。运行时包和设计时包的先后顺序不能反,否则设计时包会提示找不到运行时单元的符号。
除了编译顺序,还要在 IDE 的 Environment Options 里把 Source 对应目录加进 Library Path。我遇到过编译通过、但打开窗体时提示找不到 TMyConnection 的情况,原因就是 bpl 装了,但 IDE 搜索路径里没有 pas 文件,设计器无法解析类声明。把这个目录加进去再重启 IDE,问题就消失了。
3.2 离线编译:不装组件面板,只把源码当库用
有些内部构建机器上不跑 Delphi IDE,只需要在命令行环境里编出业务 exe。这时候不需要装设计时包,只要让编译器能找到源码单元就行。常见做法是把 Source 对应目录写进项目搜索路径,然后在工程文件里引用需要的单元。这样做组件不会出现在面板上,但运行时对象照常工作,适合自动构建和版本管理。
我一般在持续集成脚本里做两件事:一是把 dcu 输出目录固定到项目下的 Library 子目录,方便清理;二是把 Search Path 指向源码目录,而不是复制 pas 到工程目录。复制源码虽然能编过,但后续要换版本时很容易误用旧文件,排查起来非常痛苦。离线编译时如果报找不到某个单元,优先检查搜索路径里是否把 Source 的所有子目录都包含了,MyDAC 源码里有些公共单元放在根目录,有些放在对应版本目录,漏一个都会编译失败。
3.3 定制入口:默认端口、默认字符集和认证切换改在哪里
源码包最大的价值是能改默认行为。我不建议每次在窗体上手动设置十几个属性,而是直接改组件默认值,让所有新放置的组件从一开始就带着正确配置。
第一个入口是默认端口。TMyConnection 的构造函数里通常会有 Port 的初始赋值,常见值是 3306。如果公司内部 MySQL 跑在 3307,业务又必须统一连接端口,就可以把这个默认值改掉。改的时候顺手在代码里加注释,不然半年后你自己也不知道端口为什么变了。
第二个入口是字符集。老代码里中文乱码的根源,往往是组件默认 Charset 为空、UseUnicode 为 False。我会在构造函数里把默认值改成 utf8mb4 和 True:
constructor TMyConnection.Create(AOwner: TComponent); begin inherited Create(AOwner); Port := 3306; Options.Charset := 'utf8mb4'; Options.UseUnicode := True; end;这段代码说明三点:Port 是连接端口,Charset 控制连接建立后发送的字符集声明,UseUnicode 决定字符串字段是否按 Unicode 处理。第三个值很关键,只改 Charset 不改 UseUnicode,读出来的中文仍可能乱码。注意这里直接改构造函数会影响所有实例,如果你有多个环境需要不同端口,就别把 Server 也写死,只改默认值即可。
第三个入口是认证切换,针对 MySQL 8 场景。旧源码里认证协商逻辑往往只认 mysql_native_password,遇到 caching_sha2_password 会直接失败。常见做法有两个方向:一是服务端把用户切回旧插件;二是找到源码里发送握手响应的位置,把客户端声明的插件名改成服务端支持的插件。第二个方向工作量大,需要同时处理密码加密方式,我一般优先用服务端切换,风险更小。
4. 避坑实录:安装、连接与字符集翻车后的排查笔记
无论源码包多完整,实战中踩坑都在所难免。这里记录几条我反复遇到、且每条都能直接照抄解决方法的排查笔记。
4.1 组件装完面板空白,窗体上还提示找不到 TMyConnection
现象:设计时包安装提示成功,但组件面板上找不到 TMyConnection,打开旧窗体时提示“Class TMyConnection not found”。
原因:最常见的两个原因:一是运行时包没有先装,设计时包加载时找不到底层类;二是不同 IDE 版本的包混装,Delphi 7 的 bpl 被拖到新版 IDE 里注册,类名冲突或无法解析。
解决:先确认安装的是当前 IDE 对应的 Source 版本目录,然后在 Component > Install Packages 里把旧的 MyDAC 包移除,重新按“运行时包先编、设计时包后编”的顺序来一遍。装完重启 IDE,在组件面板里右键找到 My 开头的页签,确认组件已经注册。
4.2 localhost 连接被拒:10061 错误其实和代码无关
现象:使用 Conn.Connect 时报“Can't connect to MySQL server on 'localhost' (10061)”,但同一台机器上用命令行 mysql 能正常登录。
原因:Windows 上 localhost 可能解析成 IPv6 地址 ::1,而 MySQL 服务端配置只监听了 127.0.0.1。TMyConnection 把 Server 设为 localhost 后,走的却是 IPv6,服务端根本不听这个端口。这不是组件问题,是地址解析问题。
解决:先把 Server 改成 127.0.0.1 试一次。如果必须用主机名,检查 MySQL 的 bind-address 配置是否包含对应监听地址。验证监听状态可以用这条命令:
netstat -ano | findstr :3306如果输出里只有[::]:3306而没有0.0.0.0:3306,说明服务端只监听 IPv6,需要调整 my.ini 的 bind-address 为 0.0.0.0 或 127.0.0.1。从那以后我新建连接先把 Server 写成 IP,不写 localhost,省掉这块来回折腾的时间。
4.3 中文字段乱码:Charset 和 UseUnicode 必须一起设置
现象:程序里插入中文正常,SELECT 出来的中文在 DBGrid 里显示成问号;或者 Navicat 里看数据是好的,程序读出来是乱码。
原因:连接建立后没有发送正确的SET NAMES utf8mb4,服务端按 latin1 处理客户端传输的字符;同时 UseUnicode 为 False 时,字符串字段被当作 AnsiString 处理,数据在两层转换中丢失。
解决:连接前设置两个参数,缺一不可:
Conn.Options.Charset := 'utf8mb4'; Conn.Options.UseUnicode := True;如果用的是旧版本组件,Charset 属性没接上,可以在 AfterConnect 事件里手动兜底:
Conn.ExecSQL('SET NAMES utf8mb4');执行完之后再查一遍字符集,确认 client 和 results 都是 utf8 相关值:
SELECT @@character_set_client, @@character_set_results;如果结果里出现 latin1,说明连接参数没生效,回到前面的设置去查,别在数据转换代码里找问题。
4.4 libmysql.dll 位数与版本不匹配导致闪退
现象:本机调试正常,把 exe 复制到另一台机器后直接闪退,或者弹“Cannot load libmysql.dll”。
原因:用的是客户端库模式,但目标机器没有 libmysql.dll,或者程序是 32 位的,却放了一个 64 位的 DLL。Windows 加载 DLL 时位数不匹配不会给友好提示,通常直接让进程退出。
解决:先确认你的 Delphi 程序编译目标是 32 位还是 64 位,再去找对应位数的 libmysql.dll,放在 exe 同目录。不要指望放在 system32 里,exe 目录优先级更高,而且 system32 目录下容易混入旧版本。如果业务用不到新协议特性,最简单是切回直连模式,直接去掉 DLL 依赖。
4.5 MySQL 8 认证插件 caching_sha2_password 连接失败
现象:MySQL 服务端是 8.0,用户默认认证插件是 caching_sha2_password,MyDAC 5.10 连接时报“Authentication plugin 'caching_sha2_password' cannot be loaded”。
原因:MyDAC 5.10 时代 MySQL 8 还没发布,源码里的认证协商逻辑不认识这个新插件。握手时客户端无法按服务端要求完成密码交换。
解决:短期方案是在 MySQL 里把该用户临时切回旧插件:
ALTER USER 'test'@'%' IDENTIFIED WITH mysql_native_password BY '密码';这个方案在 MySQL 8.0 上还能用,但新版 MySQL 已经逐步禁用旧插件,所以不能把希望全押在这条路上。更好的做法是换用较新的客户端库文件,并把 Protocol 切到客户端库模式,让新 DLL 里的认证逻辑去处理。如果源码里有清晰的握手处理函数,也可以根据服务端发来的插件名增加分支,但工作量不小,适合真有必要时再做。
5. 落地一个可运行的查询工程:组件放置、连接串与调试三板斧
避坑说到底是为了让业务跑起来。这一章给一个最小可用的工程骨架,覆盖连接、查询、批量执行和调试四个刚需场景。
5.1 最小连接样例:TMyConnection 与 TMyQuery 跑通一条查询
先放一个最基本的按钮事件,做的事情是:创建连接、打开数据库、查询最近 20 条记录、逐行往 Memo 里输出。代码里刻意避免在设计期放组件,用运行时创建方式,这样更容易看出每个参数的作用。
procedure TForm1.Button1Click(Sender: TObject); var Conn: TMyConnection; Q: TMyQuery; begin Conn := TMyConnection.Create(nil); try Conn.Server := '127.0.0.1'; Conn.Port := 3306; Conn.Username := 'appuser'; Conn.Password := 'apppass'; Conn.Database := 'appdb'; Conn.Options.Charset := 'utf8mb4'; Conn.Options.UseUnicode := True; Conn.Connect; Q := TMyQuery.Create(nil); try Q.Connection := Conn; Q.SQL.Text := 'SELECT id, title, create_time ' + 'FROM articles ' + 'WHERE status = :st ' + 'ORDER BY create_time DESC LIMIT 20'; Q.Params.ParamByName('st').AsInteger := 1; Q.Open; while not Q.Eof do begin Memo1.Lines.Add(Q.FieldByName('title').AsString); Q.Next; end; finally Q.Free; end; finally Conn.Free; end; end;这里几个参数要说明:Server 填 127.0.0.1 而不是 localhost,能避开 IPv6 解析问题;Charset 和 UseUnicode 成对出现,保证中文不乱码;Q.Open 只用于 SELECT,如果是 INSERT 或 UPDATE,要改成 Q.ExecSQL。使用:st参数是为了避免拼 SQL 时的注入和转义问题,每次执行前用 ParamByName 赋值。注意 Q.Next 循环读取时,如果数据量大,要考虑 FetchAll 的设置,默认情况下边取边读会占用连接,循环期间不要在同一连接上执行其他查询。
5.2 TMyScript 批量执行与事务边界控制
初始化数据库、导入脚本这类场景,逐条执行 SQL 太慢。TMyScript 的作用就是把多条 SQL 一次发给服务端处理。但要注意它只负责执行,不负责原子性,事务仍要自己控制。
procedure RunBatchScript(Conn: TMyConnection); var Sc: TMyScript; begin Sc := TMyScript.Create(nil); try Sc.Connection := Conn; Sc.SQL.Text := 'INSERT INTO log_audit(user_name, action) VALUES (''dev'', ''login'');' + 'UPDATE t_user SET last_login = NOW() WHERE id = 2;'; Sc.Execute; finally Sc.Free; end; end;TMyScript 默认按分号切分语句,所以每条 SQL 之间必须有分号。如果脚本里包含存储过程定义,BEGIN...END 结构里的分号会被误判为语句边界,这是 5.10 版本常见的解析限制。遇到这种情况,我一般把 CREATE PROCEDURE 拆出来用单独连接执行,或者改用 TMyQuery 逐条执行。
需要保证多条 SQL 要么全成功、要么全失败时,要额外套事务:
Conn.StartTransaction; try // 执行第一条 SQL // 执行第二条 SQL Conn.Commit; except Conn.Rollback; raise; end;注意先确认 TMyConnection 的 AutoCommit 属性。如果 AutoCommit 为 True,组件会在每条语句执行后自动提交,事务控制不会生效。我一般把 AutoCommit 设为 False,由代码显式控制提交时机。
5.3 使用 TMySQLMonitor 抓取 SQL 日志
连接正常、查询结果不对、或者想知道某条 SQL 到底在什么时候执行,靠断点单步太慢。MyDAC 自带的 TMySQLMonitor 组件可以直接把客户端发出的 SQL 写到日志文件,配置很轻量:
SQLMonitor := TMySQLMonitor.Create(nil); try SQLMonitor.FileName := ExtractFilePath(Application.ExeName) + 'sql_trace.log'; SQLMonitor.Active := True; Conn.Monitor := SQLMonitor; finally // 连接销毁时由 Owner 统一释放 end;这样每次 TMyQuery 执行,SQL 语句会连同执行时间写入日志。日志文件里的内容能直观看出:参数值有没有正确传入、字符集声明有没有在连接后自动执行、哪条语句耗时最长。需要注意,TMySQLMonitor 组件在不同版本里的属性名略有出入,我说的是 5.10 源码包里的常见用法,如果编译时提示找不到 Monitor 属性,去 Demos 目录里找对应的 MonitorDemo 看一遍挂接方式就行。
日志文件别一直开着,调完就关闭 Active,否则程序在生产环境持续写文件,磁盘空间会悄悄被占满。
6. 验证自检与进阶改造:把 5.10 源码改到能连 MySQL 8
最后分享一个我每次拆完源码包都会下意识做的动作:写一个最小的自检函数,验证“连接、字符集、事务回滚”三件事是否正常。这个函数能筛掉大部分环境问题。
6.1 一个自检函数:三分钟确认连接环境
function CheckMyDACSetup(Conn: TMyConnection; out Msg: string): Boolean; var Q: TMyQuery; begin Result := False; Q := TMyQuery.Create(nil); try Q.Connection := Conn; Q.SQL.Text := 'SELECT @@character_set_client, @@character_set_results'; Q.Open; if (Pos('utf8', Q.Fields[0].AsString) = 0) or (Pos('utf8', Q.Fields[1].AsString) = 0) then begin Msg := 'charset error: ' + Q.Fields[0].AsString; Exit; end; Result := True; Msg := 'ok'; finally Q.Free; end; end;这个函数在 Conn.Connect 成功后调用。如果返回 charset error,说明前面设置的 Options.Charset 没有生效,按第 4 章乱码排查的方向继续查。如果这里正常,后续中文显示还有问题,那问题就在界面数据感知控件或数据库表的排序规则上,方向完全不同。
6.2 连 MySQL 8 前的四项核对清单
服务端如果是 MySQL 8,我建议在业务代码之前先核对四件事。第一,确认用户插件类型,执行SELECT user, host, plugin FROM mysql.user,看目标用户是不是 caching_sha2_password。第二,确认客户端库文件的位数和版本,32 位程序必须配 32 位 DLL。第三,确认程序里 Protocol 用的是直连还是客户端库,这决定了前两项哪一个真正生效。第四,确认连接后字符集查询结果是 utf8mb4 而不是 latin1。
这四项核对完,5.10 源码包连 MySQL 8 的兼容性问题基本都能定位到具体层。剩下的无非是换 DLL、换用户插件、或者改代码里认证协商,不会再是“玄学”。
6.3 我给自己定的固定动作
从那以后,我每次拿到这类组件源码包,都强制走一遍固定顺序:先原包编译安装,再做一次最小连接自检,然后只改动一个默认参数,重新编译回归。这个顺序能保证构建环境的问题和代码改动的问题不混在一起。很多人一上来就改源码,编译不过就怀疑包有问题,其实八成是 IDE 路径没配好。先让原包跑通,再动定制,排查效率会高很多。希望帮到你。
本文还有配套的精品资源,点击获取