Mysql API学习

一、 MySQL C API 发展出来的背景

正如 websocketpp 是为了发挥 C++ 在网络通信上的性能优势一样,MySQL C API 的存在也是为了追求极致的控制力和速度

  1. 最底层的基石: MySQL 数据库服务端本身就是用 C 和 C++ 编写的。官方提供的这个 C API(动态链接库为 libmysqlclient)是所有连接方式中最原生、最底层的一种。

  2. 万物之源: 在实际开发中,很多高级语言操作 MySQL 的库(比如 Python 的 mysqlclient、PHP 的 mysqli 等),其底层源码其实全是对这个 C API 的二次封装。

  3. 性能优势: 在 C++ 后端开发中,如果不使用臃肿的 ORM(对象关系映射)框架,直接调用原生 C API 来发送 SQL 语句和提取数据,是执行效率最高、内存开销最小的方案,非常适合高并发的网关和游戏服务器后台。

二、 MySQL 的底层通信协议与数据格式

跟 WebSocket 有着异曲同工之妙,MySQL 客户端与服务端同样是建立在可靠的 TCP 长连接之上,但它使用的是自己独有的 MySQL 客户端/服务器协议 (MySQL Wire Protocol)。这个协议的交互阶段大致分为两部分:

1.连接与鉴权阶段 (Connection Phase)

  • 服务器主动"打招呼": 与 HTTP/WebSocket 由客户端发起升级请求不同,TCP 连接一建立,MySQL 服务器会先主动向客户端发送一个握手初始包,里面包含数据库版本号和一个随机挑战码(类似于 WebSocket 的 nonce)。

  • 客户端回应: 客户端拿到挑战码后,结合用户密码进行加密哈希计算,连同用户名一起发给服务器。验证通过后,才正式进入指令阶段。

2.命令执行阶段 (Command Phase)

所有发送的数据被封装在报文 (Packet) 中。常见的报文格式如下:

  • 命令包: 客户端向服务器发送的每一个动作都有一个操作码 (Opcode)。例如发送 SQL 语句使用的是 COM_QUERY (值为 0x03)。

  • OK 包: 当你执行 INSERTUPDATEDELETE 等不需要返回具体数据的语句时,服务器会返回一个 OK 包,里面包含"受影响的行数"等状态信息。

  • 结果集包 (Result Set): 当你执行 SELECT 语句时,服务器返回的数据不是简单的字符串,而是经过严格分割的二进制块。它首先发包告诉你"有几列",然后发包描述"每一列叫什么名字",最后才连续发送包含"每一行具体数据"的报文。

三、 MySQL C API 常用核心接口介绍

使用 MySQL C API 就像流水线作业,核心分为:句柄操作、执行 SQL、解析结果

1. 核心数据结构与句柄

  • MYSQL连接句柄 。相当于我们之前用的 websocketpp::connection_hdl。它代表着你和数据库之间的一条物理 TCP 连接通道,所有的状态都存在这里。

  • MYSQL_RES结果集指针。当你执行了查询语句后,服务器返回的那个庞大的"结果集包",就会被存放在这个结构里。

  • MYSQL_ROW单行数据类型 。它本质上是一个字符串数组(char**)。你可以通过数组下标 row[0], row[1] 轻松取出当前这一行的第一列、第二列的值。

2. 连接与生命周期管理接口

  • mysql_init(MYSQL *mysql) :分配或初始化一个 MYSQL 连接对象。

  • mysql_real_connect(...):发起真正的 TCP 连接并完成上面提到的密码鉴权过程。你需要传入主机 IP、用户名、密码、数据库名和端口号(通常是 3306)。

  • mysql_close(MYSQL *mysql):彻底关闭连接,并释放该句柄占用的内部内存。

3. 指令执行接口

mysql_query(MYSQL *mysql, const char *stmt_str) :最核心的"发号施令"函数。把你的 SQL 语句(比如 "SELECT * FROM users")直接传给它。返回 0 代表服务端成功接收并执行;返回非 0 代表 SQL 语句有语法错误或执行失败。

4. 结果集解析接口 (专用于 SELECT)

当你执行完查询后,必须按顺序使用以下接口来"拆快递":

  • mysql_store_result(MYSQL *mysql) :将刚刚查到的所有数据,一次性从服务器网络缓冲区全部拉取到本地内存中,并返回一个 MYSQL_RES 指针。

  • mysql_num_rows(MYSQL_RES *result):快速获取结果集中一共有多少行数据。

  • mysql_num_fields(MYSQL_RES *result):获取数据表一共有多少列(字段)。

  • mysql_fetch_row(MYSQL_RES *result)核心遍历动作 。每次调用这个函数,它就像一个迭代器,会指向结果集的下一行,并返回一个装满该行数据的 MYSQL_ROW 数组。当遍历到最后一行之后,它会返回 NULL

  • mysql_free_result(MYSQL_RES *result)极其重要! 拿到所需数据后,必须调用这个函数把庞大的结果集内存清理掉,否则每次查询都会造成内存泄漏。

四、代码展示

cpp 复制代码
#include <iostream>
#include <string>
#include <mysql/mysql.h> // 引入 MySQL C API 的核心头文件

int main() {
    // ==========================================
    // 步骤一:初始化与建立连接
    // ==========================================
    MYSQL *conn = mysql_init(nullptr); // 初始化连接句柄
    if (conn == nullptr) {
        std::cerr << "MySQL 句柄初始化失败!" << std::endl;
        return 1;
    }

    // 设置连接参数 (请替换为您自己的数据库账号密码)
    const char* host = "127.0.0.1";
    const char* user = "your_username";     // 替换为您的用户名
    const char* password = "your_password"; // 替换为您的密码
    
    // 发起真实的 TCP 连接
    // 参数说明:句柄, IP, 用户名, 密码, 默认数据库(先填NULL), 端口(默认3306), Socket(通常NULL), 标志位(0)
    if (mysql_real_connect(conn, host, user, password, nullptr, 3306, nullptr, 0) == nullptr) {
        std::cerr << "连接数据库失败: " << mysql_error(conn) << std::endl;
        mysql_close(conn);
        return 1;
    }
    std::cout << "成功连接到 MySQL 服务器!\n" << std::endl;

    // 设置通信字符集为 utf8mb4,防止中文存取时变成问号乱码
    mysql_set_character_set(conn, "utf8mb4");

    // ==========================================
    // 步骤二:执行 SQL 指令 (建库、建表、插入)
    // ==========================================
    // 准备我们要执行的 SQL 语句
    std::string sql_create_db = "CREATE DATABASE IF NOT EXISTS chat_system_db;";
    std::string sql_use_db = "USE chat_system_db;";
    std::string sql_create_table = R"(
        CREATE TABLE IF NOT EXISTS messages (
            id INT AUTO_INCREMENT PRIMARY KEY,
            sender VARCHAR(50) NOT NULL,
            content TEXT NOT NULL
        );
    )";
    std::string sql_insert = "INSERT INTO messages (sender, content) VALUES ('Alice', 'Hello C++!'), ('Bob', '这是一个测试。');";

    // 执行建库并切换过去
    mysql_query(conn, sql_create_db.c_str());
    mysql_query(conn, sql_use_db.c_str());

    // 执行建表操作
    if (mysql_query(conn, sql_create_table.c_str()) != 0) {
        std::cerr << "建表失败: " << mysql_error(conn) << std::endl;
    } else {
        std::cout << "数据表 `messages` 准备就绪!" << std::endl;
    }

    // 执行数据插入操作
    if (mysql_query(conn, sql_insert.c_str()) != 0) {
        std::cerr << "插入数据失败: " << mysql_error(conn) << std::endl;
    } else {
        // mysql_affected_rows 可以获取刚才的 INSERT 操作影响了多少行
        std::cout << "成功插入数据,受影响的行数: " << mysql_affected_rows(conn) << std::endl;
    }

    // ==========================================
    // 步骤三:查询数据并解析结果集 (SELECT)
    // ==========================================
    std::string sql_select = "SELECT id, sender, content FROM messages;";
    
    if (mysql_query(conn, sql_select.c_str()) != 0) {
        std::cerr << "查询失败: " << mysql_error(conn) << std::endl;
    } else {
        // 核心:把服务端返回的查询结果拉取到本地内存
        MYSQL_RES *result = mysql_store_result(conn);
        
        if (result == nullptr) {
            std::cerr << "获取结果集失败: " << mysql_error(conn) << std::endl;
        } else {
            int num_fields = mysql_num_fields(result); // 获取有多少列 (id, sender, content 就是 3 列)
            int num_rows = mysql_num_rows(result);     // 获取有多少行数据
            
            std::cout << "\n--- 查询成功,共找到 " << num_rows << " 条聊天记录 ---\n";

            MYSQL_ROW row;
            // 核心循环:每次调用 fetch_row 都会拿出一行数据,直到拿完返回 NULL 为止
            while ((row = mysql_fetch_row(result))) {
                // row[0] 是 id,row[1] 是 sender,row[2] 是 content
                std::cout << "[ID: " << (row[0] ? row[0] : "NULL") << "] ";
                std::cout << (row[1] ? row[1] : "NULL") << " 说: ";
                std::cout << (row[2] ? row[2] : "NULL") << std::endl;
            }
            std::cout << "-------------------------------------------\n";

            // ⚠️ 极其重要:使用完结果集后,必须手动释放内存,否则会导致严重的内存泄漏!
            mysql_free_result(result);
        }
    }

    // ==========================================
    // 步骤四:断开连接,清理资源
    // ==========================================
    mysql_close(conn);
    std::cout << "数据库连接已安全关闭。" << std::endl;

    return 0;
}
相关推荐
奕成则成1 小时前
Redis 大 Key 治理实战:从告警止血到长期优化(含命令与阈值)
数据库·redis·缓存
三更两点1 小时前
AI Agent 的全栈上下文工程:蒸馏、整合、护栏与评估
数据库·人工智能
skiy1 小时前
redis连接服务
数据库·redis·bootstrap
野犬寒鸦2 小时前
从零起步学习AI大模型应用开发 || 第三章:智能体项目实战中的问题与解决方案及思路详解
java·服务器·数据库·人工智能·后端·面试
l1t2 小时前
DeepSeek总结的PostgreSQL 透明列加密
数据库·postgresql
宁小法2 小时前
MySQL - 读写延迟, 并发导致的问题-分析与解决
数据库·mysql·主从延迟·并发请求
岁岁种桃花儿2 小时前
AI超级智能开发系列从入门到上天第九篇:SpringAI搭建本地知识库
数据库·人工智能·ai·llm·智能体
hutengyi2 小时前
Redis基础——1、Linux下安装Redis(超详细)
linux·数据库·redis
風清掦2 小时前
【江科大STM32学习笔记-09】USART串口协议 - 9.2 USART串口数据包
笔记·stm32·单片机·嵌入式硬件·学习