【MySQL 数据库】使用C语言操作MySQL
文章目录
- 一、关于窗口
- 二、安装库
- 三、使用库
- 3.1 连接数据库操作
- 1. 初始化数据库(核心第一步)
- 2. 连接数据库
- 3. 断开数据库连接
- 4. 连接断开测试(验证连接状态)
- 5. 编译与运行(关键)
- 连接错误
- 3.2 发送SQL
- 3.3 结果读取
- 四、代码示例
- 五、动态链接库路径配置🤔
- 🚩总结
一、关于窗口
下载地址:https://downloads.mysql.com/archives/
本篇使用C语言链接,所以我们选择MySQL Connector/C
选择
这是我的版本:
mysql
客户端版本查看
mysql --version
查看Ubantu版本
lsb_release -a
可以按以下步骤操作,简洁处理这个压缩包:
- 先创建一个新文件夹(比如叫
mysql-connector
,方便管理)
在当前目录执行:
mkdir mysql-connector
- 这会在当前目录创建一个名为
mysql-connector
的文件夹。
- 移动压缩包到新文件夹,同时重命名(一步完成)
执行以下命令,将长名压缩包移到新文件夹,并改个短名字(比如mysql-c.tar.gz
):
mv mysql-connector-c-6.1.11-linux-glibc2.12-x86_64.tar.gz mysql-connector/mysql-c.tar.gz
- 命令解析:
mv 原文件 目标路径/新文件名
- 原文件:当前目录下的长名压缩包
- 目标路径:刚创建的
mysql-connector
文件夹 - 新文件名:
mysql-c.tar.gz
(可自定义,保持.tar.gz
后缀即可)
- 进入新文件夹,后续操作(比如解压)
# 进入新文件夹
cd mysql-connector# 查看是否移动成功(会显示重命名后的压缩包)
ls# 解压(如果需要使用里面的内容)
tar -zxvf mysql-c.tar.gz
- 解压后会得到一个文件夹,里面就是
mysql-connector-c
的相关文件,后续可直接在这个文件夹里操作。
这样处理后,文件结构更清晰,后续使用也更方便~
这里是include和lib目录下的头文件和动静态库的存放
二、安装库
两种方法,第一种分别复制头文件到系统默认头文件目录(方便编译时直接引用),复制库文件到系统默认库目录(方便链接时找到库)
接下来需要将 MySQL Connector/C 库配置到系统中,以便在编译程序时能正确引用其头文件和链接库文件。具体步骤如下:
- 复制头文件到系统默认头文件目录(方便编译时直接引用)
头文件(include
文件夹下的内容)需要放到系统编译器默认搜索的路径(如/usr/local/include
或/usr/include
),这样编译时无需手动指定头文件路径。
执行以下命令(需要 sudo
权限,输入当前用户密码即可):
# 先进入解压后的include目录(当前已在mysql-c目录,直接操作)
sudo cp -r include/* /usr/local/include/
cp -r
:递归复制include下的所有文件和子文件夹;/usr/local/include/
:系统默认的用户级头文件目录,编译器会自动搜索这里。
- 复制库文件到系统默认库目录(方便链接时找到库)
库文件(lib
文件夹下的.a
和.so
文件)需要放到系统链接器默认搜索的路径(如/usr/local/lib
或/usr/lib
)。
执行以下命令:
# 复制lib下的所有库文件到系统库目录
sudo cp lib/* /usr/local/lib/
- 更新系统动态链接库缓存(关键步骤)
Linux 系统通过动态链接库缓存识别已安装的共享库(.so
文件),复制完库文件后需要更新缓存:
sudo ldconfig
- 该命令会刷新
/etc/ld.so.cache
,让系统识别刚复制的libmysqlclient.so
等库文件。
- 验证配置是否成功
可以通过一个简单的测试程序验证库是否能正常使用。例如,创建一个test_mysql.c
文件,内容如下(测试MySQL连接):
#include <mysql.h>
#include <stdio.h>int main() {printf("MySQL Connector/C 版本: %s\n", mysql_get_client_info());return 0;
}
编译该程序(链接 MySQL 库,使用 -lmysqlclient
选项):
gcc test_mysql.c -o test_mysql -lmysqlclient
如果编译成功,运行程序:
./test_mysql
若输出类似 MySQL Connector/C 版本: 6.1.11
的信息,说明库已正确配置,可以正常使用了。
第二中方法就是采用 “在代码中指定头文件路径 + 编译时指定库路径和链接库” 的方式,无需将文件复制到系统目录,更适合局部管理。具体操作步骤如下:
采用“在代码中指定头文件路径 + 编译时指定库路径和链接库”的方式,无需将文件复制到系统目录,更适合局部管理。具体操作步骤如下:
一、代码中指定头文件路径(相对路径引入)
假设你的代码文件(如 mysql_demo.c
)和 mysql-connector
文件夹在同一级目录(例如都在 ~/
下),目录结构如下:
在代码中通过相对路径引入头文件(直接指向 include
目录下的具体头文件),例如:
#include <stdio.h>
#include "../mysql-c/include/mysql.h"int main()
{printf("MySQL Connector 版本: %s\n", mysql_get_client_info());return 0;
}
Makefile
文件:编译时指定库路径和链接库
编译代码时,需要通过 -I
指定头文件搜索路径(确保编译器找到 include
目录),通过 -L
指定库文件搜索路径(确保链接器找到 lib
目录),并通过 -l
指定要链接的库(libmysqlclient
)。
test: test_mysql_client_info.c@gcc -v -o $@ $^ \-I../mysql-c/include \-L../mysql-c/lib \-lmysqlclient
# 指定头文件所在目录(相对路径)
# 指定库文件所在目录(相对路径)
# 链接MySQL客户端库(libmysqlclient.so/.a).PHONY: clean
clean:@rm -rf test
3、运行程序(解决动态库加载问题)
编译成功后生成可执行文件 mysql_demo
,但直接运行可能会提示“找不到 libmysqlclient.so
”(因为系统默认不搜索你的 lib
目录)。
需要临时指定动态库搜索路径,再运行程序:
# 设置环境变量,让系统在运行时搜索你的lib目录
export LD_LIBRARY_PATH=../mysql-c/lib:$LD_LIBRARY_PATH
- 输出类似
MySQL Connector 版本: 6.1.11
即表示成功。
从你的 LD_LIBRARY_PATH
输出来看,确实存在重复添加的路径(多个 ../mysql-c/lib
条目)。这是因为你多次执行 export LD_LIBRARY_PATH=../mysql-c/lib:$LD_LIBRARY_PATH
导致的。
一开始老是不行,试了好多次,最后成功了,但是这个路径重复了,虽然是临时的,没什么问题,但是也可以重置:
解决方法:重置 LD_LIBRARY_PATH
(清理冗余路径)
方法1:临时重置(仅当前终端生效)
在当前终端执行以下命令,覆盖重复的路径,只保留一份有效路径:
export LD_LIBRARY_PATH=../mysql-c/lib
方法2:永久配置(避免重复操作)
编辑用户环境变量文件(~/.bashrc
),只添加一次路径配置:
- 打开配置文件:
nano ~/.bashrc
- 在文件末尾添加一行(替换为你的实际绝对路径,更可靠):
export LD_LIBRARY_PATH=~/mysql-connector/mysql-c/lib:$LD_LIBRARY_PATH
- 保存并退出(
Ctrl+O
保存,Ctrl+X
退出),然后使配置生效:source ~/.bashrc
长期使用的小技巧(可选)
如果需要频繁编译运行,可以将路径配置写入当前用户的环境变量文件(如~/.bashrc
),避免每次手动设置:
# 编辑环境变量文件
nano ~/.bashrc# 在文件末尾添加(路径根据你的实际目录调整)
export MYSQL_INCLUDE=~/mysql-connector/mysql-c/include
export MYSQL_LIB=~/mysql-connector/mysql-c/lib
export LD_LIBRARY_PATH=$MYSQL_LIB:$LD_LIBRARY_PATH# 生效配置
source ~/.bashrc
之后编译命令可简化为:
gcc mysql_demo.c -o mysql_demo -I$MYSQL_INCLUDE -L$MYSQL_LIB -lmysqlclient
这种方式的优势是不污染系统默认目录,所有文件都在你的局部文件夹中管理,适合开发环境或多版本库共存的场景。核心是确保“代码引入路径”“编译参数”“运行时动态库路径”三者一致。
三、使用库
3.1 连接数据库操作
1. 初始化数据库(核心第一步)
函数原型:
MYSQL *mysql_init(MYSQL *mysql);
作用:
初始化一个 MYSQL
结构体(数据库连接句柄),为后续连接数据库做准备(分配内存、设置默认参数等)。
typedef struct st_mysql
{NET net; /* Communication parameters */unsigned char *connector_fd; /* ConnectorFd for SSL */char *host,*user,*passwd,*unix_socket,*server_version,*host_info;char *info, *db;struct charset_info_st *charset;MYSQL_FIELD *fields;MEM_ROOT field_alloc;my_ulonglong affected_rows;my_ulonglong insert_id; /* id if insert on table with NEXTNR */my_ulonglong extra_info; /* Not used */unsigned long thread_id; /* Id for connection in server */unsigned long packet_length;unsigned int port;unsigned long client_flag,server_capabilities;unsigned int protocol_version;unsigned int field_count;unsigned int server_status;unsigned int server_language;unsigned int warning_count;struct st_mysql_options options;enum mysql_status status;my_bool free_me; /* If free in mysql_close */my_bool reconnect; /* set to 1 if automatic reconnect *//* session-wide random string */char scramble[SCRAMBLE_LENGTH+1];my_bool unused1;void *unused2, *unused3, *unused4, *unused5;LIST *stmts; /* list of all statements */const struct st_mysql_methods *methods;void *thd;/*Points to boolean flag in MYSQL_RES or MYSQL_STMT. We set this flagfrom mysql_stmt_close if close had to cancel result set of this object.*/my_bool *unbuffered_fetch_owner;/* needed for embedded server - no net buffer to store the 'info' */char *info_buffer;void *extension;
} MYSQL;
参数与返回值:
- 参数
mysql
:若传入NULL
,函数会自动分配一个新的MYSQL
结构体;若传入已存在的结构体,会重置其状态。 - 返回值:成功返回初始化后的
MYSQL*
指针;失败返回NULL
(通常因内存不足)。
示例代码:
#include "./mysql-connector/mysql-c/include/mysql.h" // 头文件路径(根据实际调整)
#include <stdio.h>int main() {// 1. 初始化数据库连接句柄MYSQL *conn = mysql_init(NULL); // 传入NULL,自动分配新结构体if (conn == NULL) {// 初始化失败:通过mysql_error获取错误信息(需注意:mysql_error需传入非NULL的conn,此处直接打印内存不足)fprintf(stderr, "初始化数据库失败:内存不足\n");return 1; // 退出程序}printf("数据库初始化成功\n");// 后续步骤:连接数据库...
}
2. 连接数据库
函数原型:
MYSQL *mysql_real_connect(MYSQL *mysql, // 已初始化的MYSQL结构体指针const char *host, // 数据库主机地址(本地可填"localhost"或"127.0.0.1")const char *user, // 数据库用户名(如"root")const char *passwd, // 用户名对应的密码const char *db, // 要连接的数据库名(可选,可后续用mysql_select_db切换)unsigned int port, // 数据库端口(MySQL默认3306,填0表示使用默认)const char *unix_socket,// Unix套接字(本地连接可填NULL,使用默认)unsigned long client_flag // 客户端标志(通常填0,使用默认行为)
);
作用:
基于已初始化的 MYSQL
结构体,与指定的 MySQL 服务器建立连接。
返回值:
- 成功:返回与参数
mysql
相同的指针(连接句柄); - 失败:返回
NULL
,可通过mysql_error(conn)
获取具体错误信息。
示例代码(接初始化步骤):
// 2. 连接数据库
const char *host = "localhost"; // 主机地址
const char *user = "root"; // 用户名(替换为你的MySQL用户名)
const char *passwd = "123456"; // 密码(替换为你的MySQL密码)
const char *db = "test"; // 要连接的数据库名(若不存在可先在MySQL中创建)
unsigned int port = 3306; // 默认端口if (mysql_real_connect(conn, host, user, passwd, db, port, NULL, 0) == NULL) {// 连接失败:打印错误信息(mysql_error需传入初始化后的conn)fprintf(stderr, "连接数据库失败:%s\n", mysql_error(conn));mysql_close(conn); // 释放已初始化的句柄return 1;
}
printf("数据库连接成功\n");
3. 断开数据库连接
函数原型:
void mysql_close(MYSQL *mysql);
作用:
关闭与 MySQL 服务器的连接,释放 MYSQL
结构体占用的内存(包括初始化时分配的资源)。
参数:
mysql
:已连接的MYSQL
结构体指针(若为NULL
,函数无操作)。
示例代码(接连接成功后):
// 3. 断开连接(通常在程序结束或不需要连接时调用)
mysql_close(conn);
printf("数据库连接已断开\n");
4. 连接断开测试(验证连接状态)
连接成功后,可通过执行简单 SQL 语句(如查询版本)验证连接有效性;断开后,可尝试执行操作验证是否已断开。
完整测试示例:
int main() {// 步骤1:初始化MYSQL *conn = mysql_init(NULL);if (conn == NULL) {fprintf(stderr, "初始化失败:内存不足\n");return 1;}// 步骤2:连接数据库const char *host = "127.0.0.1";const char *user = "root";const char *passwd = "123456";const char *db = "test1";if (mysql_real_connect(conn, host, user, passwd, db, 3306, NULL, 0) == NULL) {fprintf(stderr, "连接失败:%s\n", mysql_error(conn));mysql_close(conn);return 1;}printf("连接成功!\n");// 测试1:连接状态验证(执行简单SQL)if (mysql_query(conn, "SELECT VERSION()")) { // 执行查询MySQL版本的SQLfprintf(stderr, "查询失败:%s\n", mysql_error(conn));} else {MYSQL_RES *result = mysql_store_result(conn); // 获取查询结果if (result == NULL) {fprintf(stderr, "获取结果失败:%s\n", mysql_error(conn));} else {MYSQL_ROW row = mysql_fetch_row(result); // 提取一行结果if (row != NULL) {printf("MySQL服务器版本:%s\n", row[0]); // 打印版本}mysql_free_result(result); // 释放结果集内存}}// 步骤3:断开连接mysql_close(conn);printf("已断开连接\n");// 测试2:验证断开状态(尝试执行操作应失败)// 注意:断开后conn已无效,不可再使用mysql_query等函数(会导致未定义行为)// 此处仅作逻辑验证:若强行调用,可能崩溃或返回错误printf("验证断开状态:尝试执行操作...\n");// (实际开发中无需此步,断开后应避免使用conn)return 0;
}
5. 编译与运行(关键)
编译时需指定头文件路径、库路径和链接库,命令如下(假设代码文件为 mysql_connect_demo.c
):
test: test_mysql_client_info.c@gcc -o $@ $^ \-I../mysql-c/include \-L../mysql-c/lib \-lmysqlclient
# 指定头文件所在目录(相对路径)
# 指定库文件所在目录(相对路径)
# 链接MySQL客户端库(libmysqlclient.so/.a).PHONY: clean
clean:@rm -rf test
运行前需指定动态库路径(否则可能提示“找不到 libmysqlclient.so”):
export LD_LIBRARY_PATH=./mysql-c/lib:$LD_LIBRARY_PATH
这里就配置你自己的路径就好了
然后就是运行:
连接错误
这次连接数据库的过程中,主要遇到了以下几类核心错误,总结如下:
- 客户端库与MySQL服务器版本不兼容
- 错误表现:段错误(核心已转储)、SSL连接错误(
SSL connection error: unknown error number
)。 - 原因:使用的
mysql-connector-c-6.1.11
是2016年的旧版本,而MySQL服务器是8.0.42(2025年版本)。两者在SSL协议处理、认证插件、数据包格式等方面存在兼容性差异:- 旧库不支持MySQL 8.0默认的
caching_sha2_password
认证插件(需手动改为mysql_native_password
); - 旧库对MySQL 8.0的SSL协议协商逻辑支持不完善,即使禁用SSL也可能触发错误。
- 旧库不支持MySQL 8.0默认的
- 解决:通过修改认证插件为旧库兼容的
mysql_native_password
,或最终通过连接方式规避版本差异。
- SSL连接强制协商导致失败
- 错误表现:
SSL connection error: unknown error number
。 - 原因:
- MySQL 8.0默认启用SSL,且对SSL协议的要求更严格;
- 旧客户端库(6.1.11)不支持
MYSQL_OPT_SSL_MODE
等新参数,仅能通过mysql_ssl_set
禁用SSL,但效果有限; - 使用
127.0.0.1
连接时强制走TCP/IP协议,更容易触发SSL协商,而旧库无法正确处理。
- 解决:
- 用
mysql_ssl_set(conn, NULL, NULL, NULL, NULL, NULL)
彻底禁用SSL(旧库唯一支持的方式); - 改用
localhost
作为主机名,优先使用 Unix socket连接(绕开TCP/IP的SSL协商); - 显式指定socket路径(
/var/run/mysqld/mysqld.sock
),确保客户端通过文件系统socket连接(非网络连接,几乎不涉及SSL)。
- 用
- 连接方式选择不当(TCP/IP vs Unix socket)
- 错误表现:同样的代码,用
127.0.0.1
连接失败,用localhost
连接成功。 - 原因:
127.0.0.1
强制使用TCP/IP协议连接,会触发MySQL的SSL协商逻辑,旧库处理不当;localhost
优先使用Unix socket连接(通过文件系统通信,非网络连接),MySQL对这种连接默认不强制SSL,且兼容性更好。
- 解决:优先使用
localhost
作为主机名,并显式指定socket路径(从SHOW VARIABLES LIKE 'socket'
获取),强制走Unix socket连接。
- 环境配置细节问题
- 动态库路径重复:多次执行
export LD_LIBRARY_PATH=../mysql-c/lib:$LD_LIBRARY_PATH
导致路径冗余,虽不直接报错,但可能引发混淆(通过重置路径解决)。 - Makefile格式错误:命令行未用制表符(Tab)开头,或
-O
与-o
混淆,导致编译失败(通过修正Makefile语法解决)。
mysql> SHOW VARIABLES LIKE '%ssl%';
+-------------------------------------+-----------------+
| Variable_name | Value |
+-------------------------------------+-----------------+
| admin_ssl_ca | |
| admin_ssl_capath | |
| admin_ssl_cert | |
| admin_ssl_cipher | |
| admin_ssl_crl | |
| admin_ssl_crlpath | |
| admin_ssl_key | |
| have_openssl | YES |
| have_ssl | YES |
| mysqlx_ssl_ca | |
| mysqlx_ssl_capath | |
| mysqlx_ssl_cert | |
| mysqlx_ssl_cipher | |
| mysqlx_ssl_crl | |
| mysqlx_ssl_crlpath | |
| mysqlx_ssl_key | |
| performance_schema_show_processlist | OFF |
| ssl_ca | ca.pem |
| ssl_capath | |
| ssl_cert | server-cert.pem |
| ssl_cipher | |
| ssl_crl | |
| ssl_crlpath | |
| ssl_fips_mode | OFF |
| ssl_key | server-key.pem |
| ssl_session_cache_mode | ON |
| ssl_session_cache_timeout | 300 |
+-------------------------------------+-----------------+
27 rows in set (0.13 sec)mysql> SHOW VARIABLES LIKE 'require_secure_transport';
+--------------------------+-------+
| Variable_name | Value |
+--------------------------+-------+
| require_secure_transport | OFF |
+--------------------------+-------+
1 row in set (0.00 sec)mysql> SHOW VARIABLES LIKE 'socket';
+---------------+-----------------------------+
| Variable_name | Value |
+---------------+-----------------------------+
| socket | /var/run/mysqld/mysqld.sock |
+---------------+-----------------------------+
1 row in set (0.01 sec)mysql>
纠正后代码:
#include <stdio.h>
#include "../mysql-c/include/mysql.h"int main() {// 步骤1:初始化MYSQL *conn = mysql_init(NULL);if (conn == NULL) {fprintf(stderr, "初始化失败:内存不足\n");return 1;}// 关键1:旧库唯一支持的禁用SSL方式mysql_ssl_set(conn, NULL, NULL, NULL, NULL, NULL);// 步骤2:连接数据库(用localhost + 显式socket路径,强制Unix socket连接)const char *host = "localhost"; // 必须用localhost,优先socketconst char *user = "root";const char *passwd = "qazxsw123";const char *db = "test2"; unsigned int port = 3306;const char *unix_socket = "/var/run/mysqld/mysqld.sock"; // 从SHOW VARIABLES LIKE 'socket'获取的路径if (mysql_real_connect(conn, host, user, passwd, db, port, unix_socket, 0) == NULL) {fprintf(stderr, "连接失败:%s\n", mysql_error(conn));mysql_close(conn);return 1;}printf("连接成功!\n");// 步骤3:断开连接mysql_close(conn);printf("已断开连接\n");return 0;
}
3.2 发送SQL
- SQL发送函数
mysql_query
是 MySQL C API 中用于执行 SQL 语句的核心函数,属于同步执行模式(会阻塞直到 SQL 执行完成)。以下是其详细说明:
函数原型
int STDCALL mysql_query(MYSQL *mysql, const char *q);参数说明
- `MYSQL *mysql`:数据库连接句柄(需先通过 `mysql_init` 和 `mysql_real_connect` 初始化并建立连接)。
- `const char *q`:要执行的 SQL 语句字符串(如 `SELECT * FROM table`、`INSERT INTO table VALUES (1, 'test')` 等)。返回值
- **成功**:返回 `0`。
- **失败**:返回非 `0`,可通过 `mysql_error(mysql)` 获取具体错误信息。
- 设置编码格式
用于指定 MySQL 连接的字符集,确保客户端与服务器之间的字符编码一致,避免因字符集不兼容导致的中文乱码、特殊字符解析错误等问题。
int STDCALL mysql_set_character_set(MYSQL *mysql, const char *csname);
参数说明
MYSQL *mysql:已通过mysql_init初始化并通过mysql_real_connect建立连接的数据库句柄。
const char *csname:要设置的字符集名称,例如"utf8"、"gbk"、"latin1"等(需与 MySQL 服务器支持的字符集匹配)。
返回值
成功:返回0。
失败:返回非0,可通过mysql_error(mysql)获取具体错误信息。
整合代码:
#include <stdio.h>
#include "../mysql-c/include/mysql.h"int main()
{// 步骤1:初始化MYSQL *conn = mysql_init(NULL);if (conn == NULL){fprintf(stderr, "初始化失败:内存不足\n");return 1;}// 禁用SSLmysql_ssl_set(conn, NULL, NULL, NULL, NULL, NULL);// 连接数据库const char *host = "localhost";const char *user = "root";const char *passwd = "qazxsw123";const char *db = "cuserDB";unsigned int port = 3306;const char *unix_socket = "/var/run/mysqld/mysqld.sock";if (mysql_real_connect(conn, host, user, passwd, db, port, unix_socket, 0) == NULL){fprintf(stderr, "连接失败:%s\n", mysql_error(conn));mysql_close(conn);return 1;}printf("mysql 连接成功!\n");// 设置字符集(支持中文)mysql_set_character_set(conn, "utf8mb4"); // 推荐utf8mb4(兼容所有中文和emoji)// 1. 插入数据(变量名修改为sql_insert,避免重复)const char *sql_insert = "insert into user values (4, '赵六', 21)";if (mysql_query(conn, sql_insert) != 0) // 修正:用!=0判断失败(0表示成功){fprintf(stderr, "插入数据失败:%s\n", mysql_error(conn)); // 增加具体错误信息mysql_close(conn);return 2;}printf("插入数据成功!\n");// 2. 删除数据(变量名修改为sql_delete)const char *sql_delete = "delete from user where id=2";if (mysql_query(conn, sql_delete) != 0){fprintf(stderr, "删除数据失败:%s\n", mysql_error(conn));mysql_close(conn);return 2;}printf("删除数据成功!\n");// 3. 修改数据(变量名修改为sql_update)const char *sql_update = "update user set age=66 where id=1";if (mysql_query(conn, sql_update) != 0){fprintf(stderr, "修改数据失败:%s\n", mysql_error(conn));mysql_close(conn);return 2;}printf("修改数据成功!\n");// 断开连接mysql_close(conn);printf("mysql 已断开连接\n");return 0;
}
运行前后图
3.3 结果读取
sql执行完以后,如果是查询语句,我们当然还要读取数据,如果update,insert等语句,那么就看下操作成功与否即可。我们来看看如何获取查询结果: 如果mysql_query返回成功,那么我们就通过mysql_store_result这个函数来读取结果。原型如下:
执行查询类SQL(如SELECT
)后,将整个结果集加载到客户端内存,以便后续通过mysql_fetch_row
等函数逐行读取数据。
MYSQL_RES * STDCALL mysql_store_result(MYSQL *mysql);2. 参数
- `MYSQL *mysql`:已初始化并建立连接的数据库句柄(需通过`mysql_init`和`mysql_real_connect`创建)。3. 返回值
- 成功:返回`MYSQL_RES *`类型的结果集指针,可用于后续结果集操作(如`mysql_fetch_row`、`mysql_num_rows`等)。
- 失败:返回`NULL`,可通过`mysql_error(mysql)`获取具体错误信息。
- 适用场景与注意事项
- 适用场景:适合结果集较小的查询,因为会一次性将所有数据加载到内存,便于快速随机访问。
- 注意事项:
- 仅在执行查询类SQL(如
SELECT
)后有效,执行增删改SQL(如INSERT
、DELETE
)时调用会返回NULL
; - 若结果集过大,可能导致客户端内存占用过高,此时可考虑使用
mysql_use_result
(仅加载元数据,逐行从服务器读取数据); - 使用后需通过
mysql_free_result
释放结果集内存,避免内存泄漏。
- 仅在执行查询类SQL(如
typedef struct st_mysql_res {my_ulonglong row_count;MYSQL_FIELD *fields;MYSQL_DATA *data;MYSQL_ROWS *data_cursor;unsigned long *lengths; /* column lengths of current row */MYSQL *handle; /* for unbuffered reads */const struct st_mysql_methods *methods;MYSQL_ROW row; /* If unbuffered read */MYSQL_ROW current_row; /* buffer to current row */MEM_ROOT field_alloc;unsigned int field_count, current_field;my_bool eof; /* Used by mysql_fetch_row *//* mysql_stmt_close() had to cancel this result */my_bool unbuffered_fetch_cancelled;void *extension;
} MYSQL_RES;
该函数会调用MYSQL变量中的st_mysql_methods中的 read_rows 函数指针来获取查询的结果。同时该函数会返回MYSQL_RES 这样一个变量,该变量主要用于保存查询的结果。同时该函数malloc了一片内存空间来存储查询过来的数据,所以我们一定要记的 free(result),不然是肯定会造成内存泄漏的。 执行完mysql_store_result以后,其实数据都已经MYSQL_RES
变量中了,下面的api基本就是读取MYSQL_RES 中的数据。
- 获取查询结果行数和列数
获取结果行数mysql_num_rows
my_ulonglong mysql_num_rows(MYSQL_RES *res);
获取结果列数mysql_num_fields
unsigned int mysql_num_fields(MYSQL_RES *res);
- 获取表属性
获取列名mysql_fetch_fields
MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *res);
MYSQL_FIELD对象结构体:
typedef struct st_mysql_field {char *name; /* Name of column */char *org_name; /* Original column name, if an alias */char *table; /* Table of column if column was a field */char *org_table; /* Org table name, if table was an alias */char *db; /* Database for table */char *catalog; /* Catalog for table */char *def; /* Default value (set by mysql_list_fields) */unsigned long length; /* Width of column (create length) */unsigned long max_length; /* Max width for selected set */unsigned int name_length;unsigned int org_name_length;unsigned int table_length;unsigned int org_table_length;unsigned int db_length;unsigned int catalog_length;unsigned int def_length;unsigned int flags; /* Div flags */unsigned int decimals; /* Number of decimals in field */unsigned int charsetnr; /* Character set */enum enum_field_types type; /* Type of field. See mysql_com.h for types */void *extension;
} MYSQL_FIELD;
- 获取查询结果中的一行数据
获取结果内容mysql_fetch_row
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result);
它会返回一个MYSQL_ROW变量,MYSQL_ROW其实就是char **.就当成一个二维数组来用吧。
关闭mysql链接mysql_close
void mysql_close(MYSQL *sock);
mysql C api
还支持事务等常用操作,大家下来自行了解:
my_bool STDCALL mysql_autocommit(MYSQL * mysql, my_bool auto_mode);my_bool STDCALL mysql_commit(MYSQL * mysql);my_bool STDCALL mysql_rollback(MYSQL * mysql);
四、代码示例
#include <unistd.h>
#include <stdio.h>
#include "../mysql-c/include/mysql.h"// 数据库连接配置(C语言用const char*存储字符串)
const char* DB_HOST = "localhost"; // 主机地址
const char* DB_USER = "root"; // 用户名
const char* DB_PASS = "qazxsw123"; // 密码
const char* DB_NAME = "cuserDB"; // 数据库名
const unsigned int DB_PORT = 3306; // 端口
const char *unix_socket = "/var/run/mysqld/mysqld.sock";int main()
{//1,初始化MYSQL连接句柄// MYSQL是C API核心结构体,mysql_init返回NULL表示初始化失败MYSQL* mysql_conn = mysql_init(NULL);if(mysql_conn == NULL){fprintf(stderr, "初始化MySQL失败!可能是内存不足。\n");return 1;}// 高版本对应适配(可选)禁用SSLmysql_ssl_set(mysql_conn, NULL, NULL, NULL, NULL, NULL);//2,连接数据库MYSQL* conn_result = mysql_real_connect(mysql_conn,DB_HOST,DB_USER,DB_PASS,DB_NAME,DB_PORT,unix_socket, //指定使用unix socket,不用SSL的TCP连接, 因为MySQL8.0以上版本和conncetor-c的6.11版本不适配,导致SSL的连接错误,这里只能使用原生的unix socket连接,0 //默认客户端标志 //如果要适配下载更高对应版本的connect-xxxx对应版本的使用就行);if(conn_result == NULL){fprintf(stderr, "连接数据库失败!错误信息:%s\n", mysql_error(conn_result));mysql_close(mysql_conn);return 2;}printf("连接数据库成功!");//3,设置字符集if(mysql_set_character_set(mysql_conn, "utf8mb4") != 0){fprintf(stderr,"设置字符集失败!错误信息:%s\n", mysql_error(mysql_conn));mysql_close(mysql_conn);return 3;}printf("字符集设置为utf8mb4(支持中文)\n");//4,执行查询SQL(查询user表中的所有数据)const char* sql = "select * from user;";int query_result = mysql_query(mysql_conn, sql);if(query_result != 0){fprintf(stderr, "执行SQL失败!SQL:%s 错误信息:%s\n", sql, mysql_error(mysql_conn));mysql_close(mysql_conn);return 4;}printf("SQL执行成功!SQL:%s\n", sql);//5, 获取查询结果集MYSQL_RES* result_set = mysql_store_result(mysql_conn);if(result_set == NULL){fprintf(stderr, "获取结果集失败!错误信息:%s\n", mysql_error(mysql_conn));mysql_close(mysql_conn);return 5;}//6, 处理结果集:获取行数和列数unsigned int row_count = mysql_num_rows(result_set); //行数unsigned int field_count = mysql_num_fields(result_set); //列数printf("查询到 %u 条数据,共 %u 个字段\n", row_count, field_count);//7, 打印字段名(表头)MYSQL_FIELD* fields = mysql_fetch_fields(result_set);for(unsigned int i = 0; i < field_count; ++i){printf("%s\t", fields[i].name);}printf("\n");//8, 打印每条记录的数据// / mysql_fetch_row逐行获取数据,返回NULL表示无更多行MYSQL_ROW row;while((row = mysql_fetch_row(result_set)) != NULL){for(unsigned int j = 0; j < field_count; ++j){//处理NULL值(数据库中为NULL时, row[j]是NULL)if(row[j] == NULL){printf("NULL\t");}else{printf("%s\t", row[j]); //打印字段值}}printf("\n"); }//9,释放结果集资源(必须手动释放, 避免内存泄露)mysql_free_result(result_set);printf("结果集资源已释放\n");//10, 关闭数据库连接mysql_close(mysql_conn);printf("数据库连接已经关闭\n");return 0;
}
Makefile文件:
test: test_mysql_client_info.c@gcc -o $@ $^ \-I../mysql-c/include \-L../mysql-c/lib \-lmysqlclient
# 指定头文件所在目录(相对路径)
# 指定库文件所在目录(相对路径)
# 链接MySQL客户端库(libmysqlclient.so/.a).PHONY: clean
clean:@rm -rf test
五、动态链接库路径配置🤔
在上级目录上虽然临时的导入库的链接地址,但是去执行程序时,但是还是无法找到,重新需要本执行程序路径下导入库文件路径才行,export环境变量每个路径都不相同,各自独属一份
现象本质
error while loading shared libraries: libmysqlclient.so.18: cannot open shared object file
表示程序运行时找不到 MySQL 客户端的动态库文件。LD_LIBRARY_PATH
是 Linux 用于指定动态库搜索路径的环境变量,但它是**“会话级”且“目录敏感”**的——在不同目录下执行 export
命令,环境变量的作用域仅在当前 shell 会话的当前目录上下文生效,切换目录后若未重新配置,路径可能失效。
解决方法(按优先级推荐)
方法1:在执行程序的目录下,显式配置正确的 LD_LIBRARY_PATH
每次在 test_use
目录执行程序前,确保 LD_LIBRARY_PATH
指向 MySQL 动态库的真实路径:
# 假设 mysql-c 的 lib 目录在上级目录的 mysql-c/lib 下
export LD_LIBRARY_PATH=../mysql-c/lib:$LD_LIBRARY_PATH
# 然后执行程序
./test
方法2:将动态库路径永久加入系统库搜索路径
这种方式无需每次手动设置 LD_LIBRARY_PATH
,系统会永久识别该路径:
- 创建自定义库配置文件:
sudo nano /etc/ld.so.conf.d/mysql-client.conf
- 在文件中添加 MySQL 动态库的绝对路径(例如
~/mysql-connector/mysql-c/lib
):/home/wenksen/mysql-connector/mysql-c/lib
- 刷新系统库缓存:
sudo ldconfig
方法3:编译时指定动态库的 rpath(运行时路径)
在编译程序时,直接将动态库的路径嵌入可执行文件,使其运行时自动查找该路径:
修改 Makefile
,在编译选项中加入 -Wl,-rpath=../mysql-c/lib
(假设 mysql-c/lib
是动态库目录的相对路径):
test: test_mysql_client_info.cgcc -o test test_mysql_client_info.c -I../mysql-c/include -L../mysql-c/lib -lmysqlclient -Wl,-rpath=../mysql-c/lib