之前在 https://blog.csdn.net/fengbingchun/article/details/162538324 介绍过通过数据库图形化工具Beekeeper Studio实现在客户端访问服务器端(同一网段上的不同机子)MySQL数据库中表的操作流程,并将表中数据保存到csv文件中,这里通过MySQL API接口实现。这里使用的MySQL版本为8.0.19。
安装完MySQL后,在安装目录C:\Program Files\MySQL下有以下目录:如下图所示,这里使用MySQL Server 8.0中C API实现

- Connector C++ 8.0:C++开发用的客户端SDK,可连接MySQL、执行SQL。此目录下有两套接口可实现连接MySQL:
(1).mysql\jdbc.h:传统JDBC风格API,但需依赖Boost,MySQL高版本可能不再依赖Boost
(2).mysqlx\xdevapi.h:X DevAPI,X Protocol端口,不依赖Boost,但需服务器端支持
-
Connector ODBC 8.0:ODBC(Open Database Connectivity)驱动,让任何支持ODBC标准的应用程序都能连接MySQL。
-
MySQL Router 8.0:一个轻量级的中间层代理/路由组件。
-
MySQL Server 8.0:核心数据库服务端。也包含C API即mysql.h可用于连接MySQL,不依赖Boost。
-
MySQL Shell 8.0:命令行管理工具。
-
MySQL Workbench 8.0 CE:MySQL图形化工具。
用到的函数接口说明 :数据库中表会持续更新,因此使用stmt系列接口
1.mysql_init:分配/初始化一个MYSQL连接对象,失败返回nullptr。每个连接都必须先mysql_init,用完后必须对应调用mysql_close。
2.mysql_options:设置连接选项,比如连接超时(MYSQL_OPT_CONNECT_TIMEOUT)、读超时(MYSQL_OPT_READ_TIMEOUT)、写超时(MYSQL_OPT_WRITE_TIMEOUT)、字符集(MYSQL_SET_CHARSET_NAME)等,成功返回0。必须在连接建立mysql_real_connect前调用才有效。
3.mysql_real_connect:连接数据库,失败返回nullptr。失败时也需要调用mysql_close释放句柄。
4.mysql_stmt_init:创建Prepared Statement(预处理语句、预编译语句)对象,失败返回nullptr。基于一个已连接的MYSQL对象,创建一个新的预处理语句对象,是使用mysql_stmt_*系列接口的第一步。MYSQL_STMT对象是绑定在具体连接上的,连接断开重连后,之前的stmt对象失效,必须重新mysql_stmt_init + mysql_stmt_prepare。
5.mysql_stmt_prepare:预处理SQL语句,成功返回0。把带?占位符的SQL语句发给服务器预处理,之后可以反复用不同参数值执行 ,不需要每次都重新解析SQL。?的个数必须和mysql_stmt_bind_param绑定的参数个数一致。
6.mysql_stmt_bind_param:绑定输入参数,成功返回0。把SQL语句中的?占位符和实际的C++变量关联起来,告诉MySQL每个?该用什么类型、从哪块内存取值。MYSQL_BIND数组长度必须等于SQL中?的个数,且顺序要对应。
7.mysql_stmt_execute:执行预处理语句,成功返回0。用当前绑定的参数值,向服务器发送执行请求。执行成功后,结果集还没有传输到客户端,需要接下来mysql_stmt_bind_result + mysql_stmt_fetch才能真正取到数据(除非调用了mysql_stmt_store_result,那是一次性缓冲的做法,流式方案不用它)。
8.mysql_stmt_bind_result:绑定输出结果,成功返回0。告诉MySQL查询结果集里每一列应该存到哪块内存、用什么类型接收。和mysql_stmt_bind_param是对称的操作,一个管输入参数,一个管输出结果。必须在mysql_stmt_execute之后、mysql_stmt_fetch之前调用一次即可,之后每次mysql_stmt_fetch都会往同一块内存里写新数据,不需要重复mysql_stmt_bind_result。
9.mysql_stmt_fetch:逐行读取结果 。从服务器取一行数据,写入之前mysql_stmt_bind_result绑定的内存里。这是流式读取的核心:每调用一次只取一行,内存占用恒定,不管结果集有多少行,这也是不用mysql_stmt_store_result(效果等价于mysql_store_result,一次性缓冲)的原因。返回值:0:成功取到一行;MYSQL_NO_DATA:结果集已经读完。
10.mysql_error:返回最近一次在这个连接上执行的API调用产生的错误描述文本。如果上一次操作没有出错,返回空字符串""。
11.mysql_stmt_error:和mysql_error类似,但专门针对mysql_stmt_*系列函数的错误。
12.mysql_stmt_close:释放Prepared Statement对象,成功返回0。每个mysql_stmt_init都要对应一次mysql_stmt_close,尤其在重连场景下(旧连接断开时)要记得先关闭旧的stmt,再重新mysql_stmt_init/mysql_stmt_prepare,避免资源泄漏。
13.mysql_close:关闭与服务器的连接,释放mysql_init分配的所有相关资源。如果这个连接上还有未关闭的MYSQL_STMT,应该先mysql_stmt_close,再mysql_close。
测试代码如下:执行此代码仅需要三个动态库:libmysql.dll、libssl-1_1-x64.dll、libcrypto-1_1-x64.dll,客户端不需要安装MySQL
cpp
int test_client_mysql()
{
constexpr char host[]{ "192.168.19.205" }, user[]{ "feo" }, passwd[]{ "123456" }, db[]{ "info" };
constexpr unsigned int port{ 3306 }, timeout_sec{5};
constexpr char starttime[]{ "2026-06-01 00:00:00" }, endtime[]{ "2026-07-01 00:00:00" };
constexpr char csv_name[]{ "feo.csv" };
const std::string table_name{ "time_values2" }, time_column{ "receive_date" }, value_column{ "Feo" };
auto conn = mysql_init(nullptr);
if (!conn) {
std::cerr << "failed to mysql init" << std::endl;
return -1;
}
if (auto ret = mysql_options(conn, MYSQL_SET_CHARSET_NAME, "utf8mb4"); ret != 0) {
std::cerr << "failed to set charset name: " << mysql_error(conn) << std::endl;
return -1;
}
if (auto ret = mysql_options(conn, MYSQL_OPT_CONNECT_TIMEOUT, &timeout_sec); ret != 0) {
std::cerr << "failed to set connect timeout: " << mysql_error(conn) << std::endl;
return -1;
}
if (!mysql_real_connect(conn, host, user, passwd, db, port, nullptr, 0)) {
std::cerr << "failed to connect:" << mysql_error(conn) << std::endl;
return -1;
}
auto stmt = mysql_stmt_init(conn);
if (!stmt) {
std::cerr << "failed to stmt init: " << mysql_error(conn) << std::endl;
return -1;
}
std::string sql =
"SELECT " + time_column + ", " + value_column +
" FROM " + table_name +
" WHERE " + time_column + " >= ?"
" AND " + time_column + " < ?"
" ORDER BY " + time_column + " DESC";
if (mysql_stmt_prepare(stmt, sql.c_str(), static_cast<unsigned long>(sql.length())) != 0) {
std::cerr << "failed to stmp prepare: " << mysql_stmt_error(stmt) << std::endl;
mysql_stmt_close(stmt);
return -1;
}
MYSQL_BIND param[2]{};
memset(param, 0, sizeof(param));
param[0].buffer_type = MYSQL_TYPE_STRING;
param[0].buffer = (void*)starttime;
param[0].buffer_length = (unsigned long)strlen(starttime);
param[1].buffer_type = MYSQL_TYPE_STRING;
param[1].buffer = (void*)endtime;
param[1].buffer_length = (unsigned long)strlen(endtime);
if (mysql_stmt_bind_param(stmt, param) != 0) {
std::cerr << "failed to stmt bind param: " << mysql_stmt_error(stmt) << std::endl;
return -1;
}
if (mysql_stmt_execute(stmt) != 0) {
std::cerr << "failed to stmt execute: " << mysql_stmt_error(stmt) << std::endl;
return -1;
}
char receive_date[32]{}, feo[32]{};
unsigned long date_len{ 0 }, feo_len{ 0 };
bool date_null{ 0 }, feo_null{ 0 };
MYSQL_BIND result[2]{};
memset(result, 0, sizeof(result));
result[0].buffer_type = MYSQL_TYPE_STRING;
result[0].buffer = receive_date;
result[0].buffer_length = sizeof(receive_date);
result[0].length = &date_len;
result[0].is_null = &date_null;
result[1].buffer_type = MYSQL_TYPE_STRING;
result[1].buffer = feo;
result[1].buffer_length = sizeof(feo);
result[1].length = &feo_len;
result[1].is_null = &feo_null;
if (mysql_stmt_bind_result(stmt, result) != 0) {
std::cerr << "failed to stmt bind result: " << mysql_stmt_error(stmt) << std::endl;
return -1;
}
std::ofstream csv(csv_name);
if (!csv.is_open()) {
std::cerr << "failed to open csv file: " << csv_name << std::endl;
return -1;
}
int count{ 0 };
while (true) {
auto ret = mysql_stmt_fetch(stmt);
if (ret == MYSQL_NO_DATA)
break;
if (ret == 0) {
receive_date[date_len] = '\0';
feo[feo_len] = '\0';
csv << receive_date << "," << feo << "\n";
++count;
}
else {
std::cerr << "failed to fetch: " << ret << ", " << mysql_stmt_error(stmt) << std::endl;
}
}
std::cout << "number of valid data rows: " << count << std::endl;
mysql_stmt_close(stmt);
mysql_close(conn);
return 0;
}
结果如下:
