C语言操作MySQL

一、C语言访问MySQL

除了我们前面讲的通过命令行的方式访问数据库,我们还可以通过使用MySQL原生的C语言的API来连接和访问数据库。要使用C语言连接 mysql,我们需要使用mysql官网提供的库。

我们使用C接口库来进行连接。要正常地使用C接口来访问数据库,我们需要做一些准备工作。首先需要保证MySQL服务有效,然后要在官网上下载适合自己平台的mysql connect库。

在MySQL官网中找到C API,点击进去就可以进行安装。

我们可以通过mysql_get_client_info()函数,来验证我们的引入是否成功:

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

int main()
{
    printf("mysql client version: %s\n", mysql_get_client_info());
    return 0;
}

在输入编译命令时我们需要带上`mysql_config --cflags --libs`,编译指令如下:

bash 复制代码
gcc test.c -o test `mysql_config --cflags --libs`

其中:

mysql_config是MySQL 开发包自带脚本,用来自动生成编译所需参数,避免硬编码路径,方便移植。

--cflags输出-Ixxx,用于指定 mysql.h 头文件目录,解决找不到头文件的编译报错。

--libs输出-Lxxx -lmysqlclient,用于指定库文件位置并链接 libmysqlclient.so,解决函数未定义的链接报错。

反引号 `可以让shell先执行里面命令,把输出替换进gcc命令。

引入成功的打印结果如下:

二、MySQL基础数据结构

  1. MYSQL

这个结构体代表一条数据库连接的句柄(handler),几乎所有 MySQL C API 函数都要用到它。 需要注意的是,不要自己拷贝复制MYSQL结构体,这样拷贝出来的副本不保证能正常使用。

对应接口:mysql_init、mysql_real_connect、mysql_close。这三个接口都操作这个结构体。

  1. MYSQL_RES

代表会返回行数据的查询结果,只有SELECT / SHOW / DESCRIBE / EXPLAIN这类查询才会产生它,这一块数据整体叫做结果集 (result set)。

当我们使用mysql_store_result接口就会返回它,使用完后必须使用mysql_free_result释放。 insert/update/delete操作不会产生 MYSQL_RES。

  1. MYSQL_ROW

代表一行数据,底层实现是一个字节串数组。注意,不能直接当成 C 语言以\0结尾的字符串。如果字段存二进制数据(比如图片、blob),内容内部本身就会包含\0,直接用strcpy/printf会截断。

使用mysql_fetch_row()接口时就会返回MYSQL_ROW。

  1. MYSQL_FIELD

存放字段元数据(metadata):字段名、字段类型、长度、表名等描述信息。 需要注意的是,MYSQL_FIELD就是描述一列字段的元数据,而不是表里面的行数据,真实数据都在 MYSQL_ROW中。

使用接口mysql_fetch_fields/mysql_fetch_field会使用到这个字段

  1. MYSQL_FIELD_OFFSET

用来表示字段列表里的偏移下标,配合接口mysql_field_seek()来使用。下标从 0 开始,代表一行里第几个列。 用于在结果集里移动字段读取的位置。

  1. my_ulonglong

64位无符号整数类型。

使用接口mysql_affected_rows时会返回这个类型。

  1. my_bool

布尔类型:非0代表 true,0代表 false。

对应接口:mysql_autocommit、mysql_commit、mysql_rollback的返回值类型。

三、MySQL接口介绍

在官网中可以查到C API的使用文档,我们可以在里面查询MySQL常用函数的使用方法。

要使用MySQ库,就必须先进行初始化,初始化的接口为:

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

使用示例:MYSQL *mfp = mysql_init(NULL);

初始化完毕之后,我们需要先连接数据库,再进行后续操作(mysql 网络部分是基于 TCP/IP 的):

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);

第一个参数MYSQL是 C api 中一个非常重要的变量(mysql_init 的返回值),里面有port,dbname,charset等连接基本参数。它也包含了一个叫st_mysql_methods的结构体变量,该变量里面保存着很多函数指针,这些函数指针将会在数据库连接成功以后的各种数据操作中被调用。

建立完连接之后,获取英文没有问题,如果获取中文是乱码,就需要使用接口mysql_set_character_set设置链接的默认字符集是utf8(原始默认是latin1)

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

下发命令使用接口mysql_query:

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

第一个参数是上面介绍过的MySQL,第二个参数为要执行的SQL语句。

当sql语句执行完以后,如果是查询语句,我们就需要读取数据;如果是update,insert等语句,那么我们只需要看下操作成功与否即可。获取执行结果我们使用接口mysql_store_result:

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

在获取完结果集后,我们可以通过mysql_num_rows接口来获取结果集中有多少行数据:

cpp 复制代码
mysql_num_rows(MYSQL_RES *res) 

需要注意的是,只有当接口mysql_store_result一次性把结果读到本地内存之后,这个函数才是准确的;如果是 mysql_use_result 流式读取,在读完所有行之前,它返回的数字不准。

除了获取行数之外,同时还有供我们获取列数的接口mysql_num_fields:

cpp 复制代码
mysql_num_fields(MYSQL_RES *res) 

MySQL会将所有读取出来的数据都当作字符串处理,所以res在保存数据时会先将这些数据规制化。在res内部相当于有多个二级指针char**,指向具体的某一行,第二层指针指向行中的某一列的数据。MySQL为了更好的支持遍历,提供了下面这个函数:

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

它会返回一个MYSQL_ROW变量,MYSQL其实就是char **,可以当作一个二维数组来时用,使用示例如下:

cpp 复制代码
i = 0;
MYSQL_ROW line;
for(; i < nums; i++){
    line = mysql_fetch_row(res);
    int j = 0;
    for(; j < fields; j++){
        cout << line[j] << " ";
    }
    cout << endl;
}

我们可以使用mysql_fetch_fields接口来获取结果集中的字段名、字段类型、长度、表名等属性:

cpp 复制代码
mysql_fetch_fields(MYSQL_RES *res)

由于mysql_store_result接口会在堆上开辟内存,存放查询返回的结果集数据。所以当我们程序退出时就需要把开辟的这部分内存给释放掉。而mysql_free_result接口就是用来释放MYSQL_RES* 结果集占用的内存的:

cpp 复制代码
void mysql_free_result(MYSQL_RES *result);

使用完毕之后,需要将mysql的连接关闭,需要使用到接口:

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

完整使用示例:

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

// MySQL 连接信息
const std::string host = "localhost";
const std::string user = "connector";
const std::string passwd = "123456";
const std::string db   = "conn";
// 重点:MySQL服务默认端口 3306,不是8080!8080是http端口
const unsigned int port = 3306;

int main()
{
    // 1. 初始化MYSQL句柄
    MYSQL *my = mysql_init(nullptr);
    if (my == nullptr)
    {
        std::cerr << "mysql_init 失败: " << mysql_error(my) << std::endl;
        return 1;
    }

    // 2. 建立与MySQL服务端连接
    if (mysql_real_connect(my, host.c_str(), user.c_str(), passwd.c_str(), db.c_str(), port, nullptr, 0) == nullptr)
    {
        std::cerr << "mysql_real_connect 连接失败: " << mysql_error(my) << std::endl;
        mysql_close(my);
        return 2;
    }
    std::cout << "✅ MySQL 连接成功" << std::endl;

    // 3. 设置连接字符集,解决中文乱码(utf8mb4 支持emoji、生僻字,推荐)
    if (mysql_set_character_set(my, "utf8mb4") != 0)
    {
        std::cerr << "设置字符集失败: " << mysql_error(my) << std::endl;
        mysql_close(my);
        return 3;
    }
    std::cout << "✅ 连接字符集设置为 utf8mb4" << std::endl;


    //DML语句(update / insert / delete,无结果集)
    std::string sql_update = "update user set name='Jimmy' where id=2";
    int ret = mysql_query(my, sql_update.c_str());
    if (ret == 0)
    {
        std::cout << "SQL执行成功:" << sql_update << std::endl;
        // 获取本次SQL影响的行数
        my_ulonglong affect_rows = mysql_affected_rows(my);
        std::cout << "本次影响行数:" << affect_rows << std::endl;
    }
    else
    {
        std::cerr << "SQL执行失败:" << sql_update << std::endl;
        std::cerr << "错误信息:" << mysql_error(my) << std::endl;
    }


    //DQL查询语句(select,有结果集,配套前面学的fetch_row)
    std::string sql_select = "select id, name from user";
    ret = mysql_query(my, sql_select.c_str());
    if (ret == 0)
    {
        std::cout << "\n===== 查询结果 =====" << std::endl;
        // 获取结果集
        MYSQL_RES* res = mysql_store_result(my);
        if (res == nullptr)
        {
            std::cerr << "mysql_store_result 获取结果集失败:" << mysql_error(my) << std::endl;
        }
        else
        {
            // 获取列数量
            unsigned int cols = mysql_num_fields(res);
            MYSQL_ROW row;
            // 循环读取每一行(标准安全写法,推荐)
            while ((row = mysql_fetch_row(res)) != nullptr)
            {
                for (unsigned int j = 0; j < cols; j++)
                {
                    // row[j] 为NULL代表数据库该字段是NULL
                    if (row[j] == nullptr)
                        std::cout << "(NULL) ";
                    else
                        std::cout << row[j] << " ";
                }
                std::cout << std::endl;
            }
            //释放结果集!!
            mysql_free_result(res);
        }
    }
    else
    {
        std::cerr << "查询SQL失败:" << sql_select << std::endl;
        std::cerr << "错误信息:" << mysql_error(my) << std::endl;
    }


    // 4. 关闭连接,释放句柄
    mysql_close(my);
    std::cout << "\n连接已关闭" << std::endl;
    return 0;
}
相关推荐
ShineWinsu31 分钟前
对于Qt:QTabWidget常见属性的解析
开发语言·数据库·c++·qt·浏览器·qtabwidget·标签页
星恒随风11 小时前
MySQL 从零入门(五):MySQL 内置函数详解——日期、字符串、数学函数与实战案例
linux·数据库·笔记·sql·mysql
吴名氏.13 小时前
Java转AI Agent教程(第十三周)混合检索与重排
java·数据库·人工智能·ai·ai agent
爱码少年13 小时前
Springboot下如何将CSV文件数据导入H2、Mysql数据库表
数据库·mysql·csv
2603_9696948514 小时前
【JavaWeb学习记录】MyBatis核心知识点与实战笔记:从入门到动态SQL
笔记·sql·学习·mysql·mybatis·javaweb·学习方法
可乐ea14 小时前
智能体记忆该拆成几层:笔记、记忆表示与注入上下文
服务器·数据库·笔记·ai智能体·提示词工程·上下文工程·记忆系统
阿义有问题15 小时前
MySQL 迁移达梦全流程实战:DTS 七步图解 + 对象名全大写适配(附踩坑清单)
数据库
猫猫不是喵喵.18 小时前
从0到1创建一个微服务项目
java·数据库·微服务·1024程序员节
lsc_blog19 小时前
听到优化风声,先准备能由自己掌控的事
数据库·分布式·中间件·职场和发展·重构