简介:面向在 Linux 环境下做 C/C++ 开发、需要对接 MySQL 数据库的开发者,尤其是苦于缺少 mysql-devel 包、或不便在目标机上安装 MySQL 客户端与服务器的场景。资源是一套可直接随工程携带的 MySQL 连接头文件与库文件集合,同时提供 32 位和 64 位两套目录,解压后按系统位数选择即可,无需再额外下载安装依赖包。压缩包共 77 个文件,以 68 个 .h 头文件为主,配合 8 个静态库 .a 与 1 个动态库 .so,整体约 16.49MB,头文件用于声明 MySQL C API 接口,库文件则供链接 mysqlclient 使用。该资源已有 1144 人学习下载,实用性经过了较多开发者验证。使用者将头文件与库目录放到工程同级后,在 Makefile 中通过 -I 与 -L 指定当前目录的包含路径和链接路径,并链接 mysqlclient、pthread、m 与 dl 即可完成编译,省去环境配置时间,也便于在无外网或权限受限的机器上快速搭建可编译、可移植的数据库连接工程。
1. 一个头文件引发的返工:这份资源到底解决什么
把一段在开发机上跑得好好的 MySQL 客户端代码拷到另一台机器,gcc直接甩回一行fatal error: mysql.h: No such file or directory。好不容易装上开发包编过了,运行又来一句error while loading shared libraries: libmysqlclient.so.21。换成 32 位目标再编一次,这回连-lmysqlclient都找不到了。这三连击基本就是 Linux 下 C/C++ 连 MySQL 的日常:头文件(mysql.h)决定能不能编过,库文件(libmysqlclient.so)决定能不能链上,库的架构位宽决定能不能跑。这份资源围绕linux mysql头文件 mysql库文件这条主线,把定位、编译、链接、多架构配平、运行期排查串成一条可复现的路径,适合正在把数据库接进服务端程序、又不想在编译错误里反复试错的开发者。
2. 头文件与库文件在哪:三处路径与一次版本对照
先建立一个最基本的认知:MySQL 的 C 客户端能力被拆成了两个物理实体。头文件是编译期契约,告诉编译器MYSQL结构体长什么样、mysql_real_connect有几个参数;库文件是链接期实物,提供这些符号的真实机器码。两者都来自客户端开发包,而不是服务端包。
很多人第一步就走偏:机器上mysqld跑得好好的,mysql命令行也能连,就以为开发环境齐了。实际上mysql-client只给二进制工具,mysql-server只给服务进程,头文件和.so的符号链接都躺在-dev/-devel包里。这是定位问题的第一条分水岭。
2.1 先确认包里到底有没有 mysql.h
不同发行版的包名不一样,查法也不一样。先按发行版走一遍清单,比盲目find / -name mysql.h高效得多。
| 发行版 | 客户端开发包 | MariaDB 对应包 | 头文件典型落点 |
|---|---|---|---|
| Debian / Ubuntu | libmysqlclient-dev | libmariadb-dev | /usr/include/mysql/mysql.h |
| Debian / Ubuntu(兼容层) | — | libmariadb-dev-compat | /usr/include/mysql/ |
| RHEL / CentOS 系 | mysql-community-devel | mariadb-devel | /usr/include/mysql/mysql.h |
| 通用源码编译 | 随源码 tree 提供 | 同左 | $(prefix)/include/mysql/ |
查已安装包用这两条:
# Debian 系:看开发包是否在册,以及头文件归属 dpkg -l | grep -Ei 'mysql|mariadb' | grep -E 'dev|client' # RHEL 系:确认 devel 包与库文件被哪个包提供 rpm -qa | grep -Ei 'mysql|mariadb' rpm -ql mysql-community-devel 2>/dev/null | grep -E 'mysql\.h|libmysqlclient'前一条命令输出的ii状态表示已安装,只出现mysql-client而没有libmysqlclient-dev,就说明头文件缺失是必然结果。后一条的-ql直接列出包内文件清单,grep把mysql.h和libmysqlclient过滤出来,能一次性回答"装了没装、装在哪"。这一步的意义在于把"文件找不到"从玄学变成可核对的事实。
装上之后再确认落点:
# 头文件真实路径 ls -l /usr/include/mysql/mysql.h # 库文件:注意 .so 是开发用的符号链接,.so.21 才是实体 ls -l /usr/lib64/mysql/ 2>/dev/null ls -l /usr/lib/x86_64-linux-gnu/libmysqlclient* 2>/dev/null提示:
libmysqlclient.so是给链接器用的符号链接,libmysqlclient.so.21才是运行期加载的实体文件。只装运行时包时前者不存在,-lmysqlclient就会报cannot find -lmysqlclient。
2.2 mysql_config:一次问清所有编译参数
装完开发包后,发行版通常会一并提供一个mysql_config(MariaDB 下叫mariadb_config)。这个小工具把--cflags、--libs、--include、--variable=xxx全部算好,是整套流程里最值得依赖的一个东西。
# 确认工具存在,并看它报告的版本与编译选项 which mysql_config || which mariadb_config mysql_config --version # 客户端库版本,如 8.0.xx mysql_config --cflags # 预处理与包含路径 mysql_config --libs # 链接参数全量 mysql_config --include # 仅 -I 部分 mysql_config --variable=pkglibdir # 库目录输出大致长这样:
$ mysql_config --cflags -I/usr/include/mysql -DFORCE_INIT_OF_VARS $ mysql_config --libs -L/usr/lib64/mysql -lmysqlclient -lpthread -lz -lm -lrt -lssl -lcrypto -ldl参数含义要逐个拆开看,不能整段照抄了事:-I/usr/include/mysql解决的是#include <mysql.h>的情形,如果你写的是#include <mysql/mysql.h>,这个-I就是多余的;-DFORCE_INIT_OF_VARS是某些发行版为兼容而加的宏定义;-L/usr/lib64/mysql指定库搜索目录,后面一串-lpthread -lz -lm -lrt -lssl -lcrypto -ldl是libmysqlclient的传递依赖,静态链接时一个都不能少。
一个容易忽略的细节:mysql_config报出来的路径是它自己被编译时确定的,装的是 64 位包就只报 64 位路径。想拿 32 位参数,必须让 32 位的mysql_config出场,或者手工把路径改成 32 位目录。这一点在第 4 章会展开。
2.3 64 位与 32 位库,位置和文件名都不同
同一台机器上 64 位和 32 位的 MySQL 客户端库可以并存,但它们既不共享目录,也不共享文件名,链接器不会自动帮你选。
| 架构 | 头文件目录 | 库目录(Debian 系) | 库目录(RHEL 系) | 实体文件名示例 |
|---|---|---|---|---|
| x86_64 | /usr/include/mysql | /usr/lib/x86_64-linux-gnu | /usr/lib64/mysql | libmysqlclient.so.21 |
| i386 / i686 | 同目录(内容与架构无关) | /usr/lib/i386-linux-gnu | /usr/lib/mysql | libmysqlclient.so.21 |
| ARM64 | 同目录 | /usr/lib/aarch64-linux-gnu | /usr/lib64/mysql | libmysqlclient.so.21 |
关键点在于:头文件与架构无关,库文件与架构强相关。mysql.h里全是typedef和函数声明,32 位和 64 位编译器读的是同一份;但.so里是真正的机器码和 ELF 头,ELFCLASS32和ELFCLASS64混用会直接让链接器或加载器拒绝。
用file一条命令就能验明正身:
file /usr/lib64/mysql/libmysqlclient.so.21 # 期望输出含:ELF 64-bit LSB shared object, x86-64 file /usr/lib/i386-linux-gnu/libmysqlclient.so.21 2>/dev/null # 期望输出含:ELF 32-bit LSB shared object, Intel 80386如果第二条没有任何输出,说明 32 位库根本没装,后面的-m32编译必然失败在链接阶段。这是排错顺序上的一个硬性前置:先确认架构对得上,再谈编译参数。
3. 从 gcc 到 CMake:四种链接写法和各自边界
头文件路径搞清楚了,接下来是把编译命令写对。同一份代码有四种常见接法,复杂度递增,适用场景也不同。选错的代价通常是"编过了但跑不起来"或者"换台机器就得重来"。
3.1 最直白的 gcc 写法:把路径写死
先写一个最小可验证程序,把所有能出问题的地方都暴露出来:
/* conn_demo.c —— 最小连接验证,仅用于确认头文件与库能被正确找到 */ #include <stdio.h> #include <mysql/mysql.h> /* 若用 -I/usr/include/mysql,可改成 <mysql.h> */ int main(void) { MYSQL *conn; /* 初始化句柄;失败通常意味着库版本与头文件不匹配 */ conn = mysql_init(NULL); if (conn == NULL) { fprintf(stderr, "mysql_init failed\n"); return 1; } /* 显式指定 3306 端口,避免客户端库按用户态配置去猜 socket 路径 */ if (mysql_real_connect(conn, "127.0.0.1", "app_user", "app_pass", "testdb", 3306, NULL, 0) == NULL) { fprintf(stderr, "connect failed: %s\n", mysql_error(conn)); mysql_close(conn); return 2; } /* 客户端库版本与服务器版本分开打印,排错时非常有用 */ printf("client: %s\n", mysql_get_client_info()); printf("server: %s\n", mysql_get_server_info(conn)); mysql_close(conn); return 0; }这份代码刻意用了127.0.0.1而不是localhost。原因是localhost在 MySQL 客户端库里会触发 Unix socket 连接,socket 路径写死在库的编译期配置里,跨机器很容易对不上;用 IP 强制走 TCP,问题范围就收窄到网络和认证两件事上。
编译命令的最简形式:
# -I 指头文件目录,-L 指库目录,-lmysqlclient 触发符号链接 gcc -o conn_demo conn_demo.c \ -I/usr/include/mysql \ -L/usr/lib64/mysql \ -lmysqlclient三个参数各司其职:-I只在#include查找时生效,-L只在-l解析时生效,-l按名字去找libmysqlclient.so或libmysqlclient.a。顺序很关键,-l必须放在源文件之后,否则链接器处理到-lmysqlclient时还不知道有哪些未定义符号,GNU ld 会直接丢掉它,表现为"明明加了-l还是 undefined reference"。
这种写法的边界很明显:路径写死,换发行版或换架构就得改;而且没有把传递依赖列全,静态链接时必然失败。只适合本机快速验证。
3.2 交给 mysql_config:让参数自己长出来
把mysql_config嵌进 Makefile 是性价比最高的一招,既避免了手抄路径出错,也自动带上传递依赖:
# Makefile —— 用 mysql_config 生成编译参数,避免路径写死 MYSQL_CONFIG ?= mysql_config CC ?= gcc CFLAGS += $(shell $(MYSQL_CONFIG) --cflags) LDLIBS += $(shell $(MYSQL_CONFIG) --libs) conn_demo: conn_demo.c $(CC) -o $@ $< $(CFLAGS) $(LDLIBS) clean: rm -f conn_demo?=让MYSQL_CONFIG可以被命令行覆盖,这一条是为了后面切换 32 位工具留的口子;$(shell ...)在 make 解析阶段就执行命令并把输出拼进变量,等价于每次构建都问一遍真值。相比硬编码,--libs带回的-lpthread -lz -lm -lrt -lssl -lcrypto -ldl会一并进入链接行,动态链接时多数是冗余但无害,静态链接时则是必需的。
注意:如果机器上装的是 MariaDB 开发包,
mysql_config可能是软链,也可能根本不存在,只有mariadb_config。在 Makefile 里用?=覆盖成mariadb_config即可,两者参数签名一致。
3.3 CMake 工程:find_path 与 find_library 各找一次
CMake 没有 MySQL 的官方 Find 模块,find_package(MySQL)在多数发行版上会失败。稳妥做法是手动分两步找头文件和库,并把架构差异交给变量控制:
# CMakeLists.txt —— 手动定位 MySQL 客户端,适合需要跨架构切换的工程 cmake_minimum_required(VERSION 3.16) project(conn_demo C) # 允许外部指定:cmake -DMYSQL_ROOT=/usr -DMYSQL_LIBDIR=/usr/lib64/mysql .. set(MYSQL_ROOT "/usr" CACHE PATH "MySQL 安装前缀") set(MYSQL_LIBDIR "${MYSQL_ROOT}/lib64/mysql" CACHE PATH "MySQL 库目录") find_path(MYSQL_INCLUDE_DIR NAMES mysql.h PATHS "${MYSQL_ROOT}/include/mysql" NO_DEFAULT_PATH) find_library(MYSQL_CLIENT_LIB NAMES mysqlclient PATHS "${MYSQL_LIBDIR}" NO_DEFAULT_PATH) if(NOT MYSQL_INCLUDE_DIR OR NOT MYSQL_CLIENT_LIB) message(FATAL_ERROR "MySQL 客户端未找到: inc=${MYSQL_INCLUDE_DIR} lib=${MYSQL_CLIENT_LIB}") endif() add_executable(conn_demo conn_demo.c) target_include_directories(conn_demo PRIVATE ${MYSQL_INCLUDE_DIR}) target_link_libraries(conn_demo PRIVATE ${MYSQL_CLIENT_LIB} pthread z m ssl crypto dl)NO_DEFAULT_PATH是这一段的核心:它禁止 CMake 去系统默认路径瞎猜,逼着人显式指定MYSQL_ROOT和MYSQL_LIBDIR。看起来很笨,但换来的是可预测——在做 32 位和 64 位双构建时,只要改这两个缓存变量就能整体切换,不会出现头文件取了 64 位目录、库文件取了 32 位目录这种半吊子状态。FATAL_ERROR那句是把"某一步找不到"提前到配置阶段,比链接期报错好定位得多。
链接行里pthread z m ssl crypto dl是手工补的传递依赖,因为find_library只给出库本身,不会替你展开它的依赖链。如果目标机上的libmysqlclient是静态库,这几个缺一个都会报未定义符号。
3.4 静态链接:把 libmysqlclient.a 吃进可执行文件
需要把程序扔到没有装 MySQL 客户端库的机器上跑时,静态链接是选项之一。前提是开发包里存在libmysqlclient.a:
# 确认静态库是否存在,并查看它依赖哪些符号 ls -l /usr/lib64/mysql/libmysqlclient.a nm -u /usr/lib64/mysql/libmysqlclient.a | head -20静态链接的命令要显式指定.a的完整路径,并把它放在依赖它的目标文件之后:
# 静态链接版本:-Wl,-Bstatic 只对 libmysqlclient 生效,其余仍走动态 gcc -o conn_demo_static conn_demo.c \ -I/usr/include/mysql \ -Wl,-Bstatic -L/usr/lib64/mysql -lmysqlclient -Wl,-Bdynamic \ -lssl -lcrypto -lz -lm -lpthread -ldl -lrt-Wl,-Bstatic和-Wl,-Bdynamic这一对是给链接器切换模式用的,中间只夹-lmysqlclient,让只有这一个库走静态;后面的 OpenSSL、zlib 仍然动态链接。全静态(连 libc 都静态)会带来 DNS 解析和 NSS 的麻烦,一般不建议。
代价要说清楚:静态链接会把 MySQL 客户端库的版本钉死在二进制里,之后服务端升级到用了新认证插件的版本,旧客户端库可能连不上。所以静态方案只适合对部署环境完全可控、且不打算频繁升级的场景。多数情况下动态链接加正确设置运行期库路径,是更省心的选择。
4. 32 位与 64 位双架构:把编译链完整配平
真正让这套流程翻车的,基本都集中在这一章。-m32这个开关只改了编译器前端的目标,没改后面链接器和系统库的架构,所以"加了-m32就报错"几乎是必经之路。
4.1 先用 file 与 readelf 确认目标架构
动手之前必须回答一个问题:目标机上要跑的是 32 位还是 64 位。三条命令足够:
# 1) 看编译器默认目标 gcc -dumpmachine # 输出示例:x86_64-linux-gnu # 2) 看已编译产物的架构 file ./conn_demo # 期望输出含:ELF 64-bit LSB executable, x86-64 # 3) 看 ELF 头里的 class 与 machine 字段 readelf -h ./conn_demo | grep -E 'Class|Machine' # Class: ELF64 / Machine: Advanced Micro Devices X86-64gcc -dumpmachine给出的是默认三元组,x86_64-linux-gnu意味着不加参数就产出 64 位;要产 32 位必须显式-m32,且工具链里得有对应的 32 位运行库。readelf -h比file更精确,Class字段的ELF32/ELF64是链接器和加载器做兼容判断的直接依据。
排查wrong ELF class类错误时,这套命令是唯一可信依据。别拿uname -m判断——它说的是内核架构,跟你的二进制目标架构没有必然关系;64 位内核完全可以跑 32 位程序。
4.2 32 位编译的三块砖:multilib、libc、mysql 库
-m32编不过,最常见的报错是fatal error: bits/libc-header-start.h: No such file or directory或者cannot find -lc。这不是 MySQL 的问题,是基础 32 位开发环境没装。
# Debian 系:装 32 位编译支持 sudo dpkg --add-architecture i386 sudo apt-get update sudo apt-get install -y gcc-multilib libc6-dev-i386 # RHEL 系:装 multilib 版本的 glibc 开发包 sudo yum install -y glibc-devel.i686 libstdc++-devel.i686第一块砖是gcc-multilib,它让-m32能找到 32 位的crt1.o、crti.o这些启动文件;第二块是libc6-dev-i386,提供 32 位的 libc 头文件和libc.so;RHEL 系的glibc-devel.i686起同样作用。这两块缺任意一块,报错都发生在main之前,跟 MySQL 无关。
第三块才是 MySQL 的 32 位库:
# Debian 系:装 32 位 MySQL 客户端开发包 sudo apt-get install -y libmysqlclient-dev:i386 # RHEL 系:装 i686 版本(包名依仓库而定) sudo yum install -y mysql-community-devel.i686 # 装完立刻验明架构 file /usr/lib/i386-linux-gnu/libmysqlclient.so.21dpkg --add-architecture i386这一步不能省,它告诉包管理器这台机器要接收 32 位包;不加的话libmysqlclient-dev:i386会直接报"无法定位软件包"。装完后 32 位库落在/usr/lib/i386-linux-gnu(Debian 系)或/usr/lib/mysql(RHEL 系),与 64 位目录并存互不干扰。
4.3 同一个 Makefile 切两套架构
把架构做成变量,一份 Makefile 出两个产物,比维护两份文件靠谱:
# Makefile.dual —— 一次配置,两种架构产物 MYSQL_CONFIG_64 ?= mysql_config MYSQL_CONFIG_32 ?= mysql_config # 若存在 32 位专用配置脚本则改路径 CC ?= gcc ARCH ?= 64 ifeq ($(ARCH),32) ARCHFLAGS := -m32 LIBDIR := /usr/lib/i386-linux-gnu MYSQL_CFG := $(MYSQL_CONFIG_32) OUT := conn_demo32 else ARCHFLAGS := -m64 LIBDIR := /usr/lib64/mysql MYSQL_CFG := $(MYSQL_CONFIG_64) OUT := conn_demo64 endif CFLAGS += $(ARCHFLAGS) $(shell $(MYSQL_CFG) --cflags) $(OUT): conn_demo.c $(CC) $(CFLAGS) -o $@ $< \ -L$(LIBDIR) -lmysqlclient \ -lpthread -lz -lm -lrt -lssl -lcrypto -ldl all: conn_demo64 conn_demo32 conn_demo32: $(MAKE) ARCH=32 conn_demo32 clean: rm -f conn_demo32 conn_demo64ifeq分支里把ARCHFLAGS、LIBDIR、OUT三个变量一起切,是为了避免"编译用了-m32但链接还是走/usr/lib64/mysql"这种半错状态——那正是wrong ELF class: ELFCLASS64的直接成因。$(MAKE) ARCH=32 conn_demo32是递归调用,保证 32 位那一路在自己的变量环境里完整求值,不会残留 64 位设置。
用法就两条命令:
make conn_demo64 # 默认 64 位 make ARCH=32 conn_demo32 # 32 位 file conn_demo64 conn_demo32 # 两个产物架构应当不同如果第二行编译时报skipping incompatible /usr/lib64/mysql/libmysqlclient.so when searching for -lmysqlclient,说明LIBDIR没切过去,回头检查ifeq分支是否真的命中了;如果报cannot find -lmysqlclient且路径正确,说明 32 位开发包没装,回到 4.2。
4.4 交叉编译到 ARM 时的对应关系
给 ARM 板子编程序时,思路和 32/64 位完全一致,只是换了一组工具。核心原则是:永远不要用宿主机的 mysql_config,它报出的路径属于宿主机架构。
# 交叉编译示例:目标 aarch64,sysroot 里已备好 MySQL 客户端开发文件 CROSS=aarch64-linux-gnu- SYSROOT=/opt/sysroot-aarch64 ${CROSS}gcc -o conn_demo_arm conn_demo.c \ -I${SYSROOT}/usr/include/mysql \ -L${SYSROOT}/usr/lib64/mysql \ -Wl,-rpath-link,${SYSROOT}/usr/lib64 \ -lmysqlclient -lpthread -lz -lm -lrt -lssl -lcrypto -ldl-I和-L全部指向 sysroot 内部,确保取到的是目标架构的头文件和库。-Wl,-rpath-link是交叉链接的关键补充:它告诉链接器去哪个目录解析间接依赖(比如libmysqlclient依赖的libssl.so),只在链接期生效,不写进产物。缺了它,常见报错是warning: libssl.so.1.1, needed by ... not found,紧接着一堆 undefined reference。
验证方式和本机一样:file conn_demo_arm应显示ARM aarch64,readelf -d conn_demo_arm | grep NEEDED应列出libmysqlclient.so.21等依赖项。
5. 排查清单:六个让编译和运行翻车的点
前面三章讲的是"怎么配对",这一章讲"配错了长什么样"。下面六条都按现象、原因、解决三段写,基本覆盖了这条链路上 90% 的报错。
5.1 现象:fatal error: mysql.h: No such file or directory
原因:只装了mysql-client或mysql-server,没装-dev/-devel包;或者装了但头文件在/usr/include/mysql/而代码写的是#include <mysql.h>,缺-I。
解决:先dpkg -l | grep mysql或rpm -qa | grep mysql确认开发包在册。装好之后二选一——要么在编译命令里加-I/usr/include/mysql并保留#include <mysql.h>,要么改成#include <mysql/mysql.h>不加-I。两条路别混着走,混用会出现"有的文件能编、有的不能"的怪状态。
5.2 现象:undefined reference to `mysql_init'
原因:三种可能。一是漏了-lmysqlclient;二是-l放在了源文件前面,被链接器丢弃;三是装了 MariaDB 的兼容头文件但链接的是 MySQL 库,符号命名不完全一致。
解决:先调整顺序,确保-l在所有.c/.o之后。再确认库里确实有这个符号:nm -D /usr/lib64/mysql/libmysqlclient.so.21 | grep mysql_init。若符号存在仍报错,检查是否误加了-Wl,--as-needed且-l位置不对,或头文件来自/usr/include/mariadb而库来自 MySQL——两者的头文件目录必须和库配套使用。
5.3 现象:error while loading shared libraries: libmysqlclient.so.21: cannot open shared object file
原因:编译链接成功,但运行期加载器找不到库。/usr/lib64/mysql通常不在ld.so的默认搜索路径里,装了运行时包但没刷新缓存也会这样。
解决:三条路按推荐度排。首选把目录登记进系统配置再刷新缓存:
# 登记库目录并刷新加载器缓存 echo "/usr/lib64/mysql" | sudo tee /etc/ld.so.conf.d/mysql-client.conf sudo ldconfig -v | grep mysqlclient # 应急:仅当前 shell 生效 export LD_LIBRARY_PATH=/usr/lib64/mysql:$LD_LIBRARY_PATH # 固化进二进制:编译时写入 RUNPATH gcc -o conn_demo conn_demo.c -I/usr/include/mysql \ -L/usr/lib64/mysql -lmysqlclient -Wl,-rpath,/usr/lib64/mysql-Wl,-rpath会把路径写进 ELF 的RUNPATH,用readelf -d conn_demo | grep -E 'RPATH|RUNPATH'可以验证。三种方式优先级是LD_LIBRARY_PATH高于RUNPATH高于ld.so.conf,排错时按这个顺序怀疑。
5.4 现象:skipping incompatible ... when searching for -lmysqlclient
原因:链接器在搜索目录里找到了libmysqlclient.so,但架构与目标不符。典型是编 32 位程序时-L仍指向/usr/lib64/mysql。
解决:用file逐个确认搜索目录里的库架构,再把-L改到对应目录。注意链接器的搜索顺序:多个-L时从左到右,第一个命中的就用了,后面即使有正确架构的也不会被考虑。把正确架构的目录放在最前面,或者干脆只留一个。
5.5 现象:Client does not support authentication protocol requested by server
原因:客户端库版本较老,服务端用了新版默认认证插件。连接本身建立了,认证阶段被拒。这在混用不同来源的头文件与库时尤其常见。
解决:先确认两端版本,mysql_get_client_info()打印客户端库版本,SELECT VERSION()看服务端。优先方案是让客户端库与服务端版本线对齐,重新装配套的开发包。确有兼容需求时,可在服务端为该账号单独配置认证方式,但这是权宜之计,长期看还是要升级客户端库。另外注意:这类问题在编译期完全看不出来,只有连上去才知道,所以最小验证程序必须真的执行一次连接,不能只满足于"编过了"。
5.6 现象:64 位程序正常,同一份代码编成 32 位后连不上或崩溃
原因:头文件是 64 位包带的,库是 32 位包带的,两者版本号或编译宏不一致;或者 32 位库依赖的 OpenSSL 版本与系统里的 32 位 OpenSSL 不匹配。
解决:用ldd看 32 位产物的完整依赖树,重点检查有没有not found:
# 检查 32 位产物的运行期依赖是否齐全 ldd ./conn_demo32 | grep -E 'not found|mysql|ssl|crypto' # 检查 ELF 头里的 INTERP 是否为 32 位加载器 readelf -l ./conn_demo32 | grep interpreter # 期望:/lib/ld-linux.so.2(而非 ld-linux-x86-64.so.2)INTERP段指向/lib/ld-linux.so.2才说明这是真正的 32 位可执行文件;如果指向ld-linux-x86-64.so.2,说明-m32没生效,产物还是 64 位。ldd输出里出现not found的项要逐个补齐,通常是 32 位的libssl或libcrypto没装。
6. 进阶:把配置探测写进构建脚本,一次验完两套架构
前面几章的手工步骤跑通之后,值得做一件事:把"探测—编译—验证"固化成脚本,避免换机器重新踩一遍。我一般会写一个不到四十行的 bash,核心是三段判断。
#!/usr/bin/env bash # build_check.sh —— 探测本机 MySQL 客户端能力,并按可用架构逐个编译验证 set -u SRC=${1:-conn_demo.c} # 1) 探测可用的 config 工具 CFG="" for c in mysql_config mariadb_config; do command -v "$c" >/dev/null 2>&1 && { CFG=$c; break; } done [ -z "$CFG" ] && { echo "未找到 mysql_config / mariadb_config"; exit 1; } echo "== config: $CFG ($($CFG --version)) ==" INC=$($CFG --include) LIBS=$($CFG --libs) # 2) 依次尝试 64 位与 32 位;失败不中断,最后汇总 ok=0; fail=0 for arch in 64 32; do [ "$arch" = "32" ] && { FLAGS="-m32"; LIBDIR="/usr/lib/i386-linux-gnu"; } \ || { FLAGS="-m64"; LIBDIR="/usr/lib64/mysql"; } OUT="conn_demo${arch}" # 编译:头文件用 config 给的,库目录按架构显式覆盖 if gcc $FLAGS -o "$OUT" "$SRC" $INC -L"$LIBDIR" -lmysqlclient \ -lpthread -lz -lm -lrt -lssl -lcrypto -ldl 2>/tmp/err_$arch; then echo "[OK] $OUT $(file -b "$OUT" | cut -d, -f2)" ok=$((ok+1)) else echo "[FAIL] $OUT"; sed 's/^/ /' /tmp/err_$arch | head -5 fail=$((fail+1)) fi done # 3) 汇总:两个都失败才算真失败,只有一个成功通常意味着缺另一架构的开发包 echo "== ok=$ok fail=$fail ==" [ "$ok" -eq 0 ] && exit 2 || exit 0逐段说明。第一段用循环探测mysql_config和mariadb_config,command -v判断可执行性,找到就 break,避免在 MariaDB 机器上直接失败。第二段是核心:INC和LIBS来自 config,但-L被LIBDIR显式覆盖,因为 config 只会报出它自己那套架构的路径,32 位那一路必须手工指定。编译失败时把 stderr 落到/tmp/err_$arch再取前五行打印,既能定位原因又不刷屏。第三段用ok计数决定退出码,只有一个架构成功时返回 0——这种状态下工具链是好的,缺的是另一架构的开发包,属于环境问题而非配置问题。
配套的验证习惯有两个。一个是每次编完必看file,不看到ELF 64-bit或ELF 32-bit就不算完;另一个是拿最小程序真连一次,mysql_get_client_info()和mysql_get_server_info()两行输出能一次性暴露版本错配。这两条加起来花不到十秒,但能挡掉后面几小时的定位。
有个细节值得单独提:conn_demo.c里的#include写法要跟$INC配套。如果$INC展开是-I/usr/include/mysql,源码里就写<mysql.h>;如果$INC为空(某些发行版 config 不给-I),就得写<mysql/mysql.h>。这个组合错了,脚本会一直在编译阶段失败,而报错信息看起来像是"头文件没装"——很容易误导人往错的方向修。
从那以后我每次碰这类跨架构的 C 项目,都会先把这段探测脚本跑一遍,看它打印的ok/fail数字再动手改任何一行构建配置。早期我吃过亏:凭印象以为机器上有 32 位库,直接make ARCH=32,结果在一个skipping incompatible的警告上耗了半天,最后发现只是少装了一个:i386后缀的包。希望帮到你。
本文还有配套的精品资源,点击获取