☰
C程序连接MySQL:mysql.h与libmysqlclient编译链接排错
2026/11/4 12:56:49 网站建设 项目流程

简介:这是一份面向 Linux 平台 C/C++ 开发者的 MySQL 连接依赖合集,重点解决编译阶段缺少 mysql.h 等头文件、以及 mysqlclient 静态库和动态库的问题,可免去额外安装 mysql-devel 或完整客户端服务端的步骤。压缩包共 77 个文件,包含 68 个 h 头文件、8 个 a 静态库和 1 个 so 动态库,并按 32 位与 64 位分目录整理,整体约 16.49MB,便于依据目标系统架构直接选取对应版本。资源已有 1144 人学习,适合需要快速补齐编译依赖、搭建本地实验环境、维护旧项目或做多架构兼容验证的开发者。其价值在于头文件与库文件成套提供,位数目录区分清楚,能减少环境配置中的查找与版本匹配成本。对离线或受限环境下的 MySQL 客户端程序构建与验证,也提供了较完整的依赖基础,方便读者把精力放在业务代码与连接逻辑上。

1. 从一条编译报错说起:mysql.h 找不到,八成不是 MySQL 没装

上周帮一个做数据采集的同事看构建脚本,gcc main.c -o demo -lmysqlclient一跑就停在fatal error: mysql.h: No such file or directory。他第一反应是“MySQL 没装”,转头又去装了一遍服务端,结果当然还是报错。这种情形我在 linux 下配 mysql 头文件和 mysql 库文件的场景里见过太多次:服务器上mysqld跑得好好的,mysql命令行也能连,但编译期就是找不到mysql.h,链接期又冒出undefined reference to mysql_init,最后程序跑起来还提示libmysqlclient.so.21: cannot open shared object file。

这三类错误分别属于编译期、链接期、运行期,原因是三件不同的事:开发包头文件没装、-l参数位置放错、动态库没进动态链接器的搜索路径。32 位和 64 位并存时还要再多一层:lib目录根本对不上架构。所以下面我不按“先装什么后装什么”空讲,而是按我实际排错的顺序,把这套环境从版本选择、包装、参数配置一直到验证,完整走一遍,后面附上五条踩过的坑。

2. 装之前先定版本:头文件、库文件和 32/64 位对照

动手装包之前,先弄清本机到底缺哪一块。很多人一上来就apt install mysql-server,装完发现编译还是过不去,因为服务端包和开发包是两回事,头文件与链接用的库文件只跟着开发包走。

2.1 mysql.h 管接口,libmysqlclient 管实现

C 程序连 MySQL,靠的是客户端库 libmysqlclient。头文件里放的是结构体定义、函数原型、宏和错误码,比如MYSQL、MYSQL_RES、mysql_init()、mysql_real_connect()的声明,以及CLIENT_SSL、MYSQL_OPT_CONNECT_TIMEOUT这类选项常量;真正干活的那部分代码被编译成.so或.a文件,由链接器在最后一步拼进你的可执行文件。头文件版本和库文件版本必须匹配,否则会出现“编译通过、运行崩溃”的情况,或者更隐蔽的,比如结构体大小对不上。

库文件目录里的几个名字经常把人搞混:

文件名类型谁在用备注
libmysqlclient.so开发用软链接链接器-lmysqlclient由开发包提供,指向带版本号的文件
libmysqlclient.so.21运行时实体库程序启动时SONAME,版本号随 MySQL 大版本变
libmysqlclient.a静态库静态链接需要时再加-static
libmysqlclient_r.so老版本线程安全库已废弃5.5 之后功能并入主库

如果编译时用-lmysqlclient,链接器实际找的是libmysqlclient.so这个软链接;如果只装了运行时的libmysqlclient21包,那.so软链接就没有,-lmysqlclient一定失败。所以判断“装没装全”,看的是.so软链接在不在,而不是.so.21在不在。

2.2 32 位与 64 位是两套目录,不是两个开关

同一台机器上,64 位库在/usr/lib/x86_64-linux-gnu(Debian 系)或/usr/lib64(RHEL 系),32 位库在/usr/lib/i386-linux-gnu或/usr/lib。gcc -m32只会改变编译目标,不会自动把库路径切过去,-L指错了照样报skipping incompatible ... when searching for -lmysqlclient。

各发行版的开发包和目录位置对比如下,装之前对一眼能少走很多弯路:

环境开发包头文件目录库文件目录配置脚本
Debian/Ubuntu amd64libmysqlclient-dev/usr/include/mysql/usr/lib/x86_64-linux-gnumysql_config
Debian/Ubuntu i386libmysqlclient-dev:i386同上,会和 amd64 冲突/usr/lib/i386-linux-gnu通常共用,需另行处理
RHEL/CentOS x86_64mysql-community-devel/usr/include/mysql/usr/lib64mysql_config
MariaDB 连接器libmariadb-dev/usr/include/mariadb同架构库目录mariadb_config
通用二进制包无包名<解包目录>/include<解包目录>/lib<解包目录>/bin/mysql_config

还有一个容易忽略的点:RHEL 系在 7 之后的 MySQL 官方仓库里基本只发 x86_64 和 aarch64 的包,i686 的开发包在 5.7 时代还有,再往后就很少见了。所以真要做 32 位,通用做法是拿一个还带 32 位构建的压缩包,或者用发行版保留的 i386 多架构包,不要指望dnf install mysql-community-devel.i686一定能成。

2.3 三步确认本机架构和已装组件

在动手之前,先花一分钟把现状摸清,比后面反复猜省事:

# 1. 确认系统位宽和架构 getconf LONG_BIT # 输出 64 表示系统是 64 位 uname -m # x86_64 / i686 / aarch64 # 2. 看头文件和库文件到底在不在 ls /usr/include/mysql/mysql.h ls -l /usr/lib/x86_64-linux-gnu/libmysqlclient.so* # 3. 看开发包装没装、配置脚本指向哪 mysql_config --version mysql_config --cflags mysql_config --libs

mysql_config --libs的输出很关键,它一般形如-L/usr/lib/x86_64-linux-gnu -lmysqlclient -lpthread -lz -lm -lrt -lssl -lcrypto -ldl,后面的-lpthread -lz -lssl都是 libmysqlclient 自己的依赖,这也是为什么手写编译参数时经常漏东西。如果这条命令直接command not found,说明开发包没装或者只装了运行时包,下一步就是装。

3. 安装开发包:Debian 系、RHEL 系和压缩包三条路

确定好目标架构后,装法按发行版分三条。选哪条取决于你的机器能不能联网、需不需要 32 位、以及服务器上已经跑着哪个 MySQL 大版本。

3.1 Debian/Ubuntu:libmysqlclient-dev 一次到位

最小可用集就是两个包:运行时的libmysqlclient21(装libmysqlclient-dev时会自动带上)和开发用的libmysqlclient-dev。

sudo apt update # 安装开发包,包含 mysql.h 和 libmysqlclient.so 软链接 sudo apt install -y libmysqlclient-dev # 验证 ls -l /usr/include/mysql/mysql.h ls -l /usr/lib/x86_64-linux-gnu/libmysqlclient.so mysql_config --cflags --libs

第一条apt install之后,头文件落在/usr/include/mysql,库文件落在/usr/lib/x86_64-linux-gnu,mysql_config落在/usr/bin。注意 Ubuntu 从 20.04 起,默认源里的libmysqlclient-dev有时会被libmariadb-dev顶替,后者头文件在/usr/include/mariadb,配置脚本叫mariadb_config。两者协议上基本兼容,但#include <mysql/mysql.h>这种写法就会失败,需要改成<mariadb/mysql.h>或者直接用mariadb_config --cflags给出的路径。装完先dpkg -L libmysqlclient-dev | grep mysql.h看一眼实际路径,比猜快。

如果服务器上跑的是 MySQL 8.0 而发行版源里还是 5.7 的库,建议换成上游仓库,让客户端库版本和服务端对齐,否则在认证插件那块会撞墙,第 5 节会细说。

3.2 RHEL/CentOS:mysql-community-devel 与仓库选择

RHEL 系的包名是mysql-community-devel,头文件同样在/usr/include/mysql,库文件在/usr/lib64:

# 先看仓库里有没有可用的 devel 包 dnf list available | grep -i mysql-community # 安装客户端开发包(含 mysql.h 和 libmysqlclient.so) sudo dnf install -y mysql-community-devel # 确认架构与路径 rpm -ql mysql-community-devel | grep -E 'mysql\.h|libmysqlclient\.so$' mysql_config --libs

rpm -ql这条命令我习惯每次都跑,因为它能一眼看出 dev 包到底往哪个目录放了东西,尤其是机器上同时存在/usr/lib64和/usr/local/lib64的时候。如果仓库里只有mariadb-devel,那装完同样能用,只是头文件路径变成/usr/include/mariadb,mysql_config换成mariadb_config。

还有一种情况:机器不能连外网,但手上有 MySQL 的 rpm 包。那就只装两个,mysql-community-libs(运行时)加mysql-community-devel(开发),mysql-community-client可以不装,别顺手把 server 也带上去。

3.3 通用压缩包:只要 include 和 lib 两个目录

不能改系统包、或者要做 32 位而仓库里没有对应包时,我一般直接下 MySQL 的 Linux 通用压缩包,把它当纯客户端 SDK 用,只取两个目录:

# 解包,把 8.0.xx 换成你实际下载的版本号 tar -xf mysql-8.0.xx-linux-glibc2.17-x86_64.tar.gz # 只保留头文件和库,放进独立前缀,避免污染系统目录 sudo mkdir -p /opt/mysql-client sudo cp -r mysql-8.0.xx-linux-glibc2.17-x86_64/include /opt/mysql-client/ sudo cp -r mysql-8.0.xx-linux-glibc2.17-x86_64/lib /opt/mysql-client/ # 检查关键文件 ls /opt/mysql-client/include/mysql.h ls /opt/mysql-client/lib/libmysqlclient.so*

这条路的优点是版本完全可控,缺点是需要自己处理两件事:一是编译参数要显式带上-I/opt/mysql-client/include -L/opt/mysql-client/lib;二是运行时动态链接器默认不认/opt下的库,得配ld.so.conf.d或者用-Wl,-rpath,第 4 章会给出两种写法。压缩包里的lib目录一般只带.so.21这种运行时实体库和一个.so软链接,如果某天发现只有.so.21,用ln -s补一个软链接就可以,这种做法很常见,不用觉得不正统。

3.4 补 32 位:i386 多架构与 gcc -m32 的配合

32 位是整个流程里最容易翻车的一段,因为开发包在多架构下会互相覆盖/usr/bin/mysql_config这类共享文件。Debian 系上我一般这么走:

# 打开 i386 架构支持并更新索引 sudo dpkg --add-architecture i386 sudo apt update # 安装 32 位的编译环境(缺了它 -m32 会在链接 libc 时失败) sudo apt install -y gcc-multilib libc6-dev-i386 # 装 32 位运行库和开发包 sudo apt install -y libmysqlclient-dev:i386 # 如果提示与 amd64 版本文件冲突,就改用解包方式放到独立目录 apt download libmysqlclient-dev:i386 dpkg -x libmysqlclient-dev_*.deb /opt/mysql32 ls /opt/mysql32/usr/lib/i386-linux-gnu/libmysqlclient.so

前面加...-dev:i386那一步如果报文件冲突属于正常现象,因为两个架构的开发包都想占用/usr/include/mysql和/usr/bin/mysql_config。稳妥做法就是后面那三行:apt download把 deb 拿下来,dpkg -x解到/opt/mysql32,编译时用-I/opt/mysql32/usr/include/mysql -L/opt/mysql32/usr/lib/i386-linux-gnu指过去,不碰系统目录。gcc-multilib一定要装,否则-m32会在链接阶段提示找不到 32 位的crt1.o,那个报错和 MySQL 一点关系都没有,很容易带偏。

RHEL 系这边思路一致,只是工具换成yum install glibc-devel.i686 libstdc++.i686再补 32 位的 mysql 库。仓里没有 32 位开发包的话,还是回到 3.3 那条压缩包路线,找一个仍有 32 位构建的版本,把头文件和lib取出来单独放。

4. 配置编译与运行参数:从 mysql_config 到 ldconfig

包装好只是第一步,真正决定能不能编过的是-I、-L、-l三个参数怎么给,以及运行期动态库怎么被找到。

4.1 用 mysql_config 把参数交给编译器

最省事也最不容易漏依赖的写法,是让mysql_config直接吐参数:

# 单独看两类参数 mysql_config --cflags # 例如 -I/usr/include/mysql mysql_config --libs # 例如 -L/usr/lib/x86_64-linux-gnu -lmysqlclient -lpthread -lz -lm -lrt -lssl -lcrypto -ldl # 一次编译:源文件在前,库参数在后 gcc -O2 -Wall main.c -o demo $(mysql_config --cflags) $(mysql_config --libs)

顺序这件事必须强调:--libs展开出来的-lmysqlclient一定放在源文件或目标文件后面。链接器是从左往右扫描的,先遇到-lmysqlclient时还没有任何未解析符号,它就会认为这个库没用,直接跳过,接着扫描到main.o里的mysql_init,于是报undefined reference。这是我把--libs写在main.c前面时踩过的坑,血泪经验就一条:源文件永远在前。

--cflags展开出的-I/usr/include/mysql也值得看一眼。如果你的代码写的是#include <mysql/mysql.h>,那-I应该指到/usr/include;如果写的是#include <mysql.h>,-I就得指到/usr/include/mysql。两种写法对应两种-I,混了就是那句经典的No such file or directory。

4.2 手写 -I -L -l:什么时候必须自己写

用压缩包或者 32 位独立目录时,mysql_config给的是系统路径,不能直接用,得手写:

# 64 位,使用 /opt 下的独立客户端库 gcc -O2 -Wall main.c -o demo \ -I/opt/mysql-client/include \ -L/opt/mysql-client/lib \ -Wl,-rpath,/opt/mysql-client/lib \ -lmysqlclient -lpthread -lz -lm -lrt -lssl -lcrypto -ldl # 32 位,指向 i386 目录并加 -m32 gcc -m32 -O2 -Wall main.c -o demo32 \ -I/opt/mysql32/usr/include/mysql \ -L/opt/mysql32/usr/lib/i386-linux-gnu \ -lmysqlclient -lpthread -lz -lm -ldl

-Wl,-rpath,<dir>的作用是把库目录直接写进可执行文件的DT_RPATH段,程序启动时优先按这个路径找.so,不用依赖系统配置,适合把客户端库放在/opt或者私有目录的场景。代价是这个路径被固化进二进制,换机器部署时目录结构必须一致;如果做不到,就走下面ldconfig那条路。

另外,32 位场景下-lssl -lcrypto有时不需要,因为老版本的 libmysqlclient 把 SSL 支持编进去了;但 8.0 的库对 OpenSSL 是动态依赖,漏了会报undefined reference to SSL_*,遇到就补上,或者直接$(mysql_config --libs)拷过来再改路径。还有个替代方案是pkg-config --cflags --libs mysqlclient,有些发行版的开发包会带.pc文件,输出比mysql_config更干净;没有这个文件就别在这上面花时间。

4.3 运行期找不到 so:ldconfig、rpath 和 LD_LIBRARY_PATH

编译链接都过了,跑起来却提示error while loading shared libraries: libmysqlclient.so.21: cannot open shared object file,这是运行期问题,跟编译参数无关。检查顺序固定为三步:

# 1. 看可执行文件到底在找哪个库 ldd ./demo | grep mysqlclient # 2. 看系统缓存里注册了哪些库 ldconfig -p | grep mysqlclient # 3. 库在非标准目录时,注册进去 echo "/opt/mysql-client/lib" | sudo tee /etc/ld.so.conf.d/mysqlclient.conf sudo ldconfig ldconfig -p | grep mysqlclient

ldd的输出会直接告诉你libmysqlclient.so.21 => not found还是指向了别的路径。ldconfig -p查的是/etc/ld.so.cache,只有这个缓存里有的库,动态链接器才会去加载;/etc/ld.so.conf.d/*.conf加目录再加ldconfig就是往缓存里注册,这是长期有效的做法。LD_LIBRARY_PATH只在临时调试时用,导出在~/.bashrc里迟早会在别的进程里出问题,不建议写进环境变量。

还有一种隐蔽情况:机器上同时装了 MariaDB 的libmariadb.so.3和 MySQL 的libmysqlclient.so.21,两者文件名不同但符号大部分重名,ldd显示正常、程序却连不上或者崩在mysql_real_connect里。这时候用LD_DEBUG=libs ./demo 2>&1 | grep -i mysql看实际加载顺序,比盯着源码看快得多。

5. 避坑与排查:五条编译链接期的真实记录

下面五条是我在不同机器上真撞过的,按「现象 → 原因 → 解决」写,遇到同样的报错可以直接对号。

5.1 编译期:头文件与路径类

现象一:fatal error: mysql.h: No such file or directory,但mysql命令行能正常连库。 原因:只装了服务端或客户端运行时包,没装开发包,/usr/include/mysql整个目录都不存在;或者装了但代码里写<mysql.h>,-I给的却是/usr/include。 解决:先ls /usr/include/mysql/mysql.h确认,没有就装对应开发包;有的话统一 include 写法和-I路径,用mysql_config --cflags的输出为准,不要手抄。

现象二:装完libmysqlclient-dev后编译还是找不到头文件,mysql_config却存在。 原因:发行版给的是 MariaDB 连接器,头文件在/usr/include/mariadb,mysql_config实际是mariadb_config的软链,--cflags输出的是/usr/include/mariadb。 解决:dpkg -L libmariadb-dev | grep mysql.h看实际位置,代码改成#include <mariadb/mysql.h>或者干脆用$(mysql_config --cflags)输出里给的目录配合<mysql.h>。

现象三:gcc -m32编译时报cannot find -lmysqlclient,但 64 位编同一份代码没问题。 原因:-L默认指向 64 位目录,那里只有 x86_64 的.so,链接器扫描时会输出skipping incompatible,然后报找不到。 解决:装 i386 的开发包或按 3.4 解包到独立目录,-L明确指向/usr/lib/i386-linux-gnu或解包出来的目录;顺手确认gcc-multilib已装。

5.2 链接与运行期:顺序、版本和认证

现象四:编译命令看起来没问题,却报一堆undefined reference to 'mysql_init'、'mysql_real_connect'。 原因:-lmysqlclient写在了源文件前面,链接器从左到右扫描时把它当无用库跳过了;另一种情况是只给了-I没给-L,链接器找到了头文件但找不到库。 解决:$(mysql_config --libs)或-lmysqlclient一律放命令行最后;补-L;用gcc -v看完整链接命令里库参数的实际位置。

现象五:程序跑起来报Authentication plugin 'caching_sha2_password' cannot be loaded,或者连上后立刻断开。 原因:客户端库版本比服务端老,8.0 服务端默认的认证插件不被老客户端库支持;32 位场景下这种版本错配尤其常见,因为 32 位的库往往来自更早的版本。 解决:优先升级客户端库到与服务端同大版本;临时过渡可以改用户认证方式(ALTER USER ... IDENTIFIED WITH mysql_native_password BY ...),但这是降低安全等级的妥协,只适合内网自测,改完记得回滚。

这五条里前三条都是路径问题,后两条是顺序和版本问题。排错的时候我习惯先跑mysql_config --cflags --libs把两个字符串打出来贴在终端上,再对着编译命令逐字比,80% 的报错当场就没了。

6. 装完怎么验证:用一个小程序把三类问题一次暴露

验证不要停在mysql_config --version有输出这一步,那只证明脚本在。真正能证明环境可用的是:写一个最小 C 程序,编译通过、链接通过、运行能拿到服务端版本号。

#include <stdio.h> #include <mysql/mysql.h> /* 若用 -I/usr/include/mysql,改成 <mysql.h> */ int main(void) { MYSQL *conn = mysql_init(NULL); /* 初始化连接句柄,失败返回 NULL */ if (conn == NULL) { fprintf(stderr, "mysql_init failed\n"); return 1; } /* 主机/用户/密码/库名按自己的环境改,端口默认 3306 */ if (mysql_real_connect(conn, "127.0.0.1", "demo", "demo_pwd", "testdb", 3306, NULL, 0) == NULL) { fprintf(stderr, "connect error: %s\n", mysql_error(conn)); mysql_close(conn); return 2; } if (mysql_query(conn, "SELECT VERSION()") != 0) { fprintf(stderr, "query error: %s\n", mysql_error(conn)); } else { MYSQL_RES *res = mysql_store_result(conn); MYSQL_ROW row = mysql_fetch_row(res); printf("server version: %s\n", row ? row[0] : "unknown"); mysql_free_result(res); } mysql_close(conn); return 0; }

编译和逐层检查按这个顺序走,每一步失败都能定位到具体阶段:

# 1. 编译链接,参数全部来自 mysql_config gcc -O2 -Wall demo.c -o demo $(mysql_config --cflags) $(mysql_config --libs) # 2. 确认架构对不对(做 32 位时这条最关键) file demo # 期望看到 ELF 64-bit 或 ELF 32-bit # 3. 确认运行期依赖都能解析 ldd demo | grep mysqlclient # 4. 真连一次 ./demo

对file和ldd的输出不必只看有没有报错,重点看两处:file显示的字长必须和目标一致,ldd里 mysqlclient 那行不能出现not found,而且指向的路径要和编译时-L给的一致。如果ldd指向的是系统目录而不是/opt下的私有库,说明rpath没生效,回到 4.2 补-Wl,-rpath。

再给一个快速自检表,出问题按行对照:

检查项命令期望结果
头文件存在ls /usr/include/mysql/mysql.h文件存在
开发库软链接存在ls -l /usr/lib/x86_64-linux-gnu/libmysqlclient.so指向.so.21
编译参数正确mysql_config --cflags --libs含-I与-lmysqlclient
目标架构正确file demoELF 32-bit或ELF 64-bit
运行时依赖齐全ldd demo | grep mysqlclient无not found
实际可连通./demo打印server version: 8.0.x

从那以后我每次配完这套环境,不管多急,file、ldd、./demo这三条都强制走一遍,因为它们分别对应编译期、链接期、运行期三道关口,任何一道没过,前面的mysql_config输出再漂亮也没意义。希望这套流程能帮到你,少在undefined reference上耗一个下午。

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

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

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

立即咨询