客户端获取服务器端MySQL数据库表中数据C++实现

之前在 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实现

  1. 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,但需服务器端支持

  1. Connector ODBC 8.0:ODBC(Open Database Connectivity)驱动,让任何支持ODBC标准的应用程序都能连接MySQL。

  2. MySQL Router 8.0:一个轻量级的中间层代理/路由组件。

  3. MySQL Server 8.0:核心数据库服务端。也包含C API即mysql.h可用于连接MySQL,不依赖Boost。

  4. MySQL Shell 8.0:命令行管理工具。

  5. 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;
}

结果如下:

GitHubhttps://github.com/fengbingchun/Messy_Test

相关推荐
灯澜忆梦3 小时前
MySQL 完整学习路线
学习·mysql
ClouGence3 小时前
保障公共安全,一线治安机构实时数据应用与实践
数据库·mysql·postgresql
Minxinbb4 小时前
TDSQL for MySQL 服务器重启操作
数据库·mysql·dba
渣渣灰飞7 小时前
MySQL 系统学习 第二阶段 第三章 DQL(Data Query Language)第二节:WHERE 条件查询
sql·学习·mysql
不知疲倦的仄仄8 小时前
ShardingSphere:高效管理数据库集群的终极方案
sql·mysql·oracle·database
Java小白笔记9 小时前
MySQL中存储过程大表分批删除历史数据
android·mysql·adb
数据库小学妹10 小时前
MySQL慢SQL优化实战指南:EXPLAIN分析+索引优化+8个真实案例
sql·mysql·mysql优化·数据库性能·索引优化
无小道11 小时前
Redis——redis和mysql的数据同步
数据库·redis·mysql
linuxoffer12 小时前
MYSQL回收碎片空间
数据库·mysql