【MySQL 数据库】使用C语言操作MySQL

文章目录


一、关于窗口

下载地址:https://downloads.mysql.com/archives/

本篇使用C语言链接,所以我们选择MySQL Connector/C

选择

这是我的版本:
mysql客户端版本查看

cpp 复制代码
mysql --version

查看Ubantu版本

cpp 复制代码
lsb_release -a

可以按以下步骤操作,简洁处理这个压缩包:

  1. 先创建一个新文件夹(比如叫 mysql-connector,方便管理)
    在当前目录执行:
bash 复制代码
mkdir mysql-connector
  • 这会在当前目录创建一个名为 mysql-connector 的文件夹。
  1. 移动压缩包到新文件夹,同时重命名(一步完成)
    执行以下命令,将长名压缩包移到新文件夹,并改个短名字(比如 mysql-c.tar.gz):
bash 复制代码
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 后缀即可)
  1. 进入新文件夹,后续操作(比如解压)
bash 复制代码
# 进入新文件夹
cd mysql-connector

# 查看是否移动成功(会显示重命名后的压缩包)
ls

# 解压(如果需要使用里面的内容)
tar -zxvf mysql-c.tar.gz
  • 解压后会得到一个文件夹,里面就是 mysql-connector-c 的相关文件,后续可直接在这个文件夹里操作。


这样处理后,文件结构更清晰,后续使用也更方便~

这里是include和lib目录下的头文件和动静态库的存放

二、安装库

两种方法,第一种分别复制头文件到系统默认头文件目录(方便编译时直接引用),复制库文件到系统默认库目录(方便链接时找到库)

接下来需要将 MySQL Connector/C 库配置到系统中,以便在编译程序时能正确引用其头文件和链接库文件。具体步骤如下:

  1. 复制头文件到系统默认头文件目录(方便编译时直接引用)
    头文件(include 文件夹下的内容)需要放到系统编译器默认搜索的路径(如 /usr/local/include/usr/include),这样编译时无需手动指定头文件路径。

执行以下命令(需要 sudo 权限,输入当前用户密码即可):

bash 复制代码
# 先进入解压后的include目录(当前已在mysql-c目录,直接操作)
sudo cp -r include/* /usr/local/include/
  • cp -r:递归复制include下的所有文件和子文件夹;
  • /usr/local/include/:系统默认的用户级头文件目录,编译器会自动搜索这里。
  1. 复制库文件到系统默认库目录(方便链接时找到库)
    库文件(lib 文件夹下的 .a.so 文件)需要放到系统链接器默认搜索的路径(如 /usr/local/lib/usr/lib)。

执行以下命令:

bash 复制代码
# 复制lib下的所有库文件到系统库目录
sudo cp lib/* /usr/local/lib/
  1. 更新系统动态链接库缓存(关键步骤)
    Linux 系统通过动态链接库缓存识别已安装的共享库(.so 文件),复制完库文件后需要更新缓存:
bash 复制代码
sudo ldconfig
  • 该命令会刷新 /etc/ld.so.cache,让系统识别刚复制的 libmysqlclient.so 等库文件。
  1. 验证配置是否成功
    可以通过一个简单的测试程序验证库是否能正常使用。例如,创建一个 test_mysql.c 文件,内容如下(测试MySQL连接):
c 复制代码
#include <mysql.h>
#include <stdio.h>

int main() {
    printf("MySQL Connector/C 版本: %s\n", mysql_get_client_info());
    return 0;
}

编译该程序(链接 MySQL 库,使用 -lmysqlclient 选项):

bash 复制代码
gcc test_mysql.c -o test_mysql -lmysqlclient

如果编译成功,运行程序:

bash 复制代码
./test_mysql

若输出类似 MySQL Connector/C 版本: 6.1.11 的信息,说明库已正确配置,可以正常使用了。

第二中方法就是采用 "在代码中指定头文件路径 + 编译时指定库路径和链接库" 的方式,无需将文件复制到系统目录,更适合局部管理。具体操作步骤如下:

采用"在代码中指定头文件路径 + 编译时指定库路径和链接库"的方式,无需将文件复制到系统目录,更适合局部管理。具体操作步骤如下:

一、代码中指定头文件路径(相对路径引入)

假设你的代码文件(如 mysql_demo.c)和 mysql-connector 文件夹在同一级目录(例如都在 ~/ 下),目录结构如下:

在代码中通过相对路径 引入头文件(直接指向 include 目录下的具体头文件),例如:

c 复制代码
#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)。

cpp 复制代码
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 目录)。

需要临时指定动态库搜索路径,再运行程序:

bash 复制代码
# 设置环境变量,让系统在运行时搜索你的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:临时重置(仅当前终端生效)

在当前终端执行以下命令,覆盖重复的路径,只保留一份有效路径:

bash 复制代码
export LD_LIBRARY_PATH=../mysql-c/lib

方法2:永久配置(避免重复操作)

编辑用户环境变量文件(~/.bashrc),只添加一次路径配置:

  1. 打开配置文件:

    bash 复制代码
    nano ~/.bashrc
  2. 在文件末尾添加一行(替换为你的实际绝对路径,更可靠):

    bash 复制代码
    export LD_LIBRARY_PATH=~/mysql-connector/mysql-c/lib:$LD_LIBRARY_PATH
  3. 保存并退出(Ctrl+O 保存,Ctrl+X 退出),然后使配置生效:

    bash 复制代码
    source ~/.bashrc


长期使用的小技巧(可选)

如果需要频繁编译运行,可以将路径配置写入当前用户的环境变量文件(如 ~/.bashrc),避免每次手动设置:

bash 复制代码
# 编辑环境变量文件
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

之后编译命令可简化为:

bash 复制代码
gcc mysql_demo.c -o mysql_demo -I$MYSQL_INCLUDE -L$MYSQL_LIB -lmysqlclient

这种方式的优势是不污染系统默认目录,所有文件都在你的局部文件夹中管理,适合开发环境或多版本库共存的场景。核心是确保"代码引入路径""编译参数""运行时动态库路径"三者一致。

三、使用库

3.1 连接数据库操作

1. 初始化数据库(核心第一步)

函数原型

c 复制代码
MYSQL *mysql_init(MYSQL *mysql);

作用

初始化一个 MYSQL 结构体(数据库连接句柄),为后续连接数据库做准备(分配内存、设置默认参数等)。

cpp 复制代码
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 flag
    from 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(通常因内存不足)。

示例代码

c 复制代码
#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. 连接数据库

函数原型

c 复制代码
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) 获取具体错误信息。

示例代码(接初始化步骤):

c 复制代码
// 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. 断开数据库连接

函数原型

c 复制代码
void mysql_close(MYSQL *mysql);

作用

关闭与 MySQL 服务器的连接,释放 MYSQL 结构体占用的内存(包括初始化时分配的资源)。

参数

  • mysql:已连接的 MYSQL 结构体指针(若为 NULL,函数无操作)。

示例代码(接连接成功后):

c 复制代码
// 3. 断开连接(通常在程序结束或不需要连接时调用)
mysql_close(conn);
printf("数据库连接已断开\n");

4. 连接断开测试(验证连接状态)

连接成功后,可通过执行简单 SQL 语句(如查询版本)验证连接有效性;断开后,可尝试执行操作验证是否已断开。

完整测试示例

c 复制代码
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版本的SQL
        fprintf(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):

bash 复制代码
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"):

bash 复制代码
export LD_LIBRARY_PATH=./mysql-c/lib:$LD_LIBRARY_PATH

这里就配置你自己的路径就好了

然后就是运行:

连接错误

这次连接数据库的过程中,主要遇到了以下几类核心错误,总结如下:

  1. 客户端库与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_native_password,或最终通过连接方式规避版本差异。
  1. 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)。
  1. 连接方式选择不当(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连接。
  1. 环境配置细节问题
  • 动态库路径重复 :多次执行 export LD_LIBRARY_PATH=../mysql-c/lib:$LD_LIBRARY_PATH 导致路径冗余,虽不直接报错,但可能引发混淆(通过重置路径解决)。
  • Makefile格式错误 :命令行未用制表符(Tab)开头,或 -O-o 混淆,导致编译失败(通过修正Makefile语法解决)。
cpp 复制代码
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> 

纠正后代码:

cpp 复制代码
#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,优先socket
    const 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

  1. SQL发送函数
    mysql_query 是 MySQL C API 中用于执行 SQL 语句 的核心函数,属于同步执行模式(会阻塞直到 SQL 执行完成)。以下是其详细说明:
    函数原型
c 复制代码
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)` 获取具体错误信息。
  1. 设置编码格式
    用于指定 MySQL 连接的字符集,确保客户端与服务器之间的字符编码一致,避免因字符集不兼容导致的中文乱码、特殊字符解析错误等问题。
cpp 复制代码
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)获取具体错误信息。

整合代码:

cpp 复制代码
#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;
    }

    // 禁用SSL
    mysql_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等函数逐行读取数据。

cpp 复制代码
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)`获取具体错误信息。
  1. 适用场景与注意事项
  • 适用场景:适合结果集较小的查询,因为会一次性将所有数据加载到内存,便于快速随机访问。
  • 注意事项
    • 仅在执行查询类SQL (如SELECT)后有效,执行增删改SQL(如INSERTDELETE)时调用会返回NULL
    • 若结果集过大,可能导致客户端内存占用过高,此时可考虑使用mysql_use_result(仅加载元数据,逐行从服务器读取数据);
    • 使用后需通过mysql_free_result释放结果集内存,避免内存泄漏。
cpp 复制代码
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

cpp 复制代码
 my_ulonglong mysql_num_rows(MYSQL_RES *res);

获取结果列数mysql_num_fields

cpp 复制代码
 unsigned int mysql_num_fields(MYSQL_RES *res);
  • 获取表属性

获取列名mysql_fetch_fields

cpp 复制代码
 MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *res);

MYSQL_FIELD对象结构体:

cpp 复制代码
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

cpp 复制代码
 MYSQL_ROW mysql_fetch_row(MYSQL_RES *result);

它会返回一个MYSQL_ROW变量,MYSQL_ROW其实就是char **.就当成一个二维数组来用吧。

关闭mysql链接mysql_close

cpp 复制代码
 void mysql_close(MYSQL *sock);

mysql C api还支持事务等常用操作,大家下来自行了解:

cpp 复制代码
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);

四、代码示例

cpp 复制代码
#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;
    }

    // 高版本对应适配(可选)禁用SSL
    mysql_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文件:

cpp 复制代码
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 动态库的真实路径:

bash 复制代码
# 假设 mysql-c 的 lib 目录在上级目录的 mysql-c/lib 下
export LD_LIBRARY_PATH=../mysql-c/lib:$LD_LIBRARY_PATH
# 然后执行程序
./test

方法2:将动态库路径永久加入系统库搜索路径

这种方式无需每次手动设置 LD_LIBRARY_PATH,系统会永久识别该路径:

  1. 创建自定义库配置文件:

    bash 复制代码
    sudo nano /etc/ld.so.conf.d/mysql-client.conf
  2. 在文件中添加 MySQL 动态库的绝对路径(例如 ~/mysql-connector/mysql-c/lib):

    复制代码
    /home/wenksen/mysql-connector/mysql-c/lib
  3. 刷新系统库缓存:

    bash 复制代码
    sudo ldconfig

方法3:编译时指定动态库的 rpath(运行时路径)

在编译程序时,直接将动态库的路径嵌入可执行文件,使其运行时自动查找该路径:

修改 Makefile,在编译选项中加入 -Wl,-rpath=../mysql-c/lib(假设 mysql-c/lib 是动态库目录的相对路径):

makefile 复制代码
test: test_mysql_client_info.c
    gcc -o test test_mysql_client_info.c -I../mysql-c/include -L../mysql-c/lib -lmysqlclient -Wl,-rpath=../mysql-c/lib

🚩总结

相关推荐
千码君20165 小时前
Go语言:对其语法的一些见解
开发语言·后端·golang
东城绝神5 小时前
《Linux运维总结:基于ARM64+X86_64架构CPU使用docker-compose一键离线部署mongodb 7.0.22容器版分片集群》
linux·运维·mongodb·架构·分片集群
mjhcsp5 小时前
C++ char 类型深度解析:字符与字节的双重身份
开发语言·c++·char
程序猿John5 小时前
python深度学习之爬虫篇
开发语言·爬虫·python
yuuki2332335 小时前
【C语言】预处理详解
c语言·windows·后端
peiwang2455 小时前
Linux系统中CoreDump的生成与调试
java·linux·开发语言
你想考研啊5 小时前
二、redis集群部署(3主3从)
数据库·redis·缓存
努力也学不会java5 小时前
【Spring】Spring事务和事务传播机制
java·开发语言·人工智能·spring boot·后端·spring
虚行5 小时前
WPF入门
开发语言·c#·wpf