news 2026/7/17 6:32:43

MySQL数据库 (十八) MySQL访问,C/C++引入MySQL客户端库

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MySQL数据库 (十八) MySQL访问,C/C++引入MySQL客户端库

目录

一、C/C++引入MySQL客户端库

mysql connect引入MySQL第三方库

mysql接口介绍

初始化 mysql_init() :

连接数据库 mysql_real_connect() :

下达mysql命令 mysql_query() :

关闭 mysql_close() :

总结:

示例:

C/C++查询的处理细节

mysql_store_result()

二、总结


平时我们操作 MySQL 数据库,都是直接打开终端,用 mysql 命令行客户端输入 SQL 完成增删改查。但真实的后端开发场景里,业务程序不会人工手动敲命令操作数据库。比如我们用 C/C++ 写后台服务、数据采集程序以及高性能工具程序时,程序需要自动不间断地读写数据库,这就必须让代码直接和 MySQL 服务建立网络通信,自动发送 SQL、接收数据结果。因此 MySQL 官方专门提供了一套 C 语言客户端开发库,C 和 C++ 代码都可以直接调用这套库封装好的接口,不用我们自己手写底层 Socket、不用解析 MySQL 私有通信协议,就能完成数据库交互。所以今天我们完整学习从零搭建环境、配置数据库账号、编写 C/C++ 代码、编译运行,实现程序自动连接并操作 MySQL 服务。

一、C/C++引入MySQL客户端库

程序想要登录 MySQL 服务,不能直接使用 root 超级管理员账号,生产环境和本地开发都要遵循最小权限原则,需要单独创建一个专供 C/C++ 程序使用的数据库账号,并且只给它分配指定数据库的操作权限。 如下 :

首先登录 MySQL 的 root 管理员账号,执行创建专用程序用户的语句 create user 'connector'@'localhost' identified by '123456';。这条语句创建本地登录账号 connector,密码 123456,专门给本地 C/C++ 程序使用;执行完成后查询 user 表,就能在用户列表里看到这条全新记录。 刚创建完成的用户默认没有任何库表操作权限,哪怕登录成功也无法读写数据。

因此下一步执行授权语句:grant all on conn.* to 'connector'@'localhost';。这条语句代表给 connector 账号开放 conn 这个数据库下所有数据表的全部操作权限,测试环境可以直接用 all,正式项目只分配 select、insert、update、delete 四类基础权限。 权限分配完成后执行 flush privileges; 刷新权限缓存,让新建用户、分配的权限立刻在 MySQL 服务中生效。如果跳过这条刷新语句,部分 MySQL 版本会出现权限延迟,代码连接数据库时报访问拒绝。 到这里数据库侧的准备工作全部完成,拥有了程序专用登录账号、对应数据库访问权限,接下来才能着手本地开发环境配置。

mysql connect引入MySQL第三方库

首先我们明确核心知识点:想要用 C 语言代码操作 MySQL,不能凭空编写网络通信逻辑,必须依赖 MySQL 官方提供的第三方开发库 Connector/C。这套库分为两大核心组件,缺一不可,分别是include 头文件目录、lib 库文件目录。头文件里存放所有 API 函数、结构体的声明,告诉编译器有哪些可用函数;lib 目录存放编译好的静态库、动态库,里面是函数的底层实现代码,编译程序时需要链接这部分库,程序运行时也需要加载动态库。在正式写数据库连接逻辑之前,我们必须先把这套开发库完整部署到本地环境,这是所有编码工作的前置基础。

使用 Connector/C 库有两个硬性前提,第一是本地 MySQL 服务必须正常运行,服务没有启动的话,后续代码即便库引入成功,也无法建立 TCP 连接访问数据库;第二是获取对应系统平台的 Connector/C 开发包,Linux、Windows、MacOS 的库文件互不兼容,需要去 MySQL 官网下载匹配操作系统版本的压缩包。 在 Linux 系统中也可以直接使用 yum、apt 包管理器一键安装开发包。

下面我们看一下拆解解压后的 Connector/C 包目录结构:

tree 命令打印出的文件夹层级,解压下载好的 Connector/C 压缩包后,根目录下只会出现两个关键文件夹:include 文件夹与 lib 文件夹。先说 include 目录,里面包含大量.h 后缀头文件,其中核心文件是 mysql.h,我们写 C 代码时必须#include <mysql.h>,这个文件定义了 MYSQL 连接句柄、MYSQL_RES 结果集、mysql_init、mysql_real_connect 等全部操作函数的声明,没有这个头文件编译器会报找不到 MySQL 相关类型、函数的错误。其余配套头文件负责字符集、错误码、线程、预处理语句等辅助功能。再讲 lib 目录,里面存放编译程序需要链接的库文件,分为静态库.a和动态库.so两类。截图里能看到 libmysqlclient.a静态库,以及带版本号的 libmysqlclient.so 系列动态链接库,软链接指向对应真实库文件。静态库编译时会直接打包进可执行程序,动态库运行时才会被程序加载,是 Linux 环境下最常用的链接方式。

mysql接口介绍

我们引入好 Connector/C 库、环境校验完成之后,操作 MySQL 数据库的所有代码都必须遵循一套固定的接口调用顺序,顺序不能颠倒。完整流程为:先调用 mysql_init() 初始化连接句柄 → 调用 mysql_real_connect() 建立 TCP 网络连接 → 调用 mysql_query() 发送 SQL 语句执行操作 → 处理查询结果集(查询语句专属步骤) → 全部操作完成后调用 mysql_close() 关闭连接释放资源。 每一个接口各司其职,前一个接口执行成功,才能执行下一个接口,少任何一步或者顺序错乱,程序都会出现崩溃、连接失败、数据读取异常等问题。

初始化 mysql_init() :

只要我们想要使用 MySQL 客户端库,第一步必须执行初始化,没有初始化就无法调用任何数据库操作接口。

这个接口的函数原型是MYSQL *mysql_init(MYSQL *mysql);,日常标准写法是 MYSQL *mfp = mysql_init(NULL);,传入 NULL 代表让函数在堆内存中自动创建一块 MYSQL 结构体空间,函数执行完成后返回该结构体的指针。MYSQL 结构体是整个数据库连接的核心载体,里面会存储后续连接的 IP 地址、账号、密码、端口、字符集、套接字描述符、错误信息、底层操作函数指针等全部数据。所有后续连接、执行 SQL、获取错误的接口,都需要传入这个 MYSQL 指针作为第一个参数。如果初始化失败,函数会返回 NULL,代表内存分配失败,此时程序无法继续操作数据库,需要直接终止并打印错误信息。

连接数据库 mysql_real_connect() :

完成 mysql_init 初始化得到合法 MYSQL 句柄之后,下一步必须调用 mysql_real_connect() 接口,和 MySQL 服务端建立基于 TCP/IP 的网络通信通道,因为 MySQL 底层通信依赖 TCP 协议。


函数一共七个参数,每个参数作用清晰直观。第一个参数就是 mysql_init 返回的 MYSQL 句柄指针;第二个 host 是数据库服务地址,本地连接填写 localhost 或者 127.0.0.1;第三个 user 是登录数据库的用户名,也就是我们之前创建的 connector;第四个 passwd 是账号对应的登录密码;第五个 db 是程序要操作的目标数据库名,这里填写 conn;第六个 port 是 MySQL 服务端口,默认固定 3306;第七个 unix_socket 本地连接填 NULL 即可;最后 clientflag 是连接标识,默认传 0。

函数执行逻辑:内部自动发起 TCP 三次握手,按照 MySQL 私有协议把账号、密码、库名发送给服务端,服务端校验 user 表的账号权限,校验通过就返回和入参一致的 MYSQL 指针,代表连接成功;校验失败返回 NULL,我们可以通过配套 mysql_error 接口拿到拒绝原因,比如账号不存在、密码错误、没有目标数据库权限。这里连接操作必须使用我们前置创建好的 connector 专用账号,不能随意使用 root。

下达mysql命令 mysql_query() :

成功和数据库建立连接之后,所有增删改查、建表、删库等 SQL 命令,都依靠 mysql_query()这个接口下发给 MySQL 服务端。

函数原型为 int mysql_query(MYSQL *mysql, const char *q);,第一个参数是我们已经建立好连接的 MYSQL 句柄;第二个参数是字符串形式的完整 SQL 语句,比如 select * from user、insert into user values(4,'张飞'),语句末尾不需要额外加分号也可以正常执行。
函数拥有明确的返回值规则:返回 0 代表 SQL 语句执行成功;返回非 0 数字代表执行失败,比如表不存在、字段不匹配、权限不足、SQL 语法写错,都可以结合 mysql_error 打印具体报错内容区分问题。
这里区分两种 SQL 执行结果:如果是增、删、改这类无返回数据的语句,执行完 mysql_query 直接通过返回值判断成功与否;如果是 select 查询类语句,mysql_query 仅负责把查询请求发给服务端,想要拿到表里的数据,还需要配套结果集接口解析返回内容。

关闭 mysql_close() :

整套数据库操作全部结束、不再需要访问 MySQL 服务时,必须调用 mysql_close() 接口,用来关闭 TCP 网络连接,同时释放 mysql_init 阶段在堆上分配的 MYSQL 结构体内存,避免程序出现内存泄漏、数据库闲置长连接堆积。

函数原型为 void mysql_close(MYSQL *sock);,只需要传入之前初始化、建立连接的 MYSQL 句柄指针即可,没有返回值。这个接口是收尾操作,必须放在所有 SQL 执行、结果集读取完成之后调用;如果中途程序异常退出没有执行 mysql_close,会导致这条 TCP 连接长时间挂在 MySQL 服务端,占用服务连接数资源,高并发场景下会引发服务连接耗尽无法登录的故障。

总结:

整套接口的调用逻辑是闭环的,mysql_init 开辟存储连接信息的结构体,mysql_real_connect 搭建程序与数据库的网络通道,mysql_query 实现业务层面的 SQL 交互,mysql_close 销毁连接与内存回收。这四个接口是 C/C++ 操作 MySQL 最基础、必不可少的核心接口,后续所有读取查询数据、事务、预处理语句的拓展接口,全部都要依托 MYSQL 句柄,在这套基础流程之上才能使用。

示例:

下面我们开始编写代码,我们在底层调用 MySQL 官方 C 接口,复现前面讲的四大核心接口调用顺序:mysql_init 初始化句柄、mysql_real_connect 建立连接、mysql_close 释放连接,中间做两层判空容错,用来验证程序能不能正常和 MySQL 服务建立通信。

代码最开头先引入三类头文件,<iostream>用来做控制台打印输出,<string>封装字符串存储连接参数,<mysql/mysql.h>是 MySQL 客户端库的核心头文件,所有数据库操作接口、MYSQL 结构体都定义在这个文件内。接着定义全局常量,统一存放连接数据库的固定参数,和我们之前创建的账号完全匹配:host 本地地址 127.0.0.1,登录用户名 connector,密码 123456,要操作的目标数据库 conn,服务端口 8080。把参数单独抽成常量,后续修改账号、IP、库名时只改一处即可,代码维护更方便。进入 main 主函数后,第一步执行初始化操作 MYSQL *my = mysql_init(nullptr);,给 mysql_init 传入空指针,自动在堆分配 MYSQL 连接句柄。紧接着做容错判断,如果返回值是 nullptr,代表初始化内存分配失败,打印错误信息后直接 return 1 终止程序,错误码 1 用来标记初始化阶段故障。初始化成功后调用核心连接函数 mysql_real_connect,把 string 类型的参数通过.c_str()转为 C 语言原生 char * 字符串,依次传入地址、用户名、密码、数据库名、端口,unix_socket 填 nullptr,客户端标识传 0。函数返回值和 nullptr 对比,若相等代表 TCP 握手、账号密码校验、权限校验任意一步失败,打印连接错误,return 2 标记连接阶段故障。两层校验全部通过,就会打印 connect success,代表程序和 MySQL 服务成功建立 TCP 连接。最后调用mysql_close(my)关闭连接、释放句柄内存,return 0 正常退出程序。

下面我们运行上述代码观察运行结果:

执行编译好的./mytest,终端输出 connect success,证明整套库引入、账号权限、网络通信全部没有问题,完成连接验证,接下来就可以基于这个可用连接,执行增删改查 SQL 语句操作数据表。

下面我们需要建立一张表进行实验操作:

程序连接成功后,我们需要一张数据表用来测试插入、修改、删除、查询操作,所以先在 MySQL 命令行执行建表语句,创建 user 测试表。建表语句定义了四张字段:id 是 bigint 类型主键,设置自增,每条数据自动生成唯一编号;name 是 varchar (32) 字符串,非空约束,必须填写姓名;age 是 int 整数,非空约束,年龄不能为空;telphone 手机号字段 varchar (32),设置唯一约束,不允许两条数据手机号重复。

执行desc user;查看表结构,表格清晰展示每一列的字段名、数据类型、是否允许为空、索引类型、默认值、额外属性,能直观核对我们设置的主键、自增、唯一约束是否生效。这张 user 表就是我们接下来所有 C++ 代码操作的目标对象,所有 insert、update、delete、select 语句都针对这张表编写。

所以下面我们就进行下达 mysql 指令的代码编写:

上面代码是在之前成功建立 MySQL 连接的基础上,封装了一个简易交互式终端,让程序能够手动输入任意 SQL 语句下发给数据库,核心依靠 mysql_query 接口完成指令下发。我们在外层使用 while(true) 搭建无限循环,只要不输入 quit,程序就会持续接收用户输入的 SQL。循环内部先打印提示符 MySQL>>>,模拟 mysql 命令行客户端的交互界面,方便区分输入区域。紧接着调用 std::getline(std::cin, sql) 读取用户整行输入,同时做判断:如果读取输入失败,或者用户输入的内容等于字符串 quit,程序就打印 bye bye 并跳出循环,结束交互流程。拿到完整 SQL 字符串后,调用核心执行函数int n = mysql_query(my, sql.c_str());,把 C++ 的 string 转为 C 语言需要的 char * 字符串传入接口。函数返回值用来区分执行结果,约定规则是返回 0 代表 SQL 执行无错误,非 0 数字代表执行失败。代码中通过 if 分支做结果打印:n 等于 0 时输出当前 SQL 执行成功,同时打印返回值 0;失败则通过标准错误流打印失败提示与错误码。

编译运行程序后,终端会出现 MySQL>>> 等待输入,说明交互循环逻辑正常生效,程序已经具备下发任意 SQL 指令的能力。

下面我们接着插入数据:

成功运行后我们接着在交互提示符后输入标准 insert 插入语句,向之前创建的 user 测试表写入数据,语句示例为 insert into user (name, age, telphone) values ('tom', 12, '123456789');。
执行完成后程序输出 success: 0,对应 mysql_query 返回值为 0,代表插入操作没有语法错误、权限校验通过,数据库已经接收并执行这条新增语句。这里补充一个实操细节:SQL 语句末尾的分号可加可不加,mysql_query 接口内部会自动兼容两种写法,不会影响执行结果。我们重复执行一条结构一致、手机号不同的 insert 语句,同样打印 success:0,代表第二条数据也写入成功,两条插入操作都没有触发表的约束报错,telphone 字段唯一约束因为手机号不一样,没有产生冲突。

怎么证明?

程序仅返回执行成功码只能代表 SQL 无语法故障,想要确认数据真正落盘入库,需要登录原生 MySQL 客户端查询 user 表。所以我们在终端执行 select * from user; 全表查询语句,结果表里能看到两条完整记录,第一条 id 为 1、第二条 id 为 2,name、age、telphone 和我们在程序中输入的内容完全匹配。id 自增主键自动生成,也印证插入操作真实修改了数据库内的物理数据,并非仅在程序内存中模拟执行。这一步验证也区分了两种场景:mysql_query 返回 0 仅代表语句执行无报错,不代表肉眼可见的数据变更;必须通过原生客户端查询数据库底层表,才能百分百确认增删改操作持久化完成。

前面我们通过程序完成了 insert 插入数据,现在用同样的mysql_query接口实现数据更新操作,底层执行逻辑和插入完全一致。我们先定义存储更新语句的 string 对象 sql,完整 SQL 语句为 update user set name='Jimmy' where id=2;,作用是把主键 id 等于 2 这条记录的 name 字段修改为 Jimmy。随后调用 mysql_query(my, sql.c_str()) 下发语句,通过返回值 n 判断执行状态,0 代表执行成功,非 0 代表失败。

下面我们运行:

运行编译好的程序,在交互终端输入这条 update 语句后,程序直接输出 success,说明 SQL 语句已经被 MySQL 服务正常接收执行。

为了确认修改真正生效,我们切换到原生 mysql 客户端执行 select * from user; 查询整张 user 表,能清晰看到 id=2 对应的 name 字段已经从 tom 变成 Jimmy,其他字段数据保持不变,证明更新操作持久化到数据库中。

从这套逻辑可以总结:insert 新增、update 修改、delete 删除这三类 DML 语句,全部只依靠mysql_query这一个接口就能完成下发,代码写法统一,只需要更换字符串里的 SQL 文本即可,底层没有额外复杂接口。

这里要注意 : 无论插入、更新还是删除,只要 mysql_query 返回非 0、程序提示 failed,基本都是数据库约束或者语法层面出现冲突。最典型的场景就是主键唯一冲突:比如重复插入相同 id 的记录、向 telphone 唯一字段写入重复手机号,MySQL 会触发唯一约束拦截,直接返回执行失败;除此之外,字段非空约束未传值、字段数据类型不匹配、表名字段名拼写错误、用户缺少对应操作权限,都会导致语句执行失败,程序都会捕获到失败返回值。这类报错仅靠返回值只能知道执行出错,想要查看详细失败原因,还可以搭配mysql_error()接口打印完整数据库错误信息,方便定位问题。

这里补充一个高频踩坑点:程序读写英文数字完全正常,但读写中文时出现问号、乱码,根源是程序与 MySQL 服务的字符集不匹配:

MySQL 客户端库默认字符集是 latin1,而我们建表、存储数据大多使用 utf8 字符集,两边编码不一致,中文数据传输解析时就会乱码。官方提供专用接口 mysql_set_character_set() 统一设置连接字符集,使用时机必须放在mysql_real_connect连接成功之后、执行任何 SQL 语句之前,传入参数为我们已经建立好的 MYSQL 连接句柄,第二个参数指定"utf8"。调用这条接口后,当前这条数据库连接的收发数据都会统一采用 utf8 编码,程序插入中文、查询中文都不会再出现乱码,是开发中必须加上的标准化配置。


C/C++查询的处理细节

前面我们执行 insert、update、delete 这类修改数据的 SQL 时,只需要调用 mysql_query,通过返回值 0 就能确认操作完成,不需要额外读取数据。

但 select 查询语句完全不一样,如上图这段代码我们只执行 mysql_query("select * from user"),程序只会打印 success,但是控制台看不到表里的任何数据。为什么? 原因很简单:mysql_query的作用仅仅是把 SQL 发送给 MySQL 服务、判断语句语法和权限是否合法,它不会主动把查询到的多行数据拉取到程序并打印。增删改执行完没有返回数据集,所以只看返回值就够用;select 会带回完整表格数据,必须额外调用一套专门读取结果集的接口,才能拿到查询内容。

mysql_store_result()

当 mysql_query执 行 select 并且返回 0(执行成功)之后,必须调用 mysql_store_result() 把服务端返回的所有查询数据全部拉取到本地程序内存:

这个函数原型是MYSQL_RES *mysql_store_result(MYSQL *mysql);,入参就是我们已经连接好的 MYSQL 句柄,返回值是一个 MYSQL_RES 类型的指针,这个结构体专门存放整张查询结果表,包含行数、列数、每一行的字段数据。底层逻辑上,这个函数会通过 MYSQL 句柄内部封装的函数指针,读取 TCP 通道里传回的所有行数据,并且在堆内存中分配空间存储这些数据。这里有一个非常关键的注意点:这片内存是手动分配出来的,程序不会自动回收,如果读完数据不释放,会持续占用内存,引发内存泄漏。后续必须配套 mysql_free_result 释放 MYSQL_RES 占用的内存。

MYSQLRES:

首先在官方文档的定义中,我们要分清 MYSQL 与 MYSQL_RES 两个核心结构体的区别,文档里一共给出两个基础结构体,二者完全不同,是整套 C API 的两大载体。

  1. 第一个是 MYSQL 结构体,它是单条数据库连接的句柄,我们从 mysql_init 创建、mysql_real_connect 建立 TCP 通道,全程操作的都是这个结构体。几乎所有数据库接口第一个参数都要传 MYSQL*,文档也明确提醒不能复制这个结构体,复制后的对象无法保证正常使用。
  2. 第二个就是红框标注的 MYSQL_RES,专门用来承载带返回行的查询结果,只有执行 SELECT、SHOW、DESCRIBE、EXPLAIN 这类会返回表格数据的 SQL 时,才会生成 MYSQL_RES 对象。执行 insert、update、delete 没有返回表格,不会产生这个结构体。服务端传回的整张查询表格数据,在代码里统一叫做结果集 result set,全部存放在 MYSQL_RES 内部。

想要得到 MYSQL_RES 对象,就必须在 mysql_query 执行查询且返回 0 成功后,调用 MYSQL_RES *mysql_store_result(MYSQL *mysql);。函数接收已经连通的 MYSQL 连接句柄,内部一次性把服务端所有查询行拉取到程序堆内存;执行成功返回合法 MYSQL_RES * 指针,失败返回 NULL。这里要记住关键细节:这块数据是堆上动态分配的,程序不会自动回收,使用完毕必须调用 mysql_free_result 释放,否则持续内存泄漏。

怎么理解这个 MYSQLRES 结构体呢?

如上图,我们可以把 MYSQL_RES 内部存储逻辑类比成字符串二维数组 char** res []。我们 user 表里每一行数据,会被封装成一个一维 char数组,数组里每个元素代表一个字段,不管数据库里是数字、字符串、自增 id,客户端库统一全部转成 char字符串;多张行数据组合在一起,就形成二维数组,全部托管在 MYSQL_RES 结构体内部。我们后续读取数据的接口,本质就是遍历这个二维数组,逐行、逐字段取出字符串打印。

又因为是表结构,表有行和列,拿到 MYSQL_RES 指针后,我们可以通过两个接口获取整张结果集的行和列,方便循环遍历豆好所以再介绍两个接口:

获取结果行数 mysql_num_rows() :

第一个接口my_ulonglong mysql_num_rows(MYSQL_RES *res);,传入结果集指针,返回查询结果一共有多少行记录,适合提前判断表里有没有数据、循环控制总次数。

获取结果列数 mysql_num_fields() :

第二个接口unsigned int mysql_num_fields(MYSQL_RES *res);,获取一张结果集有多少列字段,比如 user 表是 id、name、age、telphone 四列,调用后返回数字 4,遍历单行字段时用来做循环上限。

这两个函数入参都只能是合法的 MYSQL_RES 指针,不能传入 MYSQL 连接句柄,否则程序会直接崩溃。

下面我们验证一下行和列:

首先定义查询 SQL 字符串 select * from user,调用 mysql_query 向数据库下发查询语句,通过返回值n判断语句是否执行成功。如果返回非 0,代表 SQL 语法错误、权限不足等问题,直接打印失败信息并退出程序;只有n == 0时,才能继续处理查询返回的数据。确认查询语句执行无错误后,调用 mysql_store_result(my),传入已建立好的数据库连接句柄 my,把 MySQL 服务端传回的整张表格数据一次性拉取到程序内存,返回MYSQL_RES类型的结果集指针 res。这里必须增加判空逻辑,如果res等于空指针,代表拉取结果集失败,打印错误并终止程序,避免后续操作空指针引发程序崩溃。拿到合法的res结果集指针后,分别调用两个统计接口:mysql_num_rows(res)用来获取查询结果一共有多少行记录,返回值存入rows变量;mysql_num_fields(res)用来获取单条数据包含多少个字段(也就是多少列),返回值存入fields变量。最后通过控制台打印出行、列的数值,直观展示当前查询结果集的尺寸。

程序运行后,首先输出 select * from user success,证明查询语句下发成功;紧接着打印行:3、列:4,和下方数据表内容完全匹配。数据表内一共有 3 条数据记录,对应 3 行;每条数据包含 id、name、age、telphone 四个字段,对应 4 列,接口统计出来的数值和真实表格结构完全一致,验证了 mysql_num_rows、mysql_num_fields 两个接口统计数据准确可靠。

同时查询返回的行数、列数会跟随 SQL 筛选条件动态变化。如果我们给 select 语句加上 where 条件过滤部分数据,或者只查询指定几个字段,两个接口返回的行列数值会同步改变,接口会自动根据当前结果集实时统计,不需要我们手动计算。

现在我们已经通过 mysql_store_result 拿到了完整结果集 MYSQL_RES* res,也通过 mysql_num_rows、mysql_num_fields 拿到总行、总列,但只能看到数字,拿不到表里每一条真实数据,所以 MySQL C API 专门提供mysql_fetch_row用来逐行提取数据。

获取结果内容 mysql_fetch_row() :

函数原型MYSQL_ROW mysql_fetch_row(MYSQL_RES *result);,唯一入参是我们之前拿到的结果集指针 res。它内部自带迭代器指针,每调用一次自动向后移动一行;如果读到最后一行无数据时,返回 nullptr,代表遍历结束。无论数据库字段是 int、bigint、varchar 类型,客户端全部转成字符串给我们,代码里统一按字符串打印、解析即可,不用区分字段原始类型。

我们现在已经有了行和列了,下面就应该进行遍历操作,MySQL为了支持遍历操作,提供了一个结构体 MYSQL_ROW,如下:

先看底层定义:typedef char** MYSQL_ROW;,MYSQL_ROW 本质就是二级字符指针,等效字符串一维数组。调用一次该函数,就会从结果集里取出一行完整记录,该行里每一列数据都被统一转为 char* 字符串,存进这个一维数组,通过下标 row[0]、row[1] 就能分别取第 1 列、第 2 列字段。

所以最终的遍历就是:

在进入循环读取数据之前,我们已经完成了查询、拉取结果集、获取行列总数三步基础操作。第一步执行 mysql_query 下发 select 语句,确认返回 0 代表查询无语法、权限错误;第二步调用 mysql_store_result 将数据库返回的整张数据表加载到程序内存,得到 MYSQL_RES 类型的结果集指针 res;

第三步通过 mysql_num_rows(res) 拿到结果总行数、mysql_num_fields(res)拿到单条记录总列数。 这两个行列数值是循环的边界条件,外层循环控制一共读取多少行数据,内层循环控制一行里要打印多少个字段,没有这两个数值就无法确定循环终止条件。代码里先打印出行、列数字,用来提前校验结果集尺寸是否和预期数据表匹配,方便调试排查数据缺失问题。

红框内第一层循环 for(int i = 0; i < rows; i++),循环次数等于查询结果总行数,每一次循环代表读取数据表里完整一行记录。循环内部核心语句 MYSQL_ROW row = mysql_fetch_row(res);,每调用一次这个函数,就会从 MYSQL_RES 结果集中提取下一行数据。MYSQL_ROW 本质是 char**二级字符指针,等效一维字符串数组,当前行每一列的数据都会转为 char * 字符串存放在这个数组里,不管数据库里字段是数字、字符串类型,客户端统一转字符串处理。函数内部自带迭代器,每执行一次游标自动向后偏移一行,不需要我们手动维护行下标,循环走完 rows 次就刚好把全部数据读取完毕。

内层循环 for(int j = 0; j < fields; j++),用来遍历当前这一行的全部字段。row[j]代表当前行的第 j 列数据,下标从 0 开始依次对应 id、name、age、telphone,用制表符\t分隔不同字段,保证控制台输出排版整齐。注释里的 *(row+j) 和 row[j] 写法完全等价,数组下标语法本质就是指针偏移取值,两种写法都可以读取对应列数据。一行内所有字段循环打印完成后执行 std::cout << "\n"; 换行,切换到下一行记录,还原数据库表格分行展示的效果。

运行结果:

程序运行输出分为三层信息,第一层打印 select * from user success 代表查询语句下发成功;第二层打印行 3、列 4,和 user 表 3 条记录、4 个字段的结构完全对应;第三层循环打印出 3 行完整业务数据,每条记录的 id、姓名、年龄、手机号全部完整展示,中文姓名、数字都没有丢失、乱码,证明双层循环搭配 mysql_fetch_row 可以完整、正确读取整张查询结果集。

但是上面我们用 mysql_fetch_row 只能拿到表里每一行存储的数据内容,比如 2、Jimmy、12 这类业务值。但很多场景我们还需要拿到字段本身的描述信息:列名、字段数据类型、所属表、字段长度、是否主键、是否允许为空等元数据。所以 MySQL C API 提供mysql_fetch_fields() 接口,专门一次性读取结果集里所有字段的完整属性,解决只查数据、看不到表头字段信息的问题。

获取列名 mysql_fetch_fields() :

  • 参数是已经加载完数据的 MYSQL_RES* 结果集指针;
  • 返回值为 MYSQL_FIELD*,本质是一个结构体数组首地址,数组长度等于 mysql_num_fields(res) 获取到的总列数。我们可以通过下标 fields_arr[0]、fields_arr[1] 依次访问第 0 列、第 1 列的字段属性结构体。

MYSQL_FIELD 是存储单列全部属性的结构体,如上图,成员比较多,我们只讲重要的:

  1. char *name 当前查询显示的列名,如果 SQL 里给字段起了别名,这里存别名;没有别名就是原字段名(最常用,用来打印表头)。
  2. char *org_name 字段在数据库表中原始真实名称,不受别名影响。
  3. char *table 当前字段所属数据表的别名;org_table 是原始表名。
  4. char *db 该表所属的数据库名。
  5. unsigned long length 建表时定义的字段长度,比如 varchar(32) 这里值就是 32。
  6. unsigned long max_length 本次查询结果里,该列数据实际最长字符串长度。
  7. unsigned int flags 标记字段约束属性,位标识:主键、自增、非空、唯一、无符号数字等,可以做位运算判断。
  8. enum enum_field_types type 字段原始数据类型,区分是 INT、BIGINT、VARCHAR、DATE 等类型。

其余带 xxx_length 的成员是对应字符串的字节长度,日常打印表头基本不用。

接着我们使用该接口进行遍历:

我们先看红框里获取并打印列名的完整代码逻辑,在已经拿到结果集 res、算出总列数 fields 之后,调用 mysql_fetch_fields (res) 得到 MYSQL_FIELD 类型的结构体数组首地址,存放到 fields_array 指针中,这个数组里每一个元素都对应查询结果里一列的完整属性,数组长度刚好等于总列数。紧接着开启单层循环,循环次数等于字段总数,在循环内部通过 fields_array [i].name 取出每一列的列名字符串,搭配制表符分隔输出,循环结束后换行,这样就能在打印数据表内容之前,先输出一行表头,和原生 mysql 客户端查询时展示的字段标题完全对应。表头打印完成后,再执行之前学习的双层循环逻辑读取每一行真实数据,外层循环控制总行数,每次调用 mysql_fetch_row 取出一行 MYSQL_ROW 数据,内层循环遍历该行所有字段,按列依次打印单元格内容,每一行数据遍历完毕就换行输出,表头和数据拼接在一起,控制台输出的排版就和数据库命令行查询效果完全一致。

运行程序后可以看到先打印 id、name、age、telphone 这四个字段名,再逐行输出三条用户记录,直观证明我们成功拿到了表的列属性信息,不只是单纯读取存储的数据。

整套查询、打印流程全部完成之后,不能忽略资源释放的收尾操作,我们调用mysql_free_result (res) 释放 MYSQL_RES 结果集占用的堆内存,mysql_store_result 会在堆上分配空间存放整张查询数据,如果不执行这条释放接口,多次查询会持续堆积内存造成内存泄漏,同时 fields_array、MYSQL_ROW 里所有指向数据的指针都会依附 res 存在,释放之后这些指针就会全部失效,所以必须在所有读取、打印逻辑结束之后再执行释放。

释放完结果集,最后调用 mysql_close (my) 关闭数据库 TCP 连接,销毁最开始 mysql_init 创建的 MYSQL 连接句柄,回收连接相关资源,之后主函数正常 return 0 结束程序,至此一条完整的 select 查询全流程闭环完成,从初始化连接、下发 SQL、拉取结果集、读取列属性表头、遍历行数据、释放结果集、关闭连接,整套规范操作全部覆盖,区分开增删改无结果集无需释放 res、查询语句必须手动释放结果集的核心差异。

二、总结

本文介绍了如何在C/C++程序中通过MySQL官方提供的Connector/C库实现数据库操作。首先讲解了创建专用数据库账号、配置权限等准备工作,然后详细解析了四大核心接口:mysql_init初始化连接、mysql_real_connect建立TCP连接、mysql_query执行SQL语句、mysql_close关闭连接。文章重点说明了查询操作的特殊处理流程,包括使用mysql_store_result获取结果集、mysql_fetch_row遍历数据行、mysql_fetch_fields获取列名等元数据。最后强调了必须调用mysql_free_result释放结果集内存,避免内存泄漏。通过完整示例演示了从连接到查询的完整流程,为C/C++开发者提供了操作MySQL数据库的实用指南。

谢谢大家的观看!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/17 6:31:50

C++与OpenCV实现Photoshop曲线算法:从原理到工程实践

1. 项目概述&#xff1a;从Photoshop到代码的曲线艺术在图像处理的世界里&#xff0c;Photoshop的“曲线”工具堪称调色之王。它通过一个简单的二维坐标系&#xff0c;让用户能够对图像的亮度、对比度乃至色彩进行极其精细的调整。无论是专业摄影师校正曝光&#xff0c;还是设计…

作者头像 李华
网站建设 2026/7/17 6:31:40

腾讯SRPC框架与微线程技术:C++高并发微服务架构核心解析

1. 项目概述如果你是一名C后台开发&#xff0c;尤其是在国内互联网大厂工作&#xff0c;那么“微服务”、“高并发”、“RPC”这些词对你来说一定不陌生。今天要聊的&#xff0c;是腾讯内部一个曾经被广泛使用&#xff0c;如今已开源归档的微服务开发框架——MSEC&#xff08;M…

作者头像 李华
网站建设 2026/7/17 6:30:11

AI Agent Skills开发指南:核心技能与实战解析

1. AI Agent Skills的本质与价值在AI Agent开发领域&#xff0c;Skills&#xff08;技能&#xff09;正成为提升智能体能力的核心组件。简单来说&#xff0c;Skills就是让AI Agent具备特定领域专业能力的模块化组件。它们就像给智能体安装的"技能插件"&#xff0c;使…

作者头像 李华
网站建设 2026/7/17 6:30:11

数组逆向搜索与文件加密的工程化实现:从定位策略到安全处理

1. 项目概述&#xff1a;从“逆向搜索”到“文件加密”的工程化思考最近在重构一个老项目的文件处理模块时&#xff0c;遇到了一个挺有意思的需求&#xff1a;需要先在一个配置数组中快速定位某个特定标识符的最后一次出现位置&#xff0c;然后根据这个位置信息&#xff0c;决定…

作者头像 李华
网站建设 2026/7/17 6:30:06

TTS语音生成器实战:从环境配置到API集成的完整指南

这次我们来看一个有趣的AI语音项目——"每日来给义父请安"语音生成器。这个项目基于TTS&#xff08;文本转语音&#xff09;技术&#xff0c;能够生成特定风格的问候语音&#xff0c;特别适合想要制作个性化问候或娱乐内容的用户。从项目名称就能看出&#xff0c;这是…

作者头像 李华