
简介在 Linux 下开发并连接 MySQL 数据库时缺少头文件和库文件往往是编译失败的常见原因。这套依赖包专门面向 C/C 开发者同时提供 32 位与 64 位两套接口文件省去另行安装 mysql-devel 或 MySQL 客户端、服务器的麻烦使用者只需解压并按操作系统位数选择对应目录即可获得完整的编译依赖。压缩包内共 77 个文件整体大小约 16.49MB以 68 个头文件和 8 个静态库文件为主另有 1 个动态库文件基本覆盖 MySQL 客户端链接所需的接口声明与库依赖。使用时把 mysql_include 与 mysql_lib 两个目录复制到主文件同级位置便可在 makefile 中通过 gcc -I./mysql_include xxx.c -L./mysql_lib -lmysqlclient -lpthread -lm -ldl -o xxx 直接完成编译。无论是本地调试、离线环境还是快速搭建数据库开发环境该方案都能明显减少配置时间目前已有 1140 人浏览学习适合需要即拿即用依赖集合的 Linux 开发人员。1. 为什么明明装了MySQL还是找不到mysql.h1.1 一个让新手当场懵掉的报错先说一个几乎所有Linux新手在第一次写C/C连接MySQL的程序时都会遇到的场景。代码写好了逻辑看着也没问题gcc一执行直接甩出一行fatal error: mysql.h: No such file or directory英文好一点的还知道是找不到mysql.h这个头文件英文不好的当场就懵了我明明apt install mysql-server装好了数据库show databases也能进为什么编译器就是找不到头文件这个问题的根源在于MySQL服务端程序和MySQL客户端开发库完全是两套东西。你用apt装mysql-server系统给你装的是mysqld服务进程、命令行客户端mysql、以及运行这些程序所需的运行时库。但编译程序需要的头文件mysql.h、mysql_com.h、errmsg.h这些和链接需要的静态库/动态库libmysqlclient.a / libmysqlclient.so是放在独立的开发包里的。服务端包不会把你的gcc环境当回事自然不会主动把这些开发文件塞到你系统里。1.2 不同Linux发行版下的开发包命名差异这里有一个很坑的点同一个东西在不同的Linux发行版上包名完全不一样。这个细节坑过不少从CentOS迁到Ubuntu的老手。发行版开发包名称安装命令Debian / Ubuntulibmysqlclient-devsudo apt install libmysqlclient-devRHEL / CentOS 7mysql-devel或mariadb-develsudo yum install mysql-develFedoracommunity-mysql-develsudo dnf install community-mysql-developenSUSElibmysqlclient-develsudo zypper install libmysqlclient-develArch Linuxlibmariadbclientsudo pacman -S libmariadbclient装完这个开发包之后再去编译器里搜一下头文件find /usr/include -name mysql.h如果能找到说明头文件这块就位了。但别高兴太早库文件这一关还在后面等着你。1.3 为什么单纯的头文件和库文件缺一不可头文件和库文件在编译链接过程中的分工完全不同。头文件.h负责在编译阶段告诉编译器mysql_init、mysql_real_connect这些函数的签名长什么样参数是几个、参数类型是什么、返回值是什么类型。编译器靠这些声明对你的源码做语法检查和类型检查。但真正让程序能跑起来靠的是链接阶段的库文件.so或.a。库文件里才是这些函数的具体实现代码。编译阶段只是相了个亲确认双方认识链接阶段才是领证过日子把你的代码和MySQL客户端库的实现真正绑定在一起。我见过不少人在解决了头文件缺失之后信心满满地重新执行编译结果又碰上下一个坑undefined reference to mysql_initlibmysqlclient_18这就是典型的头文件找到了但库文件没链接上。编译能过链接挂了。所以你看到任何一篇教你用C语言连接MySQL的文章一定会同时强调要include头文件和要-lmysqlclient链接库文件两件事缺一不可。2. 32位和64位开发包的本质区别以及为什么老程序员总在念叨位数要对上2.1 从编译目标到ABI差异先纠正一个常见的理解误区很多人以为32位库和64位库的区别就是文件大小不一样然后觉得反正装哪个都能用大不了编译的时候指定一下。这个想法非常危险代价轻则编译告警重则运行段错误。32位和64位库文件背后是两套完全不同的ABIApplication Binary Interface应用二进制接口。指针变量的字节长度不同32位下8字节的指针在64位下变成了16字节的内存布局数据结构的内存对齐规则不同函数调用的传参约定也不同。一个用32位编译的C语言程序去链接64位的libmysqlclient.so链接器会直接用skipping incompatible结束操作。用file命令一眼就能看出库文件的真实位数file /usr/lib/x86_64-linux-gnu/libmysqlclient.so输出结果里如果写着ELF 64-bit LSB shared object那它就是64位的库。如果你是32位目标必须找到写着ELF 32-bit LSB shared object的库。2.2 一个系统里同时存在32位和64位MySQL开发库是否可行答案是可行而且这也是本篇文章最核心的实战场景。Debian系发行版的库文件目录设计天生就支持多架构共存。64位系统上你打开/etc/ld.so.conf.d/下的配置文件大概率会看到这样的行/usr/lib/x86_64-linux-gnu /usr/lib/i386-linux-gnu这两个目录就是64位和32位动态库各自的家。只要你的系统开启了多架构支持dpkg --add-architecture i386就可以同时安装两个版本的libmysqlclient-dev。Red Hat系发行版不会默认这么做但可以通过手动指定路径把32位库和64位库放在不同目录下再用编译参数区分。这个后面细说。2.3 头文件有没有32位和64位的区别这里有个很多初学者没想明白的点头文件本身其实没有位数这一说。mysql.h就是一个纯文本文件无论你是编译32位还是64位的程序include的都是同一份头文件。真正不同的是链接阶段去找哪个目录下的.so或.a。但restricted的地方在于头文件声明中可能有一些依赖平台位数的类型定义比如用long还是long long来表示某个字段。MySQL的C API在设计时已经通过#ifdef等宏处理了这些差异所以你不需要准备两套头文件。你需要的只是确保头文件声明和库文件实现来自同一版本的MySQL否则会出现函数签名在编译阶段与链接阶段不一致的问题。3. 从零配置一套64位MySQL开发环境3.1 Debian系的一键安装与路径布局我习惯在Ubuntu上做开发这里以Debian系为例给出完整步骤。sudo apt update sudo apt install libmysqlclient-dev装完之后头文件和库文件的默认路径分布如下文件类型典型路径头文件/usr/include/mysql/mysql.h动态库/usr/lib/x86_64-linux-gnu/libmysqlclient.so静态库/usr/lib/x86_64-linux-gnu/libmysqlclient.apkg-config文件/usr/lib/x86_64-linux-gnu/pkgconfig/mysqlclient.pc注意这里有一个很多人没意识到的小细节完整路径下是找不到libmysqlclient.so这个文件本身的。在Debian系发行版中libmysqlclient.so通常是一个符号链接真正带版本号的文件名是libmysqlclient.so.21这样的格式。链接器在编译时找的是不带版本号的.so文件名这个链接会被指向带版本号的实际文件运行时加载器ld.so找的则是带版本号的libmysqlclient.so.21。两者由ldconfig的缓存机制统一管理。3.2 编译参数里面的学问装好开发包之后下面这个编译命令是无数文章都会写的gcc -o myapp myapp.c -I/usr/include/mysql -L/usr/lib/x86_64-linux-gnu -lmysqlclient但很多人不理解为什么要写这么多参数照着抄都会漏。拆开解释一遍-I/usr/include/mysql告诉编译器去这个目录下找头文件-L/usr/lib/x86_64-linux-gnu告诉链接器去这个目录下找库文件-lmysqlclient告诉链接器要链接名为libmysqlclient.so的库。注意l后面不带lib前缀也不带.so后缀这是gcc的语法规则如果你用了多线程通常还需要加上-lpthread -lm -ldl因为MySQL客户端库依赖这三个系统库。少一个的话编译时可能正常运行时才报undefined symbol更优雅的做法是用pkg-config自动获取编译参数避免不同发行版路径不同导致的麻烦gcc -o myapp myapp.c $(pkg-config --cflags --libs mysqlclient)这条命令可以自动输出-I和-L以及-l参数省得自己手写路径。前提是pkg-config文件已经安装到位。3.3 一个能跑通的验证程序环境配好了光说不练假把式写一个最小程序验证连接。完整的连接校验代码不长但包含了必要的错误处理逻辑#include stdio.h #include stdlib.h #include mysql/mysql.h int main(void) { MYSQL *conn mysql_init(NULL); if (conn NULL) { fprintf(stderr, mysql_init failed\n); return 1; } if (mysql_real_connect(conn, 127.0.0.1, username, password, testdb, 0, NULL, 0) NULL) { fprintf(stderr, Connect failed: %s\n, mysql_error(conn)); mysql_close(conn); return 1; } printf(MySQL connection OK, client version: %s\n, mysql_get_client_info()); mysql_close(conn); return 0; }编译命令gcc -o test_mysql test_mysql.c $(pkg-config --cflags --libs mysqlclient)如果编译链接全过、运行也成功输出MySQL connection OK你的64位开发环境就彻底通了。有一个很容易被忽略的注意点如果mysql服务端本身没启动或者服务端的bind-address不是127.0.0.1这里会卡在连接失败。先排除服务端因素再去怀疑头文件和库文件的问题。4. 32位目标程序的编译与库路径适配4.1 在64位系统上编译32位程序需要什么你手头可能有一台64位的编译机器但目标部署环境的系统是32位的或者你需要在64位系统上做一个32位兼容插件。这时候就涉及到在64位宿主上编译32位目标的问题。前提条件是系统要装好32位的编译工具链和基础库。Debian系的做法是sudo dpkg --add-architecture i386 sudo apt update sudo apt install libc6-dev-i386 gcc-multilib g-multilib然后安装32位版本的MySQL开发库sudo apt install libmysqlclient-dev:i386装完检查一下/usr/lib/i386-linux-gnu目录下应该出现libmysqlclient.so。编译的时候加个-m32参数gcc -m32 -o test_mysql_32 test_mysql.c -I/usr/include/mysql -L/usr/lib/i386-linux-gnu -lmysqlclient关键就在于-m32告诉gcc生成32位目标代码-L路径必须指向32位库所在的目录。如果你忘了后面这个路径gcc会默认去64位库目录找然后报一行让人莫名其妙的错/usr/bin/ld: skipping incompatible /usr/lib/x86_64-linux-gnu/libmysqlclient.so when searching for -lmysqlclient /usr/bin/ld: cannot find -lmysqlclient这两个报错信息连在一起的时候翻译成人话就是链接器找到了库文件但发现位数不匹配直接跳过不认然后找不到合适的库干脆报找不到。很多新手看到cannot find就以为库没装实际上库里装了只是位数不对。4.2 Red Hat系手动手工规划32位库的目录Red Hat系的做法不如Debian系自动尤其是老旧的CentOS 7默认软件仓库已经不怎么维护i686的mysql-devel包了。通常的做法是直接从MySQL官网下载RPM包或者从官方tar包中提取32位库。这里提一个常见操作把下载解压后得到的32位libmysqlclient.so放到一个独立目录比如/opt/mysql-lib32编译时用-L指定这个目录。这样做的好处是路径清晰不会和系统64位库目录混淆。4.3 编译通过后的运行时库路径问题32位程序编译完之后你以为万事大吉结果一运行./test_mysql_32 error while loading shared libraries: libmysqlclient.so: cannot open shared object file这个报错是运行时加载器ld.so找不到动态库了。注意它跟编译阶段的cannot find完全不是一回事。编译阶段是链接器在找库运行阶段是动态加载器在找库。两者的工作目录搜索规则完全不同。解决方式有两种。一是临时设置环境变量LD_LIBRARY_PATH/usr/lib/i386-linux-gnu ./test_mysql_32二是把库路径写入系统配置一劳永逸echo /usr/lib/i386-linux-gnu /etc/ld.so.conf.d/mysql32.conf sudo ldconfigldconfig的作用是刷新动态库缓存让加载器能够识别新加入的库路径。这一步在手动安装非系统默认路径的库时尤其重要。5. 多版本、多位数MySQL库共存时的目录规划与排查思路5.1 为什么会需要共存有人问项目里就一两个C程序连MySQL有必要搞这么复杂吗还真有。实际工作中我遇到过这样的场景同一台编译服务器上既要编一个64位的后端服务又要交叉编一个32位的老旧系统插件。两个项目依赖的MySQL版本还不一样一个用5.7的API一个用8.0的API。这种时候一股脑地apt install libmysqlclient-dev根本解不了题必须手动管理不同版本、不同位数的库目录。我的建议是建立一个三级目录结构/opt/mysql-libs/ ├── mysql5.7/ │ ├── x86_64/ │ │ ├── include/ │ │ └── lib/ │ └── i386/ │ ├── include/ │ └── lib/ └── mysql8.0/ ├── x86_64/ │ ├── include/ │ └── lib/ └── i386/ ├── include/ └── lib/编译时靠-I和-L精确指向对应目录互不干扰。这套目录规划看上去简单但在项目一多的时候使用者的体验差别非常大。乱放一气的迟早会出这份代码在甲机器编得通、在乙机器编不通的幺蛾子。5.2 用ldd验证可执行文件依赖库的正确位数编译完成后一个非常有效的验证手段是用ldd查看可执行文件的动态库依赖列表ldd test_mysql_32正常情况下会看到linux-gate.so.1 (0xf7f6b000) libmysqlclient.so /usr/lib/i386-linux-gnu/libmysqlclient.so (0xf7cb0000) libc.so.6 /lib/i386-linux-gnu/libc.so.6 (0xf7a00000)注意看libmysqlclient.so解析到的路径必须是32位库。如果这里跳出了64位的路径x86_64-linux-gnu那大概率是环境变量LD_LIBRARY_PATH或系统的ld.so.conf.d配置把顺序搞乱了。lDD是一个被低估但极其好用的调试工具任何一次链接问题排查都应该先跑一遍它。5.3 真实排查案例同一个可执行文件跑在不同机器上结果不同顺带分享一个我在实际项目中踩过的坑也是把C语言连MySQL 头文件/库文件 32/64位这几个关键词揉在一起时最容易翻车的情形。当时我们把一个编译好的64位程序部署到另一台服务器上结果报错找不到libmysqlclient.so.21。在本机编译机器上跑得好好的一换机器就崩。原因很简单本机装的是libmysqlclient-dev顺带把运行时版本的so.21也带上了目标服务器只装了mysql-client运行时库不完整。排查链路是这样的先ldd看缺什么库再用find / -name libmysqlclient*确认目标机器的库文件是否真的存在最后检查/etc/ld.so.conf.d/下的配置是否包含了库文件所在目录。找到原因后用静态链接或把.so.21一并拷贝到目标机器解决。这个案例给我们的一个教训是编译环境和运行环境要分开看。开发机上有完整的头文件和库文件不代表运行机器上也一样特别是用动态链接时运行依赖的文件往往比编译依赖的更琐碎。6. 静态链接方案与MySQL 8.x的兼容性变化如果在部署时被动态库依赖折腾烦了可以考虑静态链接。静态库在编译时就把代码嵌入到可执行文件里运行时不再依赖外部.so文件。MySQL官方在发布版本中同时提供libmysqlclient.a和libmysqlclient.so但需要注意8.0版本开始官方明确表示libmysqlclient.a可能无法用于纯静态链接因为客户端库内部依赖了OpenSSL、zlib等第三方库这些库的动态依赖依然存在。实测中纯静态链接多半会报一堆undefined reference补完一个还有下一个循环几轮之后很多人就直接放弃了。更实际的做法是半静态链接时指定优先使用静态库gcc -o myapp_static myapp.c -I/usr/include/mysql -L/usr/lib/x86_64-linux-gnu -Wl,-Bstatic -lmysqlclient -Wl,-Bdynamic-Wl,-Bstatic让链接器在其后的库上使用静态模式-Wl,-Bdynamic恢复动态模式。MySQL客户端库静态链接其余的继续动态链接。这样既减少了部署时对MySQL库的依赖又避免了纯静态链接的第三方库牵扯问题。MySQL 8.0的API相比5.7有了一些变化最典型的是mysql_get_client_info()返回的版本号字符串或者某些结构体字段的定义有所调整。如果你用同一套头文件编的代码去链不同版本的库运行阶段大概率会崩因为结构体内存布局对不上。强制建议头文件版本与库版本必须保持一致最好同一个目录下同时维护include和lib别两处分开管理。我在实际项目里更倾向于放弃系统包管理器装的MySQL开发库改用官方提供的统一目录结构把include、lib、pkgconfig整整齐齐放在一起。这样跨机器迁移时直接打包这个目录过去环境一致性有保证。唯一要注意的是别让系统默认库目录里的同名库干扰编译编译参数里的-I和-L顺序要排在最前面。32位和64位的支持则是这样规划的64位库放在lib64目录32位库放在lib32目录编译脚本里对目标架构做一个变量开关默认64位需要时切换到32位。这套做法经过多个项目的检验可靠度非常高。配置好一套之后剩下的就是写代码本身了。本文还有配套的精品资源点击获取