☰
MyDAC v5.10源码解析:Delphi连接MySQL的安装、排错与定制
2026/10/14 6:45:32 网站建设 项目流程

简介:MySQL Data Access Components(MyDAC)v5.10完整源文件包,专为Delphi 7至2007与C++Builder开发者设计,用于在不依赖第三方库或中间件的情况下直接连接MySQL服务器,覆盖事务处理、批处理操作、存储过程、异步机制与错误处理等能力,适合需要高效稳定数据库访问或进行组件级二次开发的场景。资源共1403个文件,压缩包仅1.39MB,包含360个Pas单元源码、146个Dpk工程、107个Dfm窗体、95个Cfg配置及Bdsproj、Bmp、Obj等类型,覆盖组件实现、IDE安装工程与界面资源。随包附有Hlp/Cnt帮助索引、History更新记录、FAQ问答、Readme说明、License许可及Make编译脚本,便于查阅和重新编译。已有167人学习下载。开发者可借助完整源码理解MyDAC内部机制,并按需裁剪或深度定制,是旧版IDE环境下MySQL数据库开发与控件研究的实用资料。

1. MyDAC v5.10 源文件:老 Delphi 项目连 MySQL 绕不开的一堆源码

手边出现一套 MySQL Data Access Components (MyDAC) v5.10 源文件,大概率是两种处境:要么你在接手一个十来年前用 Delphi 写的业务系统,原维护者只移交了一个源码压缩包;要么你正在评估给老项目换数据库访问层,不想继续被 BDE 和 ODBC 折腾。MyDAC 是 Delphi/C++ Builder 环境下原生直连 MySQL 的数据库访问组件集,v5.10 属于那几年里很稳的一代。它解决的问题很具体:不需要装任何驱动、不需要中间数据服务,IDE 里拖一个连接控件、填上服务器地址和账号,就能开始读写 MySQL。这套东西适合维护遗留 ERP、进销存、MIS 系统的工程师,也适合所有不想再把连接问题当玄学的人——源文件在手,每个环节都能拆开看。下文按安装、连接、读写、排错、定制这条线,把整条路走通。

2. 看懂源文件包再动手:MyDAC v5.10 的目录结构与安装闭环

拿到源文件直接双击某个包工程就编译,大概率翻车。包内单元有依赖顺序,路径配置也不只是把文件夹加进去那么简单。先把源文件的组织方式讲清楚,再给一套能落地的安装流程。

2.1 核心单元地图:连接、命令、元数据、批量导入各管一摊

源文件包和安装版最大的差别,在于没有绑定具体 IDE 版本的编译产物。你手里是一批 .pas 源码单元、若干 .dpk 包工程和文档。按职责划分,MyDAC v5.10 的代码面可以分成四层,搞清楚这四层,编译报错时能立刻判断缺了哪一块。

第一层是连接内核,对应 TMyConnection。它负责建立和管理与 MySQL 服务器的会话,包括 TCP 握手、身份认证、事务状态维护、连接参数缓存。几乎所有其他组件都依赖一个已连接的 TMyConnection 实例,所以这一层没有可替代路径,编译时必须最先通过。第二层是读写命令层,对应 TMyQuery、TMyTable、TMyCommand。TMyQuery 最常用,既能 Open 出结果集,也能用 ExecSQL 执行写操作;TMyTable 面向单表操作;TMyCommand 适合轻量命令。第三层是批量与脚本层,TMyScript 一次执行多段 SQL,TMyLoader 专做大吞吐数据装载。第四层是元数据与监控,TMyMetaData 读表结构,TMyMonitor 记录客户端与服务器之间的原始报文。

把组件映射回源文件,通常能看到这几类文件:连接核心单元、协议解析单元、组件封装单元、设计期注册单元。设计期单元只服务于 IDE 可视化安装,发布程序时不需要编译进去。我编译时习惯严格按「连接核心 → 命令层 → 设计期包」的顺序推进,一旦报错,错误信息指向的就是还没满足依赖的那一层,不会出现到处乱报的假象。

2.2 在 IDE 里安装组件:库路径、搜索路径与两个包的先后顺序

常见做法是把源文件目录的用途拆成两份:一份加进 IDE 的库搜索路径,让所有项目都能 uses 到组件单元;另一份用于编译包工程,生成设计期组件。先配置路径:

打开 Delphi 的 Tools > Options > Library(老版本在 Environment Options 里),把 MyDAC 源码根目录加入 Library Path。注意加入的是根目录而不是某个子目录,因为单元文件可能按功能分散在多个子目录,漏加会导致编译器搜不到,报错信息还会误导你反复改路径。

组件安装本身分两步,顺序固定。先打开运行时包工程,文件一般是 .dpk 或 .dproj,名字里通常带 Runtime 字样;编译通过后生成运行时 DCU 和 BPL。再打开设计期包工程,点 Install 注册到 IDE 组件面板。设计期包依赖运行时包,顺序反了会一路报缺单元。如果源文件提供的是新版工程格式,也可以用命令行编译:

# 先用 Release 配置编译运行时包,产物用于后续分发(包名以实际源文件为准) msbuild MyDACRuntime.dproj /t:Build /p:Config=Release # 再用 Debug 配置编译设计期包,供 IDE 安装使用 msbuild MyDACDesign.dproj /t:Build /p:Config=Debug

两条命令里,msbuild 是较新 IDE 的构建入口;老 IDE 没有 msbuild,直接用集成环境打开 .dpk 文件,先 Compile 后 Install 效果一样。关键在顺序:运行时包在前、设计期包在后。包工程的具体名字存在差异,以你手里的源文件为准,别照着记忆里的文件名硬找。

这里有一个容易踩的选项:包工程的输出目录。很多源文件包默认输出到 IDE 公共 DCU 目录,机器上装过多个版本时新旧 DCU 会混在一起。我习惯把输出目录改到源文件包内的 build 子目录,每次重编前先删掉 build 目录,保证加载的必然是全新产物,排查环境问题会干净很多。

注意:条件编译指令(比如针对不同 IDE 版本的 {$IFDEF})受源文件当时的支持范围限制。跨大版本使用老源码,编译不过不一定是你的问题,先确认源文件声明支持的 IDE 版本。

2.3 重新编译的边界:哪些必须自己 build,哪些可以直接用

源文件里的 .pas 是跨 IDE 版本通用的,但编译出的 DCU 不是。Delphi 大版本之间二进制不兼容,所以只要你的 IDE 比 v5.10 时代新,就必须全部重新编译。反过来说,手头正好有那个年代的老 IDE,理论上可以直接用包内附带的 DCU,我仍然建议重编一遍:你无法确认那些 DCU 的编译选项、补丁级别和优化开关,留着等于给后续排错埋雷。统一重编花不了多少时间,换来的是一个干净基线。

有一类文件不需要你编译,但运行期必须存在:MySQL 客户端库。MyDAC 访问 MySQL 走客户端库协议,常见形式是运行时动态加载 libmysql.dll。这个 dll 不是组件源码的一部分,要去 MySQL 发行包里拿,而且服务器大版本不同,dll 也要对应。编译组件时用不到它,运行程序时才加载,所以它的报错总在程序启动之后而不是编译阶段。这类问题放进第五章集中讲。

3. 连接是第一步,也是最多坑的源头:TMyConnection 参数与连接串

数据库组件的故障,超过一半发生在连接层。连接参数没配好,后面所有 SQL 都无从谈起。这一章把 TMyConnection 的关键参数、两种赋值方式和验证手段一次说透。

3.1 必须写对的 5 个基础参数,外加一个字符集

TMyConnection 的属性方式比连接串直观,高频参数就这几个:

参数典型值说明
Server192.168.10.5服务器 IP 或主机名,本机可填 localhost
Port3306MySQL 默认端口,改动过必须显式指定
UserNameapp_user连接账号,建议按最小权限原则创建
Password——对应用户密码
Databaseerp_db默认数据库;留空则连接后无当前库,SQL 要带库名前缀

这五个参数是刚性的,任何一个不对都连不上。还有一个柔性参数容易被忽略:Charset。老版本 MySQL 服务端默认字符集可能是 latin1,业务表却可能是 gbk 或 utf8,连接层的 Charset 决定客户端和服务器之间的编码约定,它必须和服务器端 character_set_client 对齐,否则连得上但数据乱。

配套的 Options.UseUnicode 也要一起看。置为 True 时,字符串在组件层以 Unicode 形式传递,能规避一大半乱码问题;代价是字符字段都走宽字符路径,老代码里如果大量用单字节 String 接收,要注意赋值时的隐性转换。我的判断标准:新项目无脑开 UseUnicode;老项目先查代码里有没有把字符串塞进定长 char 数组的写法,再决定开不开。

排查时把参数分成两类:地址类是硬故障,字符集类是软故障。硬故障表现是连不上、报错;软故障表现是能连上但数据显示不对。两类问题的排查路径完全不同,分清楚能省一半时间。

3.2 属性赋值与连接串:两种写法、一个共同终点

MyDAC 支持两种配置连接的方式:逐属性赋值,或用一段连接串整体解析。场景不同选型不同。

// 方式一:逐属性赋值,适合配置写在代码里、可读性优先 MyConn := TMyConnection.Create(nil); try MyConn.Server := '192.168.10.5'; MyConn.Port := 3306; MyConn.UserName := 'app_user'; MyConn.Password := 'p@ssw0rd'; MyConn.Database := 'erp_db'; MyConn.Charset := 'utf8'; MyConn.Options.UseUnicode := True; MyConn.ConnectTimeout := 5; MyConn.Connect; finally MyConn.Free; end;

这段代码创建独立连接对象,填参数后调用 Connect 建立会话。ConnectTimeout 单位是秒,设为 5 表示 5 秒内建不成 TCP 连接就报错。这个参数在服务器不可达时特别关键:不设置的话,默认会一直等到系统级 TCP 超时,界面可能卡住十几秒甚至更久。属性赋值的好处是每个参数肉眼可见,评审和排错都很直观。

// 方式二:连接串整体赋值,适合配置外置到 ini 或注册表 MyConn := TMyConnection.Create(nil); try MyConn.ConnectString := 'Server=192.168.10.5;Port=3306;User ID=app_user;' + 'Password=p@ssw0rd;Database=erp_db;Charset=utf8;' + 'Use Unicode=True;Connect Timeout=5'; MyConn.Connect; finally MyConn.Free; end;

连接串的解析规则是分号分隔的键值对,键名大小写不敏感。两种写法最终写入同一组属性,不存在谁优先级更高,后赋值的覆盖先赋值的。真正的优先级陷阱在设计期:对象监视器里填的连接参数会被序列化进窗体 DFM,运行时代码再赋值则以代码为准。我处理过一起连错库的故障,定位到是 DFM 里残留了开发库的 Database 属性,代码里只在某条分支路径上覆盖了连接参数。

注意:连接配置的来源越少越好。要么全走代码,要么全走外部配置文件,不要两头都写,否则埋下的问题只在特定执行路径上爆发。

3.3 最小连接验证:用 SELECT 1 把问题切成两半

连接配好以后别急着连业务表,先跑一个最小验证。这一步能把「连接信息错」和「SQL 写错」两类问题一刀切开。

procedure ValidateConnection(AConn: TMyConnection); var Q: TMyQuery; begin Q := TMyQuery.Create(nil); try Q.Connection := AConn; Q.SQL.Text := 'SELECT 1'; Q.Open; // 能走到这里:TCP、认证、客户端库三个环节全部通过 ShowMessage('连接正常,SELECT 1 = ' + Q.Fields[0].AsString); finally Q.Free; end; end;

SELECT 1 不触碰业务表,不要求账号具备具体表的权限,服务器接受登录就能成功。它通过后,再执行SELECT DATABASE(), CURRENT_USER()确认当前库和登录账号,进一步验证默认库是否正确。这两条语句是我换新环境后必跑的第一组测试。等它们都通过再上业务 SQL,此时还有问题,责任就在 SQL 本身而不是连接层,排查范围立刻缩小一半。

4. 读写数据:TMyQuery 参数化与 TMyScript 事务配合

连接打通,进入正题:怎么读、怎么写、怎么批量执行。这一章围绕 TMyQuery 的 Open/ExecSQL 选择、参数化传参和 TMyScript 的事务行为展开。

4.1 Open 与 ExecSQL 的分工:结果集与影响行数

TMyQuery 是使用频率最高的数据组件,对外暴露两个执行入口,选择规则非常固定:SELECT 用 Open,返回可遍历结果集;INSERT/UPDATE/DELETE 用 ExecSQL,不返回结果集,只返回受影响行数。混用会收到两个方向的报错:Open 执行 UPDATE 报语法错误,ExecSQL 执行 SELECT 拿不到数据。

// 查询走 Open,结果集按行遍历 Query.SQL.Text := 'SELECT order_id, amount FROM orders WHERE create_date >= :d1'; Query.ParamByName('d1').AsDateTime := Date - 7; Query.Open; while not Query.Eof do begin Total := Total + Query.FieldByName('amount').AsFloat; Query.Next; end; // 写操作走 ExecSQL,用 RowsAffected 做业务校验 Query.SQL.Text := 'UPDATE orders SET status = :st WHERE order_id = :id'; Query.ParamByName('st').AsInteger := 2; Query.ParamByName('id').AsInteger := 10086; Query.ExecSQL; if Query.RowsAffected = 0 then Log('没有行被更新,请核对订单号');

第一段遍历结果集求和,第二段执行更新并通过 RowsAffected 判断命中行数。这里有个高频错误:SQL 里参数写成:d1,调用时有人写ParamByName(':d1'),多了冒号。ParamByName 的参数名不需要冒号,带了反而查不到参数对象,运行期抛参数不存在。这是代码评审里出现频率最高的低级错误。

结果集还有个选项值得提前了解:FetchAll。默认情况下 MyDAC 会把结果集一次性取到客户端内存,几百行的表没问题,几十万行的大查询首屏会明显卡顿。把 FetchAll 关掉、配合 FetchRows 分段取值,可以让大数据量查询只取当前需要的部分。代价是连接不能提前关闭,游标保持打开状态。用不用取决于业务是「全量处理」还是「翻页展示」,全量统计类任务用默认值没问题,交互式查询建议按需取。

4.2 参数化查询:两种传参方式,一份防注入保障

MyDAC 的参数分命名参数和位置参数。命名参数是默认模式,SQL 里写:name,调用时用ParamByName('name')赋值;位置参数在 SQL 里用?占位,调用时用Params[i]按下标赋值。命名参数可读性和可维护性都好一截,常规项目没有理由不用它。

Query.SQL.Text := 'SELECT * FROM users WHERE login_name = :login AND status = :st'; Query.ParamByName('login').AsString := EditLogin.Text; Query.ParamByName('st').AsInteger := 1; Query.Open;

参数化带来的第一个收益是防注入。字符串拼接写法WHERE login_name = ''' + EditLogin.Text + '''',用户输入abc' OR '1'='1就能改写 SQL 语义;参数化把输入当作普通值传给服务器,输入内容完全不参与 SQL 解析,注入路径从根上堵死。第二个收益是类型边界清晰:AsDateTime、AsInteger、AsString 分别告诉组件按什么类型编码,日期字段不会再被转成字符串,索引也能正常命中。

性能习惯也要在这里建立:同一句 SQL 反复执行时,复用一个 TMyQuery 实例,只改参数值再 Open,而不是每次新建组件重新解析 SQL。MyDAC 对重复 SQL 文本有预处理缓存,复用在批量操作里收益明显。插入几千行数据时,一个循环里反复 ExecSQL 同一个查询对象,比每次新建对象快一个数量级。

4.3 TMyScript 批量执行与事务提交策略

多条 SQL 需要按顺序执行成一段脚本时,TMyScript 比循环调 TMyQuery 更合适。它接收一整段 SQL 文本,内部拆成单条语句顺序发给服务器。

Script := TMyScript.Create(nil); try Script.Connection := MyConn; Script.SQL.Text := 'CREATE TEMPORARY TABLE tmp_sale AS SELECT * FROM sale WHERE dt = :d;' + 'UPDATE tmp_sale SET status = 1;' + 'INSERT INTO sale_archive SELECT * FROM tmp_sale;'; Script.ParamByName('d').AsDateTime := Date - 1; Script.Execute; finally Script.Free; end;

TMyScript 默认把整段脚本放进一个隐式事务,任意一条语句失败,前面已执行的语句全部回滚。这个默认行为保证了脚本整体原子性,但有两个使用边界要记住。

第一个边界:脚本文本里不要自带 START TRANSACTION、COMMIT 这类事务控制语句。组件层管理事务状态时,遇到脚本内嵌事务语句会失去对会话状态的判断,后续提交回滚可能落到错误的事务上。第二个边界:大批量脚本在单事务里执行时,回滚段会膨胀得很快,超过 InnoDB 日志限制直接报错。我的一般做法是按批提交:每 500 条左右开一个显式事务,Commit 后开下一批。每批内原子、批间可恢复,既不会丢数据,也不会拖垮服务器日志。

显式事务本身用 TMyConnection.StartTransaction / Commit / Rollback 控制。默认 Autocommit 模式下每条 SQL 独立提交,查询类应用不用额外处理;写操作密集时,手动开事务把多次写合并成一次提交,显著减少磁盘同步次数。事务期间不要夹带长时间 UI 交互,长事务持有的行锁会把在线业务堵死,这是生产事故的常见来源。

5. 避坑排查:MyDAC v5.10 最常见的 5 类翻车现场

这一章是一线踩坑记录,每一条按「现象 → 原因 → 解决」写,方便直接对照。前四类是运行期和编译期的高发问题,最后一类是发布管理上的通病。

5.1 现象:程序启动报 libmysql.dll 找不到或加载失败

现象:编译一切正常,程序运行到 Connect 时弹窗提示找不到 libmysql.dll,有的环境报的是「无法定位程序输入点」。

原因:MyDAC 运行期通过动态加载方式调用 MySQL 客户端库,dll 不在程序搜索路径下就加载失败。另一种高发原因是客户端库版本和服务器大版本不匹配:服务器是 5.7,拿的是 4.x 时代的老 dll,握手协议对不上,表现是能加载但 Connect 报协议错误。dll 属于 MySQL 发行包的一部分,不在组件源文件里。

解决:按服务器大版本取对应 libmysql.dll,放到程序 exe 同目录。位数必须匹配:32 位程序配 32 位 dll,64 位程序配 64 位 dll,混用会在加载阶段直接失败。发布目录里把 dll 列为必带文件,部署清单上写明从哪个 MySQL 安装目录获取,避免现场运维随手找一个版本凑数。

5.2 现象:中文乱码,能连上但读出来全是问号

现象:数据库里中文正常,程序查询结果显示问号或乱码;反过来,程序写入后再查库,库里是乱码。

原因:连接层字符集和服务器/表字符集不一致。Charset 属性管客户端与服务器通信时的编码约定,表的 charset 管存储。表是 utf8 而连接层配成 gbk,读取和写入都会按错误规则转换。另一个被低估的原因是 Options.UseUnicode 没开启,Delphi 侧字符串按 ANSI 传递,遇到多字节字符发生截断。

解决:先查服务器的 character_set_server、character_set_client 和业务表默认字符集,把 MyDAC 的 Charset 配成一致,同时把 Options.UseUnicode 置为 True。改完必须做三角验证:SELECT 读一条、INSERT 写一条、再 SELECT 读回比对,三个环节都正确才算真正解决。很多人只验证了读,写入方向还是错的,上线后数据进库就花了。

5.3 现象:程序空闲一段时间后,首个查询报 Lost connection

现象:程序启动后一切正常,闲置一会儿再操作,第一个查询报「Lost connection to MySQL server during query」或「MySQL server has gone away」。

原因:MySQL 服务端 wait_timeout 默认 8 小时,连接空闲超过该时间被服务器主动断开;更短的场景是网络设备或防火墙的空闲老化策略先断了 TCP 连接。MyDAC 侧不实时感知连接已死,下次真正发送数据时才收到错误,所以表现为「空闲后第一次操作必报错」。

解决:两层配合。第一层给 TMyConnection 设置合理的 ConnectTimeout,避免服务器不可达时界面无响应;第二层在业务代码里捕获连接丢失类错误,捕获后重建连接再重放最近一次操作。把 wait_timeout 调大治标不治本,服务端有连接数上限,空闲连接挂太多会耗尽连接数。规范做法是客户端做重连,而不是依赖服务器放宽超时。

5.4 现象:编译源文件时连环报错,Unit not found 刷屏

现象:把 v5.10 源文件放进 IDE 编译,报找不到某个单元,手动补了搜索路径又报下一个,像挤牙膏。

原因:三类。第一类是 IDE 版本跨代太大,源文件的条件编译分支没覆盖新版编译器指令,这是硬伤。第二类是库路径只加了子目录,漏了源码根目录,部分单元在更深层目录里,搜不到。第三类是机器上同时存在多套 DCU 产物,编译器随机命中旧货,报错指向的单元和实际原因对不上。

解决:先确认源文件包声明支持的 IDE 版本范围,硬上太新的 IDE 属于把源码往不兼容环境里塞,编译不过不是源码问题。确认兼容后,把源码根目录整体加进 Library Path,不要只加子目录;编译按「连接核心 → 命令层 → 设计期包」依赖顺序逐个来;动手前清空所有输出目录,排除旧 DCU 干扰。三步走完还报错的,把第一个错误信息贴出来按依赖链逐层解决,别被后面的报错带偏。

5.5 现象:设计期连接串被带进生产环境,连错库

现象:程序部署到生产后登录的是开发库,或者报密码错误,开发环境却一切正常。

原因:设计期在对象监视器里配置过连接参数(尤其是 Database 和 Password),这些属性被序列化进了窗体的 DFM 文件。发布时没有清理,运行时如果代码里没有显式覆盖,程序就按 DFM 里的参数去连。更隐蔽的是连接参数分散在两处:DFM 一份、代码一份,行为随代码路径变化,极难排查。

解决:从习惯上根治。设计期只填服务器地址和端口这类非敏感参数,密码、默认库一律运行时赋值;连接信息整体外置到配置文件,程序启动时统一读取、统一赋值,DFM 里不残留任何连接属性。发布前用文本搜索全量扫描 .dfm 文件,确认没有Password=字段残留。我把这条写进了发布检查清单,每次发版前必跑一次。

6. 进阶:从 v5.10 源码定位请求链路,自己定制通信日志

连接、读写、排错都过了一遍,最后聊一个能把 MyDAC 从黑匣子变成透明盒子的技巧:给组件挂事件,把一条 SQL 从发起到返回的完整过程记录下来。

MyDAC 的查询从调用 Open 开始,内部经过 SQL 解析、参数绑定、发送到服务器、等待响应、按元数据组装字段、逐行取回结果,最后才是业务代码拿到数据。想知道链路在哪一段变慢、在哪一段报错,最直接的手段是使用 TMyConnection 自带事件。BeforeConnect/AfterConnect 记录建联时刻,AfterExecute 看到每条语句执行完成,OnError 拿到服务器返回的错误码。把这些事件里的时间点、SQL 文本、执行耗时写进日志,就得到了一份请求流水。

// 挂到 TMyConnection 的 AfterExecute 事件上,记录每条 SQL 的执行耗时 procedure TForm1.MyConnAfterExecute(Sender: TObject); begin LogLine(Format('SQL: %s | 耗时: %d ms', [MyConn.LastCommandText, MyConn.ExecutionTime])); end;

LastCommandText 保存最近一次执行的完整 SQL,参数值已绑定进去,可以直接看到实际发出去的语句;ExecutionTime 是本次执行在客户端统计到的总耗时。这两条信息足够区分问题在网络还是服务端:SQL 本身很快但总耗时高,多半是网络往返多;服务器执行慢,就要去 MySQL 侧看慢查询日志。比这更底层的手段是直接改源文件,在通信缓冲区收发边界打桩打印原始报文。不过这属于牵一发动全身的改动,对核心单元的任何修改都会迫使所有依赖包重新编译,我通常只在怀疑协议级问题时才动它,常规场景挂事件就够了。

自己动手做过一次链路记录后,再遇到连不上的问题,思路会完全不一样:不再是猜,而是看日志卡在哪一段。这个习惯后来帮我处理过好几起跨网络的疑难连接故障,省掉大量来回比对的时间。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询