使用 C 语言连接 MySQL 客户端(重点)

目录

前言

一、准备工作

二、查看下载后的文件结构

[三、编写连接 MySQL 的简单 C 程序](#三、编写连接 MySQL 的简单 C 程序)

[MySQL C API 官方文档](#MySQL C API 官方文档)

四、MySQL对象初始化

1、创建MySQL对象

2、函数说明

3、MYSQL对象结构

[五、连接 MySQL 数据库](#五、连接 MySQL 数据库)

1、参数说明

2、返回值说明

六、关闭数据库连接

1、重要说明

2、版本差异导致的SSL兼容性问题(超重点!!!)

3、作用说明

4、实际效果

[5、注意事项(⚠️ 安全警告)](#5、注意事项(⚠️ 安全警告))

第一个错误:caching_sha2_password

第二个错误:mysql_native_password

七、设置字符集

1、函数定义

[参数1: MYSQL *mysql](#参数1: MYSQL *mysql)

[参数2: const char *csname](#参数2: const char *csname)

返回值

2、函数作用

3、基本用法

4、为什么需要设置字符集?

[1. 字符集不匹配导致的乱码问题](#1. 字符集不匹配导致的乱码问题)

[2. 默认字符集问题](#2. 默认字符集问题)

八、下发SQL请求

函数说明:mysql_query

参数说明

返回值说明

使用示例

九、获取查询结果

1、问题分析

[1. 缺少结果集处理](#1. 缺少结果集处理)

[2. 代码执行流程:](#2. 代码执行流程:)

需要后面会学到的添加的结果处理代码

加了处理结果集的当前输出结果:

2、获取查询结果集

3、获取结果集行数

4、获取结果集列数

5、获取列属性信息

6、逐行获取结果数据

内部游标机制(重要!!!)

7、完整查询示例

十、事务控制

1、主要函数

[1. mysql_autocommit() :开启或关闭自动提交模式](#1. mysql_autocommit() :开启或关闭自动提交模式)

[2. mysql_commit() :提交当前事务](#2. mysql_commit() :提交当前事务)

[3. mysql_rollback() :回滚当前事务](#3. mysql_rollback() :回滚当前事务)

2、使用说明

3、简单示例代码

4、注意事项

5、完整代码示例

十一、总结


前言

在开发过程中,我们有时需要使用 C 语言与 MySQL 数据库进行交互。为了实现这一点,我们需要使用 MySQL 提供的 Connector/C 库。本文将详细介绍如何在 C 语言中使用 MySQL 的 API,建立数据库连接,并进行数据操作。


一、准备工作

要在 C 语言中使用 MySQL,你需要准备以下内容:(下面教具体步骤)

  • MySQL 服务正常运行:确保 MySQL 服务已经启动,并且你有一个有效的数据库可以访问。

  • 下载 MySQL Connector/C:你需要从MySQL官网下载适合你平台的 MySQL Connector/C 库,并确保安装路径正确。

Connector/C 库包括两个部分:

  • include 文件夹:其中包含 MySQL API 的头文件。

  • lib 文件夹:包含 MySQL 客户端库的实现文件。

具体步骤如下:

首先,进入MySQL官网(MySQL :: MySQL Community Downloads),接下来选择Download Archives。如下:

因为我们是要使用C语言连接MySQL,所以这里选择MySQL Connector/C。如下:

最后选择适合自己平台的mysql connect库,然后点击下载就行了。如下:

将库文件上传至云服务器:由于下载的是适用于Linux的库文件,因此需要将其传输到云服务器上。我计划将这些文件存放在名为thirdPath的目录中,具体操作如下:

使用rz命令将刚才下载的tar文件从本地电脑上传到云服务器。如下:

然后使用tar命令将压缩包解压到当前目录下。如下:

为方便后续演示,使用mv命令将解压后的目录名缩短,操作如下:


二、查看下载后的文件结构

进入解压后的目录,可以看到包含include和lib两个子目录,结构如下:

在include目录下存放了MySQL API 的头文件(MySQL API 函数的声明),具体如下:

lib目录用于存放相应的动静态库文件(MySQL 客户端库的实现文件),具体结构如下:


三、编写连接 MySQL 的简单 C 程序

为便于项目调用MySQL连接库,建议按以下步骤操作:首先在项目目录下创建MysqlConnector目录,然后在该目录内建立两个软链接:指向include目录的软链接、指向lib目录的软链接。具体操作如下:

此时,在MysqlConnector目录下就能看到之前include和lib目录中的内容。我们可以使用ls命令查看这两个软链接指向的文件内容,如下所示:

我们要先通过调用mysql_get_client_info来判断库(引入的Connector/C 库是C语言类型的库,我们在调用库时可以使用C++语言,这是兼容的,所以下面就使用C++了,更方便点)是否引入成功,该函数的作用就是获取客户端的版本信息。下面是一个简单的示例程序,代码如下:

cpp 复制代码
#include <iostream>
#include <mysql.h>
using namespace std;

int main()
{
    //获取客户端的版本信息
    cout<<"mysql client version: "<<mysql_get_client_info()<<endl;

    return 0;
}

为了方便后续重复编译源文件,可以在项目目录下创建一个Makefile,Makefile当中的内容如下:

bash 复制代码
# 目标文件:依赖文件
mysql_connect: mysql_connect.cc
	g++ -o $@ $^ -std=c++11 -I ./include -L ./lib -l mysqlclient

.PHONY:clean
clean:
	rm -f mysql_connect

编译时,使用 g++ 命令,指定库的包含路径 (-I) 和库文件路径 (-L),并链接 MySQL 客户端库 (-lmysqlclient):

说明一下:

  • -I:用于指明头文件的搜索路径。

  • -L:用于指明库文件的搜索路径。

  • -l:用于指明需要连接库文件路径下的哪一个库。比如:mysqlclient:要链接的库名,链接器会查找 libmysqlclient.solibmysqlclient.a(掐头去尾,中间剩下的就是要链接的库)

Makefile编写完毕后,直接通过make命令即可编译代码生成可执行程序。如下:

但此时生成的可执行程序还不能直接运行,通过ldd命令可以看到,该可执行程序所依赖的mysqlclient库找不到。如下:

原因如下:

GCC/G++编译器默认采用动态链接方式,编译时会自动链接动态库,因此生成的可执行文件运行时需要加载对应的动态库。然而,我们使用的mysqlclient库并不在系统默认的搜索路径中。

需要注意的是,Makefile中的-I、-L和-l选项仅在编译阶段起作用(编译时只告诉在哪找这些文件进行编译,主要是用来编译操作,但是链接时这些文件编译器找不到,因为这些选项仅在编译阶段起作用),用于指明头文件和库文件的位置。一旦生成可执行文件后,这些选项就不再相关。

解决方法通常有三种(详细可回顾Linux系统的动静态库制作那部分,反正方法有很多种):

  • 将库文件复制到系统默认的库文件搜索路径/lib64下

  • 将库文件所在目录添加到LD_LIBRARY_PATH环境变量中,该变量用于指定额外的动态库搜索路径

  • 将库文件目录路径写入.conf配置文件,将该文件放入/etc/ld.so.conf.d/目录,然后执行ldconfig命令更新配置。该目录下的所有配置文件路径都会被纳入动态库搜索范围

这里我们采用第三种方式进行解决(永久方案)。如下:

bash 复制代码
# 1. 创建库路径配置文件
echo /home/hmz/thirdPath/mysql_connector/lib > mysql_connect.conf

# 2. 将配置文件复制到系统配置目录
sudo cp mysql_connect.conf /etc/ld.so.conf.d/

# 3. 验证配置文件是否正确安装
ls /etc/ld.so.conf.d/

# 4. 更新系统库缓存
sudo ldconfig

命令1:创建库路径配置文件

bash 复制代码
echo /home/hmz/thirdPath/mysql_connector/lib > mysql_connect.conf

作用解析:

  • echo:输出文本内容到标准输出

  • /home/hmz/thirdPath/mysql_connector/lib:MySQL连接器库文件的实际路径

  • >:重定向操作符,将输出内容写入文件(覆盖方式)

  • mysql_connect.conf:创建的配置文件名

执行结果:

  • 创建一个名为 mysql_connect.conf 的文件

  • 文件内容只有一行:/home/hmz/thirdPath/mysql_connector/lib

  • 这个路径告诉系统在哪里可以找到 MySQL 客户端库文件

conf文件内容示例:

bash 复制代码
/home/hmz/thirdPath/mysql_connector/lib

命令2:安装配置文件到系统目录

bash 复制代码
sudo cp mysql_connect.conf /etc/ld.so.conf.d/

作用解析:

  • sudo:以管理员权限执行命令(需要输入密码)

  • cp:复制文件命令

  • mysql_connect.conf:源文件(上一步创建的文件)

  • /etc/ld.so.conf.d/:目标目录,系统动态链接器配置目录

目录说明:

  • /etc/ld.so.conf.d/ 是 Linux 系统专门用于存放库路径配置的目录

  • 系统启动时会自动读取该目录下所有 .conf 文件中的路径

  • 这样配置可以让所有用户都能找到这些库文件

命令3:验证配置文件安装

bash 复制代码
ls /etc/ld.so.conf.d/

作用解析:

  • ls:列出目录内容

  • /etc/ld.so.conf.d/:要查看的目录路径

执行目的:

  • 确认 mysql_connect.conf 文件已成功复制到系统配置目录

  • 查看目录中已有的配置文件

  • 验证操作是否成功

典型输出示例:

命令4:更新动态链接器缓存

bash 复制代码
sudo ldconfig

作用解析:

  • sudo:管理员权限

  • ldconfig:Linux 动态链接器配置工具

具体作用:

  1. 扫描配置路径 :读取 /etc/ld.so.conf/etc/ld.so.conf.d/ 中的所有配置

  2. 更新缓存 :重建 /etc/ld.so.cache 文件(库文件索引缓存)

  3. 创建符号链接:为库文件创建必要的版本链接

  4. 内存映射:让系统运行时能快速找到所需的动态库

此时该可执行程序在链接阶段所依赖的mysqlclient库就能够被找到了。如下:

运行可执行程序后,可以看到客户端的版本为6.1.11,也就是刚才下载的库文件的版本。如下:

至此引入库的工作已经做完,接下来就是熟悉接口啦!!! 想在C语言程序中连接MySQL数据库,并查询相关函数和接口的用法。就得会使用以下C语言连接MySQL(MySQL C API)的官方和实用资源:

MySQL C API 官方文档

这是最权威的参考,包含了所有C语言连接MySQL所需的函数和数据结构:MySQL 8.0 C API 参考https://dev.mysql.com/doc/c-api/8.0/en/

这个文档详细介绍了:

  • mysql_init(), mysql_real_connect() - 连接数据库

  • mysql_query(), mysql_store_result() - 执行查询和处理结果

  • mysql_fetch_row(), mysql_fetch_field() - 获取数据和元数据

  • mysql_error(), mysql_errno() - 错误处理

  • mysql_close() - 关闭连接


四、MySQL对象初始化

1、创建MySQL对象

在建立数据库连接前,需要先初始化MySQL对象(也就是我们平时在服务器上使用的MySQL客户端,它就是MySQL对象,集成了各种成员方法和成员变量可以被我们使用),使用以下函数:

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

如: MYSQL *mfp = mysql_init(NULL);

2、函数说明

该函数用于分配或初始化一个MySQL对象(MySQL客户端),用于连接MySQL服务器:

  • 当参数为NULL时,函数会自动分配并返回一个新的MySQL对象(MySQL客户端)

  • 当参数为有效地址时,函数会在该地址处完成对象初始化

3、MYSQL对象结构

MYSQL对象(MySQL客户端)包含多种连接相关信息,MYSQL对象中的methods变量是一个结构体变量,该变量里面保存着很多函数指针,这些函数指针将会在数据库连接成功以后的各种数据操作中被调用。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 数据库

函数 mysql_real_connect():创建完MySQL对象后就可以连接数据库了,连接数据库的函数如下:

cpp 复制代码
MYSQL* mysql_real_connect(MYSQL *mysql, const char *host,
                    const char *user,
                    const char *passwd,
                    const char *db,
                    unsigned int port,
                    const char *unix_socket,
                    unsigned long clientflag);

1、参数说明

  • mysql: 表示在连接数据库前,调用mysql_init函数创建的MySQL对象。

  • host: 数据库服务器地址(通常是 localhost 或 IP 地址)。 表示需要连接的MySQL服务器的IP地址,"127.0.0.1"表示连接本地MySQL服务器。

  • user: 表示连接MySQL服务器时,所使用用户的用户名。

  • passwd: 表示连接MySQL服务器时,所使用用户的密码

  • db: 数据库名称。 表示连接MySQL服务器后,需要使用的数据库。

  • port: 数据库端口(默认为 3306)。 表示连接的MySQL服务器,所对应的端口号。

  • unix_socket: 用于 Unix 套接字连接的路径。 表示连接时应该使用的套接字或命名管道,通常设置为NULL。

  • clientflag: 客户端标志。可以设置为多个标志位的组合,表示允许特定的功能,通常设置为0。

2、返回值说明

  • 如果连接数据库成功,则返回一个MySQL对象,该对象与第一个参数的值相同。

  • 如果连接数据库失败,则返回NULL。


六、关闭数据库连接

在完成数据库操作后,应当及时关闭数据库连接。关闭连接的函数声明如下:

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

1、重要说明

  • 该函数参数应为通过mysql_init创建的MySQL连接对象

  • 若MySQL对象由mysql_init自动创建,调用此函数将同时释放该对象内存

在学习上面的连接数据库和关闭数据库连接后,我们可以进行连接示例操作:初始化 MySQL 客户端后,我们需要连接到 MySQL 数据库。可以使用 mysql_real_connect() 函数建立连接,以下是一个简单的连接示例:比如使用如下代码连接我的MySQL服务器(此时是在我云服务器上用代码连接MySQL客户端,并不是在本地电脑上,这点要清楚!!!):(重点!!!)

cpp 复制代码
#include <iostream>
#include <string>
#include <mysql.h>
using namespace std;

const string host = "113.45.79.2";
const string user = "hmz";
const string passwd = "HMZhmz123!!!";
const string db = "test";
const int port = 3306;

int main()
{
    //1、创建MySQL对象
    MYSQL* ms = mysql_init(nullptr);
    
    //2、连接数据库
    if(mysql_real_connect(ms, host.c_str(), user.c_str(), passwd.c_str(), db.c_str(), port, nullptr, 0) == nullptr)
    {
        cerr << "数据库连接失败: " << mysql_error(ms) << endl;  // 添加具体错误信息
        return 1;
    }
    cout<<"数据库连接成功!"<<endl;

    //3、关闭数据库
    mysql_close(ms);
    cout<<"数据库关闭成功!"<<endl;
    return 0;
}

c_str()方法详解

cpp 复制代码
const char* c_str() const noexcept;
  • 作用:返回指向C风格字符串的指针

  • 返回值const char*类型,指向std::string内部数据的只读指针

  • 特性:不分配新内存,直接返回内部缓冲区指针

2、版本差异导致的SSL兼容性问题(超重点!!!)

如果我们此时使用make命令生成可执行程序,运行后并不是得到连接成功的结果,而是:

SSL连接 是一种安全的网络通信协议,全称是Secure Sockets Layer(安全套接层),现在普遍使用其升级版TLS(Transport Layer Security)。SSL连接 = 加密通信!!!这个SSL连接错误 SSL connection error: unknown error number 是一个比较常见的MySQL SSL连接问题。

首先我们回顾上面,我们知道使用的MySQL客户端是6.1.11的,然而当前我使用的MySQL服务端却是8.0+版本的!!!这个是版本差异导致的SSL兼容性问题!!!

MySQL 8.0+ 服务端特性: 默认启用并强制SSL加密连接、SSL配置更加严格、默认要求安全传输

MySQL 6.1.11 客户端特性: 较老的客户端版本、可能使用的SSL/TLS协议、与MySQL 8.0+的SSL要求不兼容

在 MySQL 8.0+ 的配置文件 /etc/mysql/mysql.conf.d/mysqld.cnf 中添加 ssl=0skip_ssl,然后重新启动MySQL服务端,如下:

bash 复制代码
vim /etc/mysql/mysql.conf.d/mysqld.cnf

#重新启动MySQL服务端
systemctl restart mysql
sql 复制代码
ssl=0
skip_ssl

3、作用说明

ssl=0:显式禁用 SSL/TLS 加密连接、等同于 ssl=OFFssl=DISABLED

skip_ssl:跳过 SSL 功能初始化、完全禁用 SSL 相关功能

4、实际效果

  • 禁用加密连接:客户端与服务器之间的通信将不使用 SSL/TLS 加密

  • 性能提升:避免了 SSL 握手和加密解密的开销

  • 安全性降低:数据传输以明文方式进行,可能被窃听

5、注意事项(⚠️ 安全警告

  • 在生产环境中禁用 SSL 可能存在安全风险

  • 建议仅在测试环境或内部安全网络中使用!!!

  • 如果必须禁用,确保网络环境可信

然后我们现在使用make命令再生成可执行程序,运行后即可看到连接数据库成功、关闭数据库成功!!!如下:

然后我们此时如果再想在本地MySQL客户端登录云服务器上的MySQL的话,这时会登录不了!!!报错结果如下,这个跟SSL是有关系的!!!:

有些提前了解过的大佬,可以会想到如果进行下面的SQL修改操作后再尝试本地登录会不会成功?为什么会认为这个方案会成功呢?原因有下面几点:

  • 绕过 SSL 要求mysql_native_password 插件不要求强制 SSL 连接

  • 兼容禁用 SSL 的环境 :与之前配置的 ssl=0 skip_ssl 兼容

  • 客户端支持:所有 MySQL 客户端都支持这个传统的认证插件

sql 复制代码
ALTER USER 'hmz'@'%' IDENTIFIED WITH mysql_native_password BY '你的密码'; 
FLUSH PRIVILEGES;

但在我的本地电脑得出的结果如下,其主要原因是:这个错误表明本地的 MySQL 服务器端缺少 mysql_native_password 插件MySQL 8.0+ 可能没有编译或加载传统的 mysql_native_password 插件。原理上使用上面的直接修改为 mysql_native_password 插件是可以的!!!

第一个错误:caching_sha2_password

bash 复制代码
ERROR 2061: Authentication plugin 'caching_sha2_password' reported error
  • 原因 :本地客户端首选 尝试使用 caching_sha2_password 插件

  • 服务端禁用了SSL,而该插件要求安全连接

  • 结果:认证失败

第二个错误:mysql_native_password

bash 复制代码
ERROR 2059: Authentication plugin 'mysql_native_password' cannot be loaded
  • 原因 :第一个插件失败后,本地客户端回退 尝试 mysql_native_password

  • **但本地机器的客户端的 mysql_native_password 插件可能:**文件缺失或损坏、版本不兼容、路径配置错误

所以上面的例子是我们学习过程中需要解决的问题,这里我学习用不到(后面使用到再解决,毕竟我也是菜鸡。。。),就自行解决了,详细大家可以上网查找解决方案!!!

mysql_native_password 是MySQL传统的认证插件,兼容性好但不要求SSL加密;而 caching_sha2_password 是MySQL 8.0默认的新插件,安全性更高但默认要求SSL安全连接。第一个问题正是本地客户端尝试使用需要SSL的新插件去连接已禁用SSL的服务端导致的冲突!!!然而第二个问题使用第二个插件时又可能因为本地机器的文件缺失或损坏、版本不兼容、路径配置错误不能导致连接!!!


七、设置字符集

MySQL 默认使用 latin1 字符集,如果你需要处理中文或其他多字节字符,可以使用 mysql_set_character_set() 设置字符集为 utf8:

1、函数定义

sql 复制代码
int mysql_set_character_set(MYSQL *mysql, const char *csname);

参数1: MYSQL *mysql

  • 数据库连接对象指针

  • 必须是有效的、已建立的连接

参数2: const char *csname

  • 字符集名称字符串

  • 常用值:

    • "utf8""utf8mb3" - UTF-8编码(3字节)

    • "utf8mb4" - UTF-8编码(4字节,支持emoji)

    • "gbk" - 中文编码

    • "latin1" - 西欧编码

    • "binary" - 二进制数据

返回值

  • 成功 : 返回 0

  • 失败 : 返回非 0

2、函数作用

mysql_set_character_set() 函数用于设置当前数据库连接的字符集。这个函数确保客户端、连接和结果集使用指定的字符集,避免乱码问题。

3、基本用法

cpp 复制代码
#include <iostream>
#include <mysql.h>

using namespace std;

int main()
{
    MYSQL *conn = mysql_init(nullptr);

    // 连接数据库
    if (mysql_real_connect(conn, "113.45.79.2", "hmz", "HMZhmz123!!!", "test", 3306, nullptr, 0))
    {

        // 设置字符集为UTF-8
        if (mysql_set_character_set(conn, "utf8") == 0)
        {
            cout << "字符集设置成功: utf8" << endl;
        }
        else
        {
            cerr << "字符集设置失败: " << mysql_error(conn) << endl;
        }

        mysql_close(conn);
    }
    return 0;
}

**重要结论:字符集设置确实生效了,但只对当前连接有效!字符集设置是连接级别的,每个新连接都会恢复为服务器默认设置!!!**如下的查询可以证明和说明这一点:

sql 复制代码
SELECT 
    @@character_set_client as client,
    @@character_set_connection as connection,
    @@character_set_results as results;

4、为什么需要设置字符集?

1. 字符集不匹配导致的乱码问题

示例场景:

cpp 复制代码
// 如果不设置字符集,可能出现以下情况:
// 插入中文数据
const char* sql = "INSERT INTO users (name) VALUES ('张三')";
mysql_query(conn, sql);

// 查询时显示乱码:�Ź� 或者 ???

2. 默认字符集问题

  • MySQL 8.0 默认字符集是 utf8mb4

  • 旧版本MySQL默认可能是 latin1

  • 客户端默认字符集可能不匹配


八、下发SQL请求

函数说明:mysql_query

在与数据库建立连接后,即可通过 mysql_query 函数向 MySQL 服务器发送 SQL 请求。该函数的定义如下:

cpp 复制代码
int mysql_query(MYSQL *mysql, const char *q);

参数说明

  • mysql:指向一个已初始化的 MYSQL 结构体指针,通常通过 mysql_init 函数创建,并已成功建立数据库连接。

  • q:以 C 风格字符串形式表示的 SQL 语句。注意,SQL 语句末尾可以不带分号

返回值说明

  • 返回值为 0 表示 SQL 语句执行成功。

  • 返回值非零表示执行失败,可通过相关错误处理函数进一步获取错误信息。

使用示例

**MySQL 数据库测试说明:**本次测试使用的数据库名为 connect_demon,其中包含一个 user 表,表内有 3 条测试记录。具体如下:

**向数据库插入数据:**通过调用mysql_query函数,向MySQL服务器发送INSERT SQL语句。示例如下:

cpp 复制代码
#include <iostream>
#include <string>
#include <mysql.h>
using namespace std;

const string host = "113.45.79.2";
const string user = "hmz";
const string passwd = "HMZhmz123!!!";
const string db = "connect_demon";
const int port = 3306;

int main()
{
    //1、创建MySQL对象
    MYSQL* ms = mysql_init(nullptr);
    //2、连接数据库
    if(mysql_real_connect(ms, host.c_str(), user.c_str(), passwd.c_str(), db.c_str(), port, nullptr, 0) == nullptr)
    {
        cerr<<"数据库连接失败!"<<endl;
        return 1;
    }
    cout<<"数据库连接成功!"<<endl;
    mysql_set_character_set(ms, "utf8"); //设置编码格式为utf8
    
    //3、向数据库表中插入记录
    std:string sql = "insert into user values (4,'赵六',25)";
    if(mysql_query(ms, sql.c_str()) != 0)
    {
        cout<<"插入数据失败!"<<endl;
        return 2;
    }
    cout<<"插入数据成功!"<<endl;

    //4、关闭数据库
    mysql_close(ms);
    cout<<"数据库关闭成功!"<<endl;
    return 0;
}

通过make命令编译生成可执行程序后运行,程序执行结果会直接反映在MySQL数据库中,可以看到数据已成功插入。具体操作如下:

**从数据库中删除数据:**通过调用mysql_query函数向MySQL服务器发送delete语句。示例如下:

cpp 复制代码
#include <iostream>
#include <string>
#include <mysql.h>
using namespace std;

const string host = "113.45.79.2";
const string user = "hmz";
const string passwd = "HMZhmz123!!!";
const string db = "connect_demon";
const int port = 3306;

int main()
{
    // 1、创建MySQL对象
    MYSQL *ms = mysql_init(nullptr);
    // 2、连接数据库
    if (mysql_real_connect(ms, host.c_str(), user.c_str(), passwd.c_str(), db.c_str(), port, nullptr, 0) == nullptr)
    {
        cerr << "数据库连接失败!" << endl;
        return 1;
    }
    cout << "数据库连接成功!" << endl;
    mysql_set_character_set(ms, "utf8"); // 设置编码格式为utf8

// 3、删除数据库表中的记录
std:
    string sql = "delete from user where id=4";
    if (mysql_query(ms, sql.c_str()) != 0)
    {
        cout << "删除数据失败!" << endl;
        return 2;
    }
    cout << "删除数据成功!" << endl;

    // 4、关闭数据库
    mysql_close(ms);
    cout << "数据库关闭成功!" << endl;
    return 0;
}

通过make命令编译生成可执行程序,运行后在MySQL数据库中即可观察到目标数据被成功删除。操作示例如下:

**修改数据库数据:**通过调用mysql_query函数向MySQL服务器发送update SQL语句。示例如下:

cpp 复制代码
#include <iostream>
#include <string>
#include <mysql.h>
using namespace std;

const string host = "113.45.79.2";
const string user = "hmz";
const string passwd = "HMZhmz123!!!";
const string db = "connect_demon";
const int port = 3306;

int main()
{
    //1、创建MySQL对象
    MYSQL* ms = mysql_init(nullptr);
    //2、连接数据库
    if(mysql_real_connect(ms, host.c_str(), user.c_str(), passwd.c_str(), db.c_str(), port, nullptr, 0) == nullptr)
    {
        cerr<<"数据库连接失败!"<<endl;
        return 1;
    }
    cout<<"数据库连接成功!"<<endl;
    mysql_set_character_set(ms, "utf8"); //设置编码格式为utf8
    
    //3、修改数据库表中的记录
    std:string sql = "update user set age=22 where id=1";
    if(mysql_query(ms, sql.c_str()) != 0)
    {
        cout<<"修改数据失败!"<<endl;
        return 2;
    }
    cout<<"修改数据成功!"<<endl;

    //4、关闭数据库
    mysql_close(ms);
    cout<<"数据库关闭成功!"<<endl;
    return 0;
}

执行make命令编译生成程序后,运行该程序即可在MySQL数据库中观察到相应的数据变更。具体操作如下:


九、获取查询结果

思考:我们想想下面代码的输出结果是什么?下面执行的是select的查询操作:

cpp 复制代码
#include <iostream>
#include <string>
#include <mysql.h>
using namespace std;

const string host = "113.45.79.2";
const string user = "hmz";
const string passwd = "HMZhmz123!!!";
const string db = "connect_demon";
const int port = 3306;

int main()
{
    //1、创建MySQL对象
    MYSQL* ms = mysql_init(nullptr);
    //2、连接数据库
    if(mysql_real_connect(ms, host.c_str(), user.c_str(), passwd.c_str(), db.c_str(), port, nullptr, 0) == nullptr)
    {
        cerr<<"数据库连接失败!"<<endl;
        return 1;
    }
    cout<<"数据库连接成功!"<<endl;
    mysql_set_character_set(ms, "utf8"); //设置编码格式为utf8
    
    //3、查找数据库表中的记录
    std:string sql = "select * from user";
    if(mysql_query(ms, sql.c_str()) != 0)
    {
        cout<<"查找数据失败!"<<endl;
        return 2;
    }
    cout<<"查找数据成功!"<<endl;

    //4、关闭数据库
    mysql_close(ms);
    cout<<"数据库关闭成功!"<<endl;
    return 0;
}

这段代码没有显示查询结果的原因是:它只执行了查询,但没有获取和处理查询结果

1、问题分析

1. 缺少结果集处理

代码在 mysql_query() 执行成功后就直接关闭连接了,没有:获取结果集、遍历结果行、提取字段数据、显示结果

2. 代码执行流程:

缺少了关键的 结果处理 步骤。

需要后面会学到的添加的结果处理代码

cout<<"查找数据成功!"<<endl; 之后,mysql_close(ms); 之前应该添加:

cpp 复制代码
// 获取结果集
MYSQL_RES* result = mysql_store_result(ms);
if (result == nullptr) {
    cout << "获取结果集失败!" << endl;
    return 3;
}

// 获取字段数量
int num_fields = mysql_num_fields(result);

// 遍历每一行数据
MYSQL_ROW row;
while ((row = mysql_fetch_row(result))) {
    // 遍历当前行的每个字段
    for (int i = 0; i < num_fields; i++) {
        cout << (row[i] ? row[i] : "NULL") << "\t";
    }
    cout << endl;
}

// 释放结果集
mysql_free_result(result);

加了处理结果集的当前输出结果:

查询确实执行成功了,但因为没有处理结果集,所以你看不到具体的数据内容。经过上面的问题引入,我们应该明白为什么要学习下面的内容了,主要是用来获取查询结果。 对数据库执行增、删、改 操作时,仅需调用 mysql_query 下发对应 SQL 请求即可;而执行查询操作时,除下发查询语句外,还需额外获取查询结果集。

2、获取查询结果集

cpp 复制代码
MYSQL_RES *mysql_store_result(MYSQL *mysql);

说明:

  • 该函数通过 MySQL 对象内部函数指针获取查询结果,并将完整结果集保存至 MYSQL_RES 结构体中返回。

  • 注意MYSQL_RES 对象使用动态分配的内存,使用完毕后必须手动释放,否则将导致内存泄漏。

MYSQL_RES 结构定义(摘要)

cpp 复制代码
typedef struct st_mysql_res {
    my_ulonglong row_count;     // 结果集行数
    MYSQL_FIELD  *fields;       // 列属性数组
    MYSQL_ROW    current_row;   // 当前行数据
    unsigned int field_count;   // 列数
    // ... 其他内部字段
} MYSQL_RES;

3、获取结果集行数

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

说明: 返回指定 MYSQL_RES 对象中结果集的总行数。

4、获取结果集列数

cpp 复制代码
unsigned int mysql_num_fields(MYSQL_RES *res);

说明: 返回指定 MYSQL_RES 对象中结果集的列数。

5、获取列属性信息

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

说明:

  • 返回一个 MYSQL_FIELD 结构体数组,包含结果集中每一列的详细属性信息(如列名、类型、长度等)。

  • 数组长度为结果集的列数,可通过 mysql_num_fields 获取。

MYSQL_FIELD 结构定义(摘要):

cpp 复制代码
typedef struct st_mysql_field {
    char *name;                 // 列名
    char *table;                // 所属表名
    unsigned long length;       // 列定义长度
    unsigned long max_length;   // 结果集中最大长度
    enum enum_field_types type; // 列数据类型
    unsigned int flags;         // 列标志(如是否为主键)
    // ... 其他属性字段
} MYSQL_FIELD;

6、逐行获取结果数据

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

说明:

  • 从结果集中提取下一行数据,返回 MYSQL_ROW 对象。

  • MYSQL_ROW 实质为 char ** 类型,即字符串数组,每个元素对应一行中的某一列值。

    cpp 复制代码
    typedef char **MYSQL_ROW;		/* return data as array of strings */
  • 返回值说明:

    • 成功:返回当前行数据的指针

    • 失败或无更多数据:返回 NULL

内部游标机制(重要!!!)

  • MYSQL_RES* res 结果集对象内部维护着一个游标指针

  • 初始时,这个游标指向结果集的第一行之前

  • 每次调用 mysql_fetch_row(res) 时,游标会自动移动到下一行

7、完整查询示例

比如查询user表中的数据并进行打印输出。如下:

cpp 复制代码
#include <iostream>
#include <string>
#include <mysql.h>
using namespace std;

const string host = "113.45.79.2";
const string user = "hmz";
const string passwd = "HMZhmz123!!!";
const string db = "connect_demon";
const int port = 3306;

int main()
{
    //1、获取MySQL实例(相当于给我们创建了一个MySQL句柄)
    MYSQL* ms = mysql_init(nullptr);
    //2、连接数据库
    if(mysql_real_connect(ms, host.c_str(), user.c_str(), passwd.c_str(), db.c_str(), port, nullptr, 0) == nullptr)
    {
        cerr<<"数据库连接失败!"<<endl;
        return 1;
    }
    cout<<"数据库连接成功!"<<endl;
    mysql_set_character_set(ms, "utf8"); //设置编码格式为utf8
    
    //3、查询数据库表中的记录
    //a、执行查询语句
    std:string sql = "select * from user";
    if(mysql_query(ms, sql.c_str()) != 0)
    {
        cout<<"查询数据失败!"<<endl;
        return 2;
    }
    cout<<"查询数据成功!"<<endl;
    //b、获取查询结果
    MYSQL_RES* res = mysql_store_result(ms);
    int rows = mysql_num_rows(res); //数据的行数
    int cols = mysql_num_fields(res); //数据的列数
    //获取每列的属性并打印列名
    MYSQL_FIELD* fields = mysql_fetch_fields(res);
    for(int i = 0;i < cols;i++)
    {
        cout<<fields[i].name<<"\t";
    }
    cout<<endl;
    for(int i = 0;i < rows;i++)
    {
        //获取一行数据并进行打印
        MYSQL_ROW row = mysql_fetch_row(res);
        for(int j = 0;j < cols;j++)
        {
            cout<<row[j]<<"\t";
        }
        cout<<endl;
    }
    free(res); //释放内存空间
    
    //4、关闭数据库
    mysql_close(ms);
    cout<<"数据库关闭成功!"<<endl;
    return 0;
}

在两层for循环那里,我们可以将其看作为一个二维数组,二维数组每个元素对应着一个字符串,如下直观图:(忘记了的话就回看C语言指针!!!)

执行make命令生成可执行程序,运行后即可查看数据查询结果。示例如下:


十、事务控制

MySQL C API 提供了完整的事务操作支持,以下是常用的事务相关函数:

1、主要函数

1. mysql_autocommit() :开启或关闭自动提交模式

功能:控制MySQL的自动提交模式

参数:

cpp 复制代码
int mysql_autocommit(MYSQL *mysql, int mode)
  • mysql: MySQL连接对象

  • mode:

    • 1非0: 开启自动提交

    • 0: 关闭自动提交

工作模式:

自动提交模式开启时 (mode=1)

cpp 复制代码
mysql_autocommit(conn, 1);  // 开启自动提交
  • 每个SQL语句都会立即提交到数据库

  • 相当于每条语句都是一个独立的事务

  • MySQL默认模式

自动提交模式关闭时 (mode=0)

cpp 复制代码
mysql_autocommit(conn, 0);  // 关闭自动提交
  • SQL语句不会立即提交

  • 需要手动调用 mysql_commit() 提交或 mysql_rollback() 回滚

  • 开启事务操作的必备步骤

使用场景:

cpp 复制代码
// 开始事务操作
mysql_autocommit(conn, 0);  // 必须先关闭自动提交

// 执行多个相关操作
mysql_query(conn, "UPDATE ...");
mysql_query(conn, "INSERT ...");

// 手动提交
mysql_commit(conn);

// 恢复自动提交(可选)
mysql_autocommit(conn, 1);

简单理解:就像游戏的"保存模式" - 自动提交是"自动保存",关闭后是"手动保存"。

2. mysql_commit() :提交当前事务

**功能:**提交当前事务,将所有未提交的更改永久保存到数据库

参数:

cpp 复制代码
int mysql_commit(MYSQL *mysql)
  • mysql: MySQL连接对象

  • 返回值:成功返回0,失败返回非0

使用前提:

必须先关闭自动提交才有效

cpp 复制代码
mysql_autocommit(conn, 0);  // 必须先关闭自动提交

工作流程:

没有mysql_commit()

cpp 复制代码
mysql_autocommit(conn, 0);
mysql_query(conn, "UPDATE user SET age=25 WHERE id=1");
// 更改不会保存到数据库!

有mysql_commit()

cpp 复制代码
mysql_autocommit(conn, 0);
mysql_query(conn, "UPDATE user SET age=25 WHERE id=1");
mysql_commit(conn);  // 现在更改才真正保存

完整事务流程

cpp 复制代码
mysql_autocommit(conn, 0);      // 1. 开始事务

mysql_query(conn, "UPDATE ..."); // 2. 执行操作
mysql_query(conn, "INSERT ...");

if(一切正常) {
    mysql_commit(conn);          // 3. 提交确认
} else {
    mysql_rollback(conn);        // 3. 回滚取消
}

简单理解 :就像网购的确认订单

  • mysql_autocommit(0) = 把商品加入购物车

  • 各种操作 = 修改商品数量、地址等

  • mysql_commit() = 点击"确认支付",交易完成

  • mysql_rollback() = 取消订单

关键点 :不调用 mysql_commit(),所有操作都不会真正生效!

3. mysql_rollback() :回滚当前事务

**功能:**回滚当前事务,取消所有未提交的更改

参数:

cpp 复制代码
int mysql_rollback(MYSQL *mysql)
  • mysql: MySQL连接对象

  • 返回值:成功返回0,失败返回非0

使用前提:

必须先关闭自动提交才有效

cpp 复制代码
mysql_autocommit(conn, 0);  // 必须先关闭自动提交

工作流程:

正常提交

cpp 复制代码
mysql_autocommit(conn, 0);
mysql_query(conn, "UPDATE user SET age=25 WHERE id=1");
mysql_commit(conn);  // 确认更改

异常回滚

cpp 复制代码
mysql_autocommit(conn, 0);
mysql_query(conn, "UPDATE user SET age=25 WHERE id=1");
mysql_rollback(conn);  // 取消所有更改!

典型使用场景

cpp 复制代码
mysql_autocommit(conn, 0);  // 开始事务

// 执行多个操作
if (mysql_query(conn, "UPDATE accounts SET balance=balance-100 WHERE user='A'")) {
    mysql_rollback(conn);  // 失败就回滚
    return;
}

if (mysql_query(conn, "UPDATE accounts SET balance=balance+100 WHERE user='B'")) {
    mysql_rollback(conn);  // 失败就回滚
    return;
}

mysql_commit(conn);  // 都成功才提交

简单理解: 就像游戏的撤销操作

  • mysql_autocommit(0) = 开始录制操作

  • 各种SQL操作 = 在游戏中移动、建造、购买

  • mysql_commit() = 保存游戏

  • mysql_rollback() = 读取存档,回到开始状态

重要特性

  • 原子性保证:要么全部成功,要么全部取消

  • 数据一致性:确保相关操作要么都执行,要么都不执行

  • 错误恢复:遇到任何错误时保护数据完整性

关键点:回滚后,所有未提交的操作就像从未发生过!

2、使用说明

在MySQL中,默认启用自动提交模式,每个SQL语句都会立即生效。要使用事务控制,需要先关闭自动提交,然后手动管理事务的提交与回滚。

3、简单示例代码

cpp 复制代码
// 关闭自动提交模式,开始事务控制
mysql_autocommit(conn, 0);

// 执行一系列SQL操作
// 例如:
// mysql_query(conn, "INSERT INTO table1 ...");
// mysql_query(conn, "UPDATE table2 ...");
// mysql_query(conn, "DELETE FROM table3 ...");

// 如果所有操作都成功,提交事务
mysql_commit(conn);

// 如果在操作过程中发生错误,可以回滚事务
// mysql_rollback(conn);

// 恢复自动提交模式(如果需要)
// mysql_autocommit(conn, 1);

4、注意事项

  • 关闭自动提交后,必须显式调用 mysql_commit()mysql_rollback() 来结束事务

  • 事务操作失败时,应及时回滚以避免数据不一致

  • 建议在事务操作期间进行错误检查,确保每一步操作都成功执行

  • 长时间的事务可能会占用数据库资源,应尽快完成并提交或回滚

5、完整代码示例

cpp 复制代码
#include <iostream>
#include <string>
#include <mysql.h>
using namespace std;

const string host = "113.45.79.2";
const string user = "hmz";
const string passwd = "HMZhmz123!!!";
const string db = "connect_demon";
const int port = 3306;

int main()
{
    //1、创建MySQL对象
    MYSQL* ms = mysql_init(nullptr);
    //2、连接数据库
    if(mysql_real_connect(ms, host.c_str(), user.c_str(), passwd.c_str(), db.c_str(), port, nullptr, 0) == nullptr)
    {
        cerr<<"数据库连接失败!"<<endl;
        return 1;
    }
    cout<<"数据库连接成功!"<<endl;
    mysql_set_character_set(ms, "utf8"); //设置编码格式为utf8

    // 开始事务示例
    // 关闭自动提交,开始事务
    if (mysql_autocommit(ms, 0)) {
        cerr << "关闭自动提交失败!" << endl;
        mysql_close(ms);
        return 2;
    }
    cout << "开始事务..." << endl;

    // 执行多个更新操作
    string sql1 = "UPDATE user SET age = age + 1 WHERE id = 1"; // 张三年龄+1
    string sql2 = "UPDATE user SET age = age - 1 WHERE id = 2"; // 李四年龄-1
    
    // 执行第一个更新
    if(mysql_query(ms, sql1.c_str()) != 0)
    {
        cerr << "第一个更新失败: " << mysql_error(ms) << endl;
        mysql_rollback(ms); // 回滚事务
        mysql_close(ms);
        return 3;
    }
    cout << "第一个更新成功!" << endl;

    // 执行第二个更新
    if(mysql_query(ms, sql2.c_str()) != 0)
    {
        cerr << "第二个更新失败: " << mysql_error(ms) << endl;
        mysql_rollback(ms); // 回滚事务
        mysql_close(ms);
        return 4;
    }
    cout << "第二个更新成功!" << endl;

    // 提交事务
    if(mysql_commit(ms) != 0)
    {
        cerr << "事务提交失败: " << mysql_error(ms) << endl;
        mysql_rollback(ms); // 回滚事务
        mysql_close(ms);
        return 5;
    }
    cout << "事务提交成功!" << endl;

    // 恢复自动提交模式
    mysql_autocommit(ms, 1);

    // 验证更新结果
    string sql = "select * from user";
    if(mysql_query(ms, sql.c_str()) != 0)
    {
        cout<<"查找数据失败!"<<endl;
        return 6;
    }

    // 获取结果集
    MYSQL_RES* result = mysql_store_result(ms);
    if(result == nullptr)
    {
        cout<<"获取结果集失败!"<<endl;
        return 7;
    }

    // 输出结果
    cout << "\n更新后的数据:" << endl;
    MYSQL_ROW row;
    while((row = mysql_fetch_row(result)))
    {
        cout << "ID: " << row[0] << ", 姓名: " << row[1] << ", 年龄: " << row[2] << endl;
    }

    // 释放结果集
    mysql_free_result(result);

    //4、关闭数据库
    mysql_close(ms);
    cout<<"数据库关闭成功!"<<endl;
    return 0;
}

回顾知识点:这些返回值 表示程序退出的不同状态码 ,用于标识不同的错误类型!!! 这些返回值是用户自定义的 ,不是系统定义的。return值主要是给其他程序用的,不是给操作系统用的!!!(重要!!!)

返回值 含义 错误位置
return 0 程序完全成功 正常结束
return 1 数据库连接失败 连接数据库时
return 2 关闭自动提交失败 开始事务时
return 3 第一个更新失败 更新张三年龄时
return 4 第二个更新失败 更新李四年龄时
return 5 事务提交失败 提交事务时
return 6 查询数据失败 验证结果时
return 7 获取结果集失败 处理查询结果时

十一、总结

本文介绍了如何在 C 语言中使用 MySQL Connector/C 库,建立与 MySQL 数据库的连接,并执行简单的 SQL 查询。通过 mysql_init() 初始化库,mysql_real_connect() 连接数据库,mysql_query() 执行查询,mysql_fetch_row() 获取结果,最后使用 mysql_close() 关闭连接。你还可以使用事务控制来保证数据的一致性和完整性。

相关推荐
清水加冰3 小时前
【MySQL】SQL调优-如何分析SQL性能
数据库·sql·mysql
知其然亦知其所以然3 小时前
MySQL性能暴涨100倍?其实只差一个“垂直分区”!
后端·mysql·面试
风跟我说过她3 小时前
CentOS 7 环境下 MySQL 5.7 深度指南:从安装、配置到基础 SQL 操作
sql·mysql·centos
白水先森3 小时前
Python 字符串与布尔值详解
java·服务器·前端
is08153 小时前
全志 H3 armbian 备份
linux·服务器·网络
倔强的石头1063 小时前
【金仓数据库】ksql 指南(二) —— 创建与管理本地数据库
数据库·kingbasees·金仓数据库
编程充电站pro3 小时前
SQL 面试题解析:如何用多表查询写用户订单统计?
数据库·sql
CS_Zero3 小时前
【开发工具】Windows10&11远程Ubuntu18及以上桌面
笔记·ubuntu
数据知道5 小时前
Go基础:正则表达式 regexp 库详解
开发语言·mysql·golang·正则表达式·go语言