简介MySQL Data Access ComponentsMyDACv5.10 源文件包面向使用 Delphi 7 至 2007 与 CBuilder 的数据库开发者提供直连 MySQL 服务端的原生组件方案不依赖第三方库或中间件覆盖数据读写、事务、批处理、存储过程与异步调用等场景适合需要深度定制的中高级开发者。整包共 1403 个文件压缩后约 1.39MB以 360 个 pas 源文件、146 个 dpk 组件包、107 个 dfm 窗体、142 个 res 资源及 81 个 bdsproj、24 个 dproj 工程文件为主另含 cpp、dpr、bpk 等 CBuilder 与旧版工程文件和多套 Make.bat 构建脚本构成可直接编译的源码树包内附 HLP 帮助、History、FAQ 与许可协议便于查阅 API 与版本变更。已有 169 人学习下载读者可据此编译调试并改造组件源码按需裁剪驱动层、扩展连接与事务逻辑也可借工程组织与批处理脚本了解 Delphi 组件库的构建流程。1. MyDAC v5.10 源文件到底解决什么问题Delphi 老项目要连 MySQL装上组件包、拖一个 TMyConnection 上去多数人跑到能查出数据就停了。真正回头去找 MyDAC v5.10 源文件的往往是另一类麻烦编译出来的 exe 换台机器就起不来字符集怎么调都出乱码某个查询慢到离谱却看不到组件内部到底发了什么 SQL手里只有编好的 bpl 和 dcu这些问题只能靠猜。源码版的价值不在功能多而在于能重编译、能单步、能改。下面按能不能用、怎么装、参数怎么设、坑在哪的顺序拆开讲。2. 把 MyDAC v5.10 源文件编进 IDE包结构、搜索路径与编译顺序拿到一份 Delphi 组件源码最常见的失败不是代码有错而是编译顺序和搜索路径没理清先编了设计期包它依赖的运行期包还没生成或者运行期包编到一半编译器去 System 目录里找了一份旧的同名单元。理顺这三件事后面全是机械操作。2.1 先分清源码包里的三层单元一套完整的 MySQL 数据访问组件源码通常分三层组织。最底下是协议与类型层负责跟 MySQL 服务端握手、打包解包、字段类型映射这一层跟 VCL 无关理论上可以拿去控制台程序里跑中间是数据集层把协议层的结果包装成 TDataSet 的形态提供 TMyQuery、TMyTable、TMyStoredProc 这些能拖到窗体上的组件最上面是设计期层只包含 Register 过程和属性编辑器编译产物是 dcl 开头的设计期包。三层对应三种编译产物协议层和数据集层合成运行期包运行期 .bpl 一堆 .dcu设计期包单独一个只被 IDE 加载。搞混的代价很直接——你把设计期包装进 IDE编译项目时却提示找不到 DBAccess 之类的单元因为运行期包的 dcu 输出目录没加进 Library Path。我一般会先在源码根目录下按 Core、Datasets、Design 建三个子目录把对应的 .pas 归位再建一个 Lib\Win32\Release 作为统一输出目录。目录结构定死之后后面命令行编译、IDE 编译、同事接手用的都是同一套路径不会出现“我这能编你那不能编”的玄学。单元文件的实际命名以包内为准认的是层次而不是具体名字。顺手在包里翻一下有没有 .inc 条件编译文件。这类组件通常用一组开关控制功能裁剪比如是否启用 SSL、是否启用连接池、是否带本地化资源。编之前先确认开关组合编完再改开关等于白编一遍。2.2 用命令行批处理重编译运行期包IDE 里点 Install 编译一次就够用了但源码版的意义在于“改完能立刻重编”手工点太慢。写成批处理改一行源码跑一次脚本十秒钟出新包。:: build_runtime.bat —— 放在源码根目录先编运行期包 set DCCC:\Program Files\Delphi\bin\dcc32.exe set SRC.\Source set OUT.\Lib\Win32\Release :: -B 全量重编 -Q 静默 -$D- 关调试信息 -$L- 关运行时范围检查 :: -U 单元搜索路径 -NU/-NB/-NH 分别指定 dcu、bpl、hpp 的输出位置 %DCC% -B -Q -$D- -$L- ^ -U%SRC%\Core;%SRC%\Datasets ^ -NU%OUT% -NB%OUT% -NH%OUT% ^ %SRC%\Packages\mydacXX.dpk if errorlevel 1 ( echo [FAIL] 运行期包编译失败先看上面第一条 Error exit /b 1 ) echo [OK] 运行期包已输出到 %OUT%这段脚本的关键在三个参数。-U是单元搜索路径只放 Core 和 Datasets故意不放 Design这样一旦数据集层误引用了设计期单元编译当场报错不会拖到安装阶段才暴露。-NU指定 dcu 输出目录必须和后面 IDE 里配的 Library Path 完全一致路径差一个反斜杠都可能让 IDE 去别处找旧 dcu。-B是全量重编源码版每次改完都建议全量增量编译在改了 .inc 开关的时候会给你留下一个半新半旧的包症状是运行期报“找不到入口点”很难查。包里那个mydacXX.dpk的 XX 是 IDE 版本代号换成你自己环境对应的那个包文件名以源码包里的实际文件为准。如果源码包同时给了 .dpk 和 .dproj命令行走 dcc32 更稳msbuild 那套依赖 MSBuild 的 Delphi 目标文件老版本 IDE 上经常因为框架版本对不上直接罢工。编译完立刻做一次验证别急着装设计期包:: 检查输出目录里 dcu 数量是否和源码 .pas 数量对得上排除设计期单元 dir /b %SRC%\Core\*.pas %SRC%\Datasets\*.pas | find /c .pas dir /b %OUT%\*.dcu | find /c .dcu两个数字差太多说明有条件编译分支被跳过了或者某个单元因为缺依赖根本没参与编译。这一步花三十秒能省掉后面半小时的“组件装上了但一拖就报错”。2.3 安装设计期包与组件面板注册运行期包编好、Library Path 配好之后设计期包才轮到上场。IDE 里走 Component → Install Packages → Add选中输出目录里的 dcl 开头的 .bpl。装完组件面板应该多出一个分类页里面是 TMyConnection、TMyQuery 这一组。装不上时按这个顺序查。第一设计期包和运行期包必须同一套编译配置一个是 Release 一个带调试信息加载时报“Cannot load package”或者“单元版本不匹配”这类报错信息很含糊实际原因就是 .dcu 的编译指纹对不上。第二Library Path 里旧版本的路径要删干净同一台机器装过别的版本组件时IDE 会优先命中先出现的那条路径。第三包里如果带了资源文件.res资源编译产物也要跟着拷到输出目录缺了它设计期包能加载但组件图标全是空白。装好之后别急着写业务先拖一个 TMyConnection 到空白窗体把 Server、Port、Username、Password、Database 填上Connected 设 True。这一步能连上说明协议层、字符集、网络都通了后面出问题一定出在数据集层或者你的 SQL 上排查范围直接砍一半。3. TMyConnection 的连接参数直连协议、字符集与连接池怎么设连接串是这类组件里最容易被复制粘贴毁掉的部分。网上抄来的一段初始化代码字段名对得上、能跑通但池大小、超时、字符集全是默认值一到并发或者跨机房就原形毕露。这一章把几个真正影响稳定性的参数挑出来讲。3.1 直连模式与客户端库模式的选择边界这类组件一般给两种连接方式。一种是直连组件自己实现 MySQL 的通信协议不需要任何外部动态库exe 拷过去就能跑另一种是走官方客户端库需要目标机器上有对应的 dll好处是协议兼容性完全跟着官方走遇到新版本服务端的认证插件、新的加密方式时不容易卡住。选哪边看两个条件。客户端是绿色部署、机器不受你控制比如装在客户内网的一堆终端上直连模式省掉“少一个 dll 就启动失败”的售后电话这是压倒性优势。反过来服务端版本比较新、开了比较激进的认证方式或者要用到一些只有客户端库才完整支持的扩展能力那就老老实实走客户端库模式把 dll 一起打进安装包。对比项直连模式客户端库模式外部依赖无需要对应平台的客户端动态库部署体积小多出几 MB 的库文件新认证方式兼容取决于组件版本可能要等更新跟着官方库走排错难度组件内部有源码可单步多一层黑匣子适合场景绿色部署、内网批量终端服务端版本新、功能依赖完整协议这段判断做完再往下配参数才有意义。用直连模式时字符集、超时、包大小这些全靠组件自己的 Option 控制用客户端库模式时一部分行为会继承 dll 那边的默认值两边都调一遍容易出现“改了这个覆盖那个”的情况建议只在一处集中设置。3.2 一份可以直接抄的连接初始化代码下面这段是常规写法重点是每个属性后面都写清了为什么这么设照抄之后按你的环境改值即可。procedure TDM.InitConnection; begin FConn : TMyConnection.Create(nil); try FConn.Server : 10.0.0.21; // 建议走内网地址公网域名解析抖动会直接体现为连接超时 FConn.Port : 3306; FConn.Username : app_rw; // 读写账号报表类只读功能另开只读账号 FConn.Password : GetPwdFromCfg; // 不要硬编码从配置文件或凭据服务取 FConn.Database : erp_main; FConn.LoginPrompt : False; // 服务端程序必须关否则弹登录框卡死后台线程 // 协议与传输属性挂在 Connection 下还是 Options 下随版本略有差异 // 以 IDE 对象检视器实际显示为准名字对不上就按同名项设置 FConn.Options.Protocol : mpTCP; FConn.Options.Compress : True; // 跨机房或广域网必开内网同交换机可关掉省 CPU FConn.Options.UseUnicode : True; // 与 utf8mb4 配套关掉必出乱码 FConn.OnConnect : DoAfterConnect; // 连接建立后的会话级设置统一挂这里 FConn.Pooling : True; // 内置连接池服务端程序默认开 FConn.MaxPoolSize : 40; // 起步值按并发线程数 × 1.2 估 FConn.ConnectionTimeout : 15; // 秒太短会在网络抖动时雪崩重连 FConn.Connect; except on E: Exception do begin LogError(MySQL 连接失败: E.Message); FreeAndNil(FConn); raise; end; end; end; procedure TDM.DoAfterConnect(Sender: TObject); begin // 会话级设置字符集、时区、SQL 模式一次设完不依赖服务端默认值 (Sender as TMyConnection).ExecSQL(SET NAMES utf8mb4); (Sender as TMyConnection).ExecSQL(SET time_zone 08:00); (Sender as TMyConnection).ExecSQL(SET SESSION sql_mode STRICT_TRANS_TABLES); end;逻辑上就三块。第一块是基础寻址把 Server、Port、账号、库名固定下来LoginPrompt一定要关服务端或后台线程里弹出一个登录对话框表现就是进程卡死、日志一片空白。第二块是传输层压缩开关按网络距离决定Unicode 开关必须和库的字符集配套。第三块是会话级设置放在 OnConnect 里最省心——连接池复用连接时会话状态是跟着物理连接走的只要第一次建连设好了后续取出来的连接就都带着这套设置不会出现“第一个请求正常第二个请求乱码”这种只有池化场景才有的怪现象。字符集这块多说一句。不同版本里设置字符集的入口可能是连接属性也可能是 Options 下的子项名字对不上很正常。稳妥做法是两者都用属性有的就设属性同时在 OnConnect 里补一条SET NAMES两条路都走一遍不会冲突还能兜住版本差异。3.3 连接池的四个必调参数连接池默认值是按“能跑”设计的不是按“能扛”设计的。四个参数建议在项目初期就定下来后面改要动回归测试。参数常见默认建议区间调错的后果Pooling关闭服务端程序开不开池每个请求建连一次握手开销压垮 QPSMaxPoolSize100 左右2050设太大数据库端连接数打满反而互相等待ConnectionTimeout0 或较长1015 秒设太长故障时线程全堵在等连接设太短抖动即报错Compress关闭广域网开、同机房关同机房开压缩CPU 白白多烧一截MaxPoolSize 的估算方法很朴素把应用的并发处理线程数数出来乘 1.2 到 1.5再和数据库的 max_connections 除一下取小的那个。见过太多项目直接写 200结果数据库端连接数被打满DBA 那边看到的是一堆 Sleep 状态的连接应用这边却在报“too many connections”两边互相甩锅。宁可把池设小、把超时设短让压力早点以报错的形式暴露出来也别让它悄悄堆积成一个看不出来的雪崩。4. TMyQuery 取数与写库FetchAll、UniDirectional 与批量装载连接通了之后性能问题九成出在数据集组件上。同样是SELECT * FROM t_order开不开 FetchAll、是不是单向游标、关闭前有没有取完内存占用能差一个数量级出事的方式也完全不一样。4.1 大结果集为什么先调 FetchAll默认情况下很多数据集组件会把整个结果集一次性拉到客户端好处是断开连接后还能翻记录坏处是碰上一张几百万行的表进程内存瞬间起飞。关掉 FetchAll 之后行为反过来查询只把游标开到服务端记录按需一条条取内存平稳但连接被这个游标一直占着而且必须把记录取完或者显式关闭才能拿这条连接干别的事。// 场景导出/统计类查询结果集可能几十万行只要顺序读一遍 qry.Close; qry.Connection : DM.Conn; qry.FetchAll : False; // 服务端游标按需取 qry.UniDirectional : True; // 单向只读省掉双向缓冲速度明显更快 qry.SQL.Text : SELECT id, order_no, amount, created_at FROM t_order WHERE created_at :d1 AND created_at :d2 ORDER BY id; qry.ParamByName(d1).AsDateTime : FStartDate; qry.ParamByName(d2).AsDateTime : FEndDate; qry.Open; try while not qry.Eof do begin WriteRowToCsv(qry); // 边取边处理内存只跟单条记录有关 qry.Next; end; finally qry.Close; // 单向游标必须显式关闭否则连接不归还 end;三个参数的配合关系要记牢。FetchAll : False换来低内存代价是连接占用时间长所以这类查询一定要单独走一条连接别和界面上的其他查询共用UniDirectional : True只对顺序读有效一旦你要RecNo、要往回翻、要排序后再定位就不能开开了在运行期直接抛异常ORDER BY加在 SQL 里而不是客户端排序配合单向游标才有意义客户端排序等于把整个结果集又拉回内存前面的优化白做。判断标准很简单这个结果集要不要在界面上反复浏览要就保持 FetchAll 为真但必须加分页或者强制加 WHERE 条件不要只是导出、对账、跑批那就关掉用单向游标顺序扫。这两条路没有中间态硬要混着用就是给自己埋雷。qry.Close放在 finally 里不是形式主义。单向游标没关连接池里那条连接就一直占着高压下几次异常就能把池耗空症状是过一会儿所有查询都开始超时重启进程立刻恢复——这种“重启治百病”的问题十次有八次是游标或连接没释放。4.2 批量导入用装载组件不要循环 ExecSQL往 MySQL 灌十万行数据最差的做法是写个 for 循环每行拼一条 INSERT 然后 ExecSQL。这样一次一个往返十万次网络交互跑一晚上都算快的。更隐蔽的坑是如果 autocommit 是开的每行还各自带一次提交磁盘刷十万次机器直接趴窝。正确的做法是用组件自带的批量装载器它把多行数据打包成一条多值 INSERT或者走 LOAD DATA 语义发过去往返次数从十万降到几百。procedure TDM.BulkInsertOrders(AData: TDataSet); var Loader: TMyLoader; begin Loader : TMyLoader.Create(nil); try Loader.Connection : FConn; Loader.TableName : t_order_stage; // 中间表先落库再在服务端做清洗 // 只导指定列顺序必须和数据集字段一一对应 // 不给自增主键、不给有默认值的列少给比多给安全 Loader.Columns.Clear; Loader.Columns.Add(order_no); Loader.Columns.Add(customer_id); Loader.Columns.Add(amount); Loader.Columns.Add(created_at); FConn.StartTransaction; try Loader.LoadFromDataset(AData); // 全量灌入内部按批拆分 FConn.Commit; except FConn.Rollback; raise; end; finally Loader.Free; end; end;调用方式和属性名在不同版本间有些出入认准两件事就够了一是必须显式指定列清单靠位置匹配字段顺序错了不会报错只会把 A 列写进 B 列这种错误在数据校验环节才会暴露代价极大二是整批必须包在一个事务里要么全进要么全不进否则中途一条脏数据失败前面已经写进去的几万行全成了需要人工清理的垃圾。如果因为版本原因没有装载组件退而求其次的方案是把 autocommit 关掉把 INSERT 拼成每批 500 到 1000 条的多值语句手动控制提交频率。这个土办法比循环 ExecSQL 快两个数量级缺点是出错时定位不到具体哪一行所以生产环境里只当降级预案用。4.3 事务和 CachedUpdates 的取舍事务这块没什么可犹豫的凡是跨多表的写操作一律显式StartTransaction/Commit/Rollback别指望自动提交。真正需要想清楚的是要不要开 CachedUpdates。开了之后所有的增删改先记在客户端的变更日志里不发给服务端直到你调 ApplyUpdates。适合的场景是主从表一起编辑——主表新增一行要拿到自增主键才能填子表的外键缓存在客户端可以先把关系理顺再一次性提交。不适合的场景是高频单行写入缓存一层只是徒增一次内存拷贝和一次状态维护。用 CachedUpdates 时有个必须注意的点变更日志不会自己清。ApplyUpdates 之后如果不调 CommitUpdates日志一直在涨界面上改了十次保存十次第九次的数据还在日志里排着队最终表现就是“我明明只改了一行数据库里却更新了十几条记录”。相关的事件回调更新前、更新后、更新出错该挂就挂出错时至少能在日志里看到是哪张表、哪条记录出的问题而不是一个光秃秃的异常。qry.CachedUpdates : True; qry.UpdateMode : upWhereKeyOnly; // 只按主键定位避免 Where 全字段导致更新不到 // ... 界面上做增删改 ... qry.ApplyUpdates; // 可能抛异常外面套 try qry.CommitUpdates; // 必须调否则变更日志不清下次 Apply 会重复提交UpdateMode这个细节容易忽略。默认用全字段做 WHERE 条件时只要有一个字段被别的会话改过你的更新就影响 0 行而且不报错静默失败。改成只按主键定位冲突变成后写覆盖至少行为是确定的、可预期的。5. 上线前必查MyDAC 源文件版五个高频翻车点排查前四章把能跑通的路径走完了这一章全是踩坑记录。每条按现象、原因、解决写遇到对得上的直接照做。5.1 编码与时区类的两个坑现象中文全变问号或者一堆乱码英文数字正常。库里的表已经是 utf8mb4客户端代码也没做转换。原因连接建立时的会话字符集没设对三次握手完成后服务端用的是它自己的默认字符集跟你程序里的字符串编码对不上。只看表的字符集是不够的连接字符集、表字符集、字段字符集三者一致才行。解决在 OnConnect 里统一执行一次SET NAMES utf8mb4同时把连接组件的 Unicode 相关开关打开。设完用一条最简单的语句验证SELECT 中文测试返回正常再往下走。这个验证要进冒烟测试清单因为不同机器上组件命中不同的连接路径表现可能不一致。现象所有时间字段取出来都比实际早或晚 8 小时日期类型DATE却完全正常。原因时间戳类型的转换是在会话时区下做的程序里的 TDateTime 按本地时间理解服务端按自己的时区返回中间差了一个偏移。日期类型不带时区信息所以不受影响这个对比正好能帮你确认问题定位准确。解决连接建立后固定会话时区SET time_zone 08:00让服务端和客户端约定用同一个偏移。更彻底的做法是时间戳统一用 UTC 存、显示时再转本地时区但改造量大的老项目先把会话时区钉死就能消掉绝大部分问题。5.2 编译与部署类的两个坑现象IDE 里一切正常打包发给测试同事双击就报“找不到 XXX.bpl”或者启动即闪退事件查看器里只有一条无用的模块加载失败。原因项目用了运行期包但没把 .bpl 随 exe 一起发布或者装了源码版之后仍然是动态链接运行期包而目标机器上压根没装这套组件。解决打开项目选项把“使用运行期包”这一项根据场景定死——要绿色部署就改成把运行期库一起静态链接进 exe代价是体积涨几 MB要走动态包就必须把对应的 .bpl 打进安装目录并确认能被搜索到。两种模式别混着来开发机上一半静态一半动态本机照样能跑一换机器立刻崩。现象换了个 IDE 版本重编源码包报一堆“单元版本不匹配”或者“找不到入口点”删了重装也没用。原因机器上同时存在多个版本的组件 dcuLibrary Path 里旧的路径排在前面编译器命中了旧 dcu链接时又跟新 bpl 对不上。解决把所有旧版本的搜索路径从 IDE 全局配置和项目配置里逐条删干净dcu 输出目录也清空重编。判定是否彻底的办法全量重编看编译日志里每个单元的实际加载路径只要出现不在你指定的输出目录下的路径就还没清干净。5.3 连接与游标类的一个坑现象系统跑十几分钟后开始大面积超时重启应用立刻恢复数据库端监控看起来连接数不高。原因连接池被耗尽但不报警。常见诱因有两个单向游标没关闭物理连接一直被游标占着不归还或者某个异常分支里 Create 了数据集没 Free泄漏的是数据集对象连带它持有的连接引用。解决把所有数据集操作套进 try/finallyClose 和 Free 都写进去。再在连接池上加监控定期把当前占用数、空闲数、等待数打到日志里出问题时看一眼曲线就知道是泄漏还是真的并发太高。这个日志平时没人看出事那十分钟能救命。6. 改源码之前先做基线用监控组件抓真实瓶颈源码在手最大的诱惑是先去改代码。我吃过这个亏花了两天把协议层的包大小调优了一遍性能只涨了 3%回头一查真实瓶颈是某条查询没走索引全表扫描。所以动源码之前先做一次能重复的基线测量。基线要靠观测而不是靠猜。组件通常自带监控工具或事件把它们挂上记录每条 SQL 的文本、执行耗时、返回行数把慢查询按耗时排序前十条看完再决定要不要动代码。观测指标采集方式正常表现异常信号单条 SQL 耗时监控组件记录与数据库端慢日志量级一致客户端耗时远大于服务端说明在等网络或等连接每秒新建连接数连接池计数峰值后迅速回落持续高位说明池没命中每次都新建结果集行数监控记录与业务预期一致预期几行却返回几万行多半是 WHERE 条件丢了内存占用曲线进程监控平稳锯齿单调上涨典型是游标或数据集没释放这套东西搭起来不超过一天收益是把“我觉得慢”变成“这条 SQL 慢了 400 毫秒”。有了基线改源码才有对照改完重跑同一批查询耗时、连接数、内存三条曲线一起看任何一条变差都说明改动有问题哪怕总体变快了也要查清楚是哪一块被牺牲掉了。真要动源码从最小的切入点开始。第一优先级是加日志在协议层的收发位置打点把完整的 SQL 文本和参数值落盘——这一步不改变任何行为只是把黑匣子打开而且后面的所有优化都要靠它验证。第二优先级才是改算法或改缓冲策略改完必须有基线数据撑着别凭感觉。验证方法说具体一点准备一份固定数据集和固定查询清单跑三轮取中位数每轮之间重启进程避免池状态干扰。改前改后用的是同一份脚本、同一台机器、同一个数据库实例任何一项变了结论就不成立。这套流程听起来繁琐但它能挡掉至少一半的“优化完反而更慢”。最后一条是血泪经验别在同一个迭代里既升级组件版本又改业务 SQL。两者叠加之后性能变好你不知道是谁的功劳变差也定位不到原因只能整批回滚白干一轮。分两次上线中间留一天观察后悔药才有得吃。希望帮到你。本文还有配套的精品资源点击获取