Delphi连接MySQL:libmysql.dll调用与dbExpress选型排错指南

发布时间:2026/10/20 15:44:19
Delphi连接MySQL:libmysql.dll调用与dbExpress选型排错指南
简介资源面向Delphi开发人员聚焦如何在不借助OLE DB的前提下直接通过MySQL官方客户端库mysql.dll完成数据库连接与基本操作从而省去ADO等中间层的映射开销更适合关注性能或需要轻量化部署的项目。整个压缩包共53个文件主要由Delphi单元源码pas、工程文件dpr/dfm/res、字符集参考文档html以及说明文档与许可协议文本组成压缩后仅87KB便于快速查阅和分发。包内示例覆盖预编译SQL语句的创建执行、多线程连接的安全使用以及不同Delphi版本的适配工程同时提供数十种字符集对应的html帮助页方便解决编码与数据乱码问题。配套动态库交互单元与平台定义头文件封装了函数接口与平台定义从LoadLibrary、GetProcAddress到mysql_init/mysql_real_connect/mysql_query/mysql_close能够完整展示直接调用MySQL原生API的调用链与实现思路。已有432人浏览学习可直接套用或作为自定义数据访问层的起点适合希望绕开OLE DB中间层、实现轻量高效MySQL访问的Delphi中高级开发者。1. 老Delphi系统要连MySQL绕不开这一关mysql.dll 到底解决了什么如果你手头有一个用了十年的Delphi客户端数据库突然要从旧库迁到MySQL第一反应往往是去装ODBC驱动或者拖一个dbExpress组件。但我这几年接手的几个内部系统最后都回到最原始的方案直接调用 mysql.dll也就是 libmysql.dll连接MySQL数据库。这个标题看起来很老实际在部署、排错、性能控制上都比绕两层封装更可控。适合的读者是维护旧Delphi代码、被第三方驱动版本问题折磨过的开发者适合的场景是内网环境、客户端机器多、不想每台都装额外驱动。这篇不讲抽象概念直接讲清楚链路怎么通、参数怎么设、坑在哪。2. 四条连接路线与DLL选型为什么先查版本和位数2.1 四种常见做法libmysql.dll、ODBC、dbExpress 和第三方组件Delphi 连接 MySQL 不是只有一种做法我按实际项目中遇到过的频率排最常见是下面四条路线依赖的DLL/驱动适合场景主要缺点直接调用C APIlibmysql.dll 或老版本 mysql.dll老项目、无驱动环境、要精细控制连接行为要自己声明函数原型代码量稍大dbExpress 驱动dbxmysql.dll libmysql.dllDelphi 自带框架能配合 TSQLQuery 等组件驱动版本和 Delphi 版本匹配敏感ODBC 驱动myodbc8a.dll / odbcad32 配置临时排查、能容忍安装驱动的环境每台客户端都要装驱动版本容易乱第三方连接组件组件自带动态库不想碰底层细节多一层依赖出问题不好定位如果你只想快速连一次ODBC 加 TADOConnection 确实最快。但只要是给多个客户端分发、长时间运行的系统我建议别把 ODBC 配置写进交付清单运维很容易把驱动版本装错。标题里直接写“mysql.dll”说明提问人看到的是老教程或者老代码那多半走的是第一条路线也就是直接调用 MySQL 官方客户端动态库这也是我后面重点展开的部分。2.2 动手前先对齐三件事MySQL版本、DLL位数、VC运行库先别急着写代码。我见过太多连不上的情况最后发现根本不是代码问题是三件前置条件没有对齐。第一确认服务端 MySQL 的版本大版本5.5、5.7 和 8.0 在认证插件、字符集默认值上差别很大。比如 MySQL 8.0 默认认证插件是 caching_sha2_password老版本 libmysql.dll 不认识这个插件连上去就会报认证错误这个在第 5 章会单独讲。第二确认你的 Delphi 编译出的 exe 是 32 位还是 64 位然后选择对应位数的 libmysql.dll。32 位程序加载 64 位 DLLLoadLibrary 会直接失败或者运行到一半崩溃。第三libmysql.dll 依赖 VC 运行库老版本依赖 VC2008、VC2010新版本依赖 VC2015-2022。目标机器上缺运行库表现就是“找不到指定的模块”但 DLL 明明放在 exe 同目录。我的一般做法是在一台干净测试机上先把服务端版本、exe 位数、DLL 位数、VC 运行库版本用一个小脚本记下来再去动代码。这三样对齐连接链路才谈得上稳定。2.3 libmysql.dll 和 mysql.dll 的关系文件名差异为什么重要标题写的是“mysql.dll”但 MySQL 官方从 5.0 开始Windows 客户端库正式文件名就是 libmysql.dll。mysql.dll 是 MySQL 3.x、4.x 时代的老叫法很多老教程里混着写。实际项目里我见过有人把 libmysql.dll 改名成 mysql.dll 来兼容旧代码这种做法能用但容易让人困惑。64 位环境下MySQL 5.x 时代还会看到 libmysql64.dll 这种命名8.0 之后官方统一叫 libmysql.dll区分位数靠安装目录而不是文件名。所以当你看到标题“delphi mysql.dll 连接mysql数据库”时心里要明白这个 DLL 指的就是 MySQL 官方 C API 客户端库。下面所有代码都按 libmysql.dll 来写如果你手上只有 mysql.dll函数导出名是一致的加载路径换一下即可。3. 直接调 libmysql.dll 连接从加载DLL到跑通第一条SELECT3.1 声明函数指针只导出你用得上的 C API直接调用 DLL第一步是把 C 函数原型翻译成 Delphi 可用的函数指针类型。MySQL C API 有一堆函数但你日常连接、查询、取结果其实用不到几个。我一般只声明这一组unit MySQLLibIntf; interface uses Windows, SysUtils; type // MYSQL* 和 MYSQL_RES* 在 Delphi 里统一用 Pointer 表示 TMySqlInit function(MySql: Pointer): Pointer; stdcall; TMySqlRealConnect function(MySql: Pointer; Host, User, Passwd, Db: PAnsiChar; Port: Cardinal; UnixSocket: PAnsiChar; ClientFlag: Cardinal): Pointer; stdcall; TMySqlQuery function(MySql: Pointer; Query: PAnsiChar): Integer; stdcall; TMySqlStoreResult function(MySql: Pointer): Pointer; stdcall; TMySqlFetchRow function(Result: Pointer): Pointer; stdcall; TMySqlNumFields function(Result: Pointer): Cardinal; stdcall; TMySqlFreeResult procedure(Result: Pointer); stdcall; TMySqlClose procedure(MySql: Pointer); stdcall; TMySqlError function(MySql: Pointer): PAnsiChar; stdcall; TMySqlAffectedRows function(MySql: Pointer): Int64; stdcall; TMySqlSetCharacterSet function(MySql: Pointer; Charset: PAnsiChar): Integer; stdcall; var MySqlInit: TMySqlInit; MySqlRealConnect: TMySqlRealConnect; MySqlQuery: TMySqlQuery; MySqlStoreResult: TMySqlStoreResult; MySqlFetchRow: TMySqlFetchRow; MySqlNumFields: TMySqlNumFields; MySqlFreeResult: TMySqlFreeResult; MySqlClose: TMySqlClose; MySqlError: TMySqlError; MySqlAffectedRows: TMySqlAffectedRows; MySqlSetCharacterSet: TMySqlSetCharacterSet; MySqlDllHandle: HMODULE;上面这些函数指针的类型声明全部使用 stdcall 调用约定这是 Windows 版 libmysql.dll 的导出约定用错调用约定程序会直接崩溃。MYSQL* 句柄在 Delphi 里不需要知道内部结构用 Pointer 就行。这里只声明了 11 个函数已经覆盖连接、查询、取数、释放的完整链路。如果你还要用事务额外加 mysql_autocommit、mysql_commit、mysql_rollback 三个声明方式一样。3.2 动态加载 DLL比 static external 多一层可控性Delphi 里声明 external libmysql.dll 的静态引入写法最省事但有个问题DLL 不存在时程序直接弹窗你连一句自定义错误提示的机会都没有。我一般用 LoadLibrary 动态加载这样加载失败可以给出明确提示还能在加载后逐一校验函数地址是否为 nil。function LoadMySqlDll(const DllName: string): Boolean; begin Result : False; if MySqlDllHandle 0 then Exit; // LoadLibrary 返回 0 表示失败 MySqlDllHandle : LoadLibrary(PChar(DllName)); if MySqlDllHandle 0 then raise Exception.Create(加载 DllName 失败请检查文件是否存在以及位数是否匹配); MySqlInit : GetProcAddress(MySqlDllHandle, mysql_init); MySqlRealConnect : GetProcAddress(MySqlDllHandle, mysql_real_connect); MySqlQuery : GetProcAddress(MySqlDllHandle, mysql_query); MySqlStoreResult : GetProcAddress(MySqlDllHandle, mysql_store_result); MySqlFetchRow : GetProcAddress(MySqlDllHandle, mysql_fetch_row); MySqlNumFields : GetProcAddress(MySqlDllHandle, mysql_num_fields); MySqlFreeResult : GetProcAddress(MySqlDllHandle, mysql_free_result); MySqlClose : GetProcAddress(MySqlDllHandle, mysql_close); MySqlError : GetProcAddress(MySqlDllHandle, mysql_error); MySqlAffectedRows : GetProcAddress(MySqlDllHandle, mysql_affected_rows); MySqlSetCharacterSet : GetProcAddress(MySqlDllHandle, mysql_set_character_set); // 核心函数只要缺一个就认为 DLL 不可用 Result : Assigned(MySqlInit) and Assigned(MySqlRealConnect) and Assigned(MySqlQuery); if not Result then begin FreeLibrary(MySqlDllHandle); MySqlDllHandle : 0; end; end;这段代码把函数地址绑定放在一个函数里加载失败时能精准抛出原因。注意赋值函数指针用了 MySqlInit : GetProcAddress(...) 的形式在 Delphi 7 到 XE 各个版本下都通用。参数 DllName 建议传绝对路径比如 exe 同目录下的 libmysql.dll不要只传文件名依赖系统 PATH。加载成功后再用 TMySqlInit 这类函数指针调用接口后续代码就和静态 external 没有区别。3.3 建立连接与设置字符集mysql_real_connect 的正确姿势加载完 DLL下一步就是初始化连接句柄并连接服务端。连接函数这块有几个细节容易踩第一个参数是 mysql_init 返回的句柄不是 nil连接失败时错误信息要立刻拷贝出来因为 mysql_error 返回的指针指向 DLL 内部缓冲下一次调用就会覆盖。function ConnectMySql(const Host, User, Passwd, DbName: string; Port: Integer): Pointer; var MySql: Pointer; UTF8Host, UTF8User, UTF8Passwd, UTF8Db: UTF8String; CharsetName: UTF8String; begin // 第一步初始化 MYSQL 对象 MySql : MySqlInit(nil); if MySql nil then raise Exception.Create(mysql_init 失败); try // 第二步显式设置字符集避免中文乱码 CharsetName : UTF8String(utf8mb4); MySqlSetCharacterSet(MySql, PAnsiChar(CharsetName)); // 第三步把 Delphi string 转成 UTF-8 UTF8Host : UTF8String(Host); UTF8User : UTF8String(User); UTF8Passwd : UTF8String(Passwd); UTF8Db : UTF8String(DbName); // 第四步建立连接 Result : MySqlRealConnect(MySql, PAnsiChar(UTF8Host), PAnsiChar(UTF8User), PAnsiChar(UTF8Passwd), PAnsiChar(UTF8Db), Port, nil, 0); if Result nil then raise Exception.Create(连接 MySQL 失败: string(AnsiString(MySqlError(MySql)))); except // 连接失败也必须释放句柄否则泄漏 MySqlClose(MySql); raise; end; end;这里最关键的参数是 port 和 clientflag。clientflag 传 0 表示使用默认行为如果你需要多语句执行可以传 CLIENT_MULTI_STATEMENTS这个常量值为 65536需要自己定义。UnixSocket 参数在 Windows 下传 nil。注意字符集设置必须在 mysql_real_connect 之前调用否则连接建立后还要额外发一条 SET NAMES多一次往返。连接失败时先读错误信息再调用 MySqlClose顺序不能反。3.4 执行查询和读取结果mysql_store_result 与遍历行集连接建立后执行 SQL 和读取结果集是用得最频繁的部分。mysql_query 返回 0 表示成功非 0 表示失败SELECT 类语句需要用 mysql_store_result 把结果拉到客户端再逐行遍历。function QueryAndDump(MySql: Pointer; const Sql: string): Integer; var UTF8Sql: UTF8String; Res: Pointer; Row: Pointer; RowData: ^PAnsiChar; // MYSQL_ROW 本质是 char** FieldCount: Cardinal; I: Integer; FieldPtr: PAnsiChar; begin // SQL 统一转 UTF-8 UTF8Sql : UTF8String(Sql); if MySqlQuery(MySql, PAnsiChar(UTF8Sql)) 0 then raise Exception.Create(SQL 执行失败: string(AnsiString(MySqlError(MySql)))); // 取结果集 Res : MySqlStoreResult(MySql); if Res nil then begin // 如果没有结果集比如 UPDATE返回受影响行数 Result : Integer(MySqlAffectedRows(MySql)); Exit; end; try FieldCount : MySqlNumFields(Res); // 循环取行mysql_fetch_row 返回 nil 表示取完 while True do begin Row : MySqlFetchRow(Res); if Row nil then Break; // Row 指向一个 PAnsiChar 数组长度为 FieldCount RowData : Pointer(Row); for I : 0 to FieldCount - 1 do begin FieldPtr : PAnsiChar(NativeUInt(RowData) I * SizeOf(PAnsiChar)); if PAnsiChar(FieldPtr^) nil then Write(NULL) else Write(UTF8ToString(RawByteString(FieldPtr^))); if I FieldCount - 1 then Write(#9); end; WriteLn; end; Result : 0; finally // 无论是否取完结果集必须释放 MySqlFreeResult(Res); end; end;这段代码有两个容易出错的地方一个是 MYSQL_ROW 的内存布局是连续排列的 PAnsiChar 指针数组不是二维数组所以取第 I 个字段要用指针偏移计算另一个是字段值本身是 PAnsiChar指向的数据是 UTF-8 编码转成 Delphi string 时要用 UTF8ToString不能直接赋值给 AnsiString否则中文会乱。mysql_store_result 之后结果集内存必须由 mysql_free_result 释放这个一定要放在 try/finally 里兜底。3.5 释放连接与内存try/finally 兜底上面两段代码已经展示了释放逻辑这里把原则单独强调一下。MySQL C API 里凡是返回 MYSQL_RES 的函数拿到手之后都必须释放MYSQL* 句柄也一样用完必须 mysql_close。我见过一个系统运行半个月后连接数打满查到最后就是有个分支忘记 close。释放的固定写法是procedure SafeCloseMySql(var MySql: Pointer); begin if MySql nil then begin MySqlClose(MySql); MySql : nil; end; end;把释放动作和置 nil 组合成一个过程每次用完连接后调用就不会出现重复释放的问题。mysql_close 对已经 close 过的指针再调一次在部分版本里会崩溃所以置 nil 这步不能省。4. 不想手写C API就用 dbExpressdbxmysql.dll 连线与部署4.1 TSQLConnection 最小配置DriverName 与两个 DLL 的关系如果你的 Delphi 版本较新或者项目里已经大量使用 DataSet 组件手写 C API 会显得格格不入。这时常见做法是用 dbExpress 框架连接组件是 TSQLConnection它背后需要两个 DLL一个是框架驱动 dbxmysql.dll另一个还是 MySQL 客户端库 libmysql.dll。这两个 DLL 分工不同驱动负责把 dbExpress 的调用翻译成 MySQL API 调用libmysql.dll 负责实际网络通信。很多人只拷了 dbxmysql.dll 就报错就是因为 vendor lib 没带。var Conn: TSQLConnection; begin Conn : TSQLConnection.Create(nil); try Conn.DriverName : MySQL; Conn.LibraryName : dbxmysql.dll; Conn.GetDriverFunc : getSQLDriverMYSQL; Conn.VendorLib : libmysql.dll; Conn.Params.Values[HostName] : 10.0.1.20; Conn.Params.Values[Database] : erp; Conn.Params.Values[User_Name] : app_user; Conn.Params.Values[Password] : app_pass; Conn.Params.Values[Port] : 3306; Conn.Params.Values[BlobSize] : -1; Conn.Connected : True; // 连接成功后可以操作 TSQLQuery / TSQLDataSet finally Conn.Free; end; end;这里最关键的是四个属性DriverName 决定走哪种驱动LibraryName 是 Delphi 端加载的驱动 DLLGetDriverFunc 是驱动入口函数名VendorLib 是底层客户端库。驱动名 MySQL 是 dbExpress 内置的映射名字不同 Delphi 版本映射的文件名不一样Delphi 7 时代叫 dbexpmysql.dll2007 之后普遍叫 dbxmysql.dll。所以看到老教程写 dbexpmysql.dll 别奇怪先确认你的版本。4.2 驱动文件的版本匹配和部署顺序dbExpress 的坑主要集中在这几个文件之间的版本匹配。驱动 dll 和 Delphi 版本绑定比如 Delphi 2010 的驱动不能拿去给 Delphi XE 用vendor lib 和服务端 MySQL 版本建议同大版本至少客户端库不能比服务端老太多。部署到客户机时我的习惯是把 dbxmysql.dll 和 libmysql.dll 统一放到 exe 同目录不要让驱动去系统目录找。如果连接时报“Cannot find dbxmysql.dll”或者“无法定位程序输入点”按这个顺序排查第一exe 同目录下是不是两个 dll 都在第二位数是不是一致第三驱动 dll 是不是当前 Delphi 版本自带的第四libmysql.dll 有没有依赖 VC 运行库。dbExpress 这块没有太多玄学版本对齐了基本一次通。4.3 两条路线的边界什么时候回到 mysql.dlldbExpress 看起来省事但它有个隐蔽问题调试不方便。TSQLConnection 内部会缓存元数据、管理事务出错时错误信息经过多层包装你很难判断到底是 SQL 问题、驱动问题还是网络问题。另外 dbExpress 对 MySQL 的字符集处理依赖驱动版本老版本经常出现 utf8mb4 支持不全导致 emoji 字符写入报错。所以我的边界判断标准是新项目、用 DataSet 组件多、团队熟悉 dbExpress走驱动路线老项目、只要增删改查、要明确掌控连接行为回到 C API 路线。标题既然落在 mysql.dll 上说明提问场景大概率是后者。两条路线不是替代关系而是同一层 API 的两种封装方式底层都是 libmysql.dll 在干活。5. 连接 MySQL 的 5 个经典翻车现场排查顺序与后悔药5.1 现象程序启动即报“无法找到指定的模块”这个错误最常见程序双击打开直接弹窗提示“无法找到指定的模块”或者“无法定位程序输入点”。现象发生在 LoadLibrary 之前说明 DLL 没找到或者 DLL 自身依赖的库缺失。原因往往不是 libmysql.dll 不在而是它依赖的 msvcr100.dll、libssl.dll 不在或系统 PATH 里没有。解决方法是把 libmysql.dll 放到 exe 同目录并把用到的 VC 运行库一起带上如果用了 LoadLibrary 动态加载就能把这段错误捕获并给出中文提示而不是让系统弹窗。5.2 现象连接 MySQL 8.0 时报认证插件错误连接时报错类似 “Authentication plugin caching_sha2_password cannot be loaded”。原因是你手上的 libmysql.dll 版本太老只认识 mysql_native_password而 MySQL 8.0 默认认证插件是 caching_sha2_password。解决有两个方向升级 libmysql.dll 到 8.0 配套版本这是最稳妥的如果服务端无法升级可以在 MySQL 里把账号改回老认证方式执行 ALTER USER app% IDENTIFIED WITH mysql_native_password BY 密码; 前者治本后者治标。我一般优先升级 DLL因为老认证方式在后续 MySQL 版本里会被移除。5.3 现象32位/64位不一致导致间歇性崩溃程序能启动连接偶尔成功但运行一会儿崩溃或者 LoadLibrary 时好时坏。这种最容易迷惑人因为错误不固定。原因就是 exe 位数和 DLL 位数不一致Windows 对 64 位进程加载 32 位 DLL 会直接拒绝但 32 位进程加载 64 位 DLL 在某些老版本 Windows 上不会立刻失败而是调用时内存错乱。解决方法是确认 Delphi 编译目标平台。Delphi 7 到 XE 默认只有 32 位那就找 32 位 libmysql.dll用 XE2 以后编译 64 位就配 64 位客户端库。别靠猜用 dumpbin 或任务管理器确认进程位数。5.4 现象中文数据写入变问号、读出乱码数据写入后变成 ????或者读出来一片乱码。原因通常是两个一个是字符集没有对齐服务端、连接、客户端三层各说各话另一个是 Delphi 版本差异Delphi 7 的 string 是 AnsiStringDelphi 2009 之后 string 是 UnicodeString直接把 string 塞给 PAnsiChar 参数会得到错误字节。解决方法是连接后统一字符集服务端用 utf8mb4代码里用 mysql_set_character_set 设置连接字符集传参时先用 UTF8String 转换取结果时用 UTF8ToString 还原。这一条做到位乱码问题基本消失。5.5 现象连接一段时间后 MySQL 报 too many connections系统跑几天后MySQL 端报 “Too many connections”重启程序又正常。原因是程序里的连接没释放或者长连接被网络设备断开但客户端不知道。排查时先执行 SHOW PROCESSLIST看大量 Sleep 状态的连接堆在哪里定位是哪个模块漏调了 mysql_close。解决是每一条连接路径都用 try/finally 保证释放长连接加心跳定期执行 SELECT 1另外可以在 mysql_options 里设置 MYSQL_OPT_RECONNECT让断掉的长连接自动重连但要注意这个选项只对网络中断有效服务端主动 kill 连接时它不会自动补偿。6. 把连接封装进公共单元一个线程一个句柄别偷懒前面讲的都是裸调用项目里如果每个窗体都写一遍 LoadLibrary 和 mysql_query后面维护会很难受。我一般会把连接句柄和操作方法收进一个简单的连接类让业务代码只关心 SQL 字符串。type TMySqlConn class private FHandle: Pointer; public constructor Create(const Host, User, Passwd, DbName: string; Port: Integer); destructor Destroy; override; function ExecSql(const ASql: string): Int64; function Ping: Boolean; end; constructor TMySqlConn.Create(const Host, User, Passwd, DbName: string; Port: Integer); begin FHandle : ConnectMySql(Host, User, Passwd, DbName, Port); end; destructor TMySqlConn.Destroy; begin if FHandle nil then MySqlClose(FHandle); inherited; end; function TMySqlConn.Ping: Boolean; begin Result : MySqlPing(FHandle) 0; end;封装之后有三件事比封装本身更重要。第一一个连接句柄只属于一个线程不要在全局公共连接上做并发查询MySQL C API 的连接句柄不是线程安全的多线程并发需要每线程独立连接只有 libmysql.dll 这个库本身是线程安全的。第二长连接一定要有心跳建议每 30 秒执行一次 SELECT 1超时时间根据网络环境调整否则路由器或防火墙会把空闲连接清掉。第三所有 SQL 和错误信息要打日志连接失败时把 mysql_error 的原文记录下来光记一个异常类名没法定位问题。我在这类项目上翻过的车最后基本都归结成三个字版、位、码——版本、位数、字符集。把这三样先对齐链路就成功了一半剩下的一半在释放和日志。这套连接方案我沿用到现在虽然代码不花哨但胜在每一步都可控。希望帮到你。本文还有配套的精品资源点击获取