23、C语言与MySQL基础:MySQL C API简介、mysql_real_connect、mysql_query、mysql_store_result

说实话,很多嵌入式工程师一听到“数据库”三个字就头大。觉得那是后端开发的事,跟底层C语言不沾边。我以前也这么想,直到有一次做设备数据采集项目,客户要求把传感器数据存到远程MySQL里做分析。嗯,那会儿我才发现——C语言操作MySQL,其实没那么玄乎。

今天我们就来聊聊MySQL C API。说白了,就是MySQL官方提供的一套C语言函数库。你写C代码,调这些函数,就能连上MySQL服务器,发SQL语句,拿结果。就这么简单。

MySQL C API 是什么?

MySQL C API 是一组C语言函数,封装在 libmysqlclient 这个库里。你编译程序时链接它,就能在C代码里操作MySQL数据库。

我个人习惯把它的工作流程总结成四步:

  1. 初始化:分配一个连接句柄
  2. 连接:用 mysql_real_connect 连上数据库
  3. 查询:用 mysql_query 发送SQL语句
  4. 取结果:用 mysql_store_result 拿到查询结果

你看,是不是很清晰?

核心要点:MySQL C API 本质上就是一组函数调用。你不需要懂数据库内核,只要会调函数、会处理返回值就行。

下面这张图,是我画的工作流程。你一看就明白。

MySQL C API 核心流程 1. 初始化 mysql_init() 2. 连接 mysql_real_connect() 3. 查询 mysql_query() 4. 取结果 mysql_store_result() 可多次查询 最后记得 mysql_close() 释放连接

mysql_init:第一步,拿个句柄

所有操作之前,你得先调用 mysql_init()。它返回一个 MYSQL * 指针。这个指针就是你的“数据库遥控器”。

MYSQL *conn = mysql_init(NULL);
if (conn == NULL) {
    fprintf(stderr, "mysql_init 失败\n");
    return -1;
}

这里有个坑。我曾经在项目里忘了检查返回值,结果后面 mysql_real_connect 直接段错误。排查了半天才发现是初始化就没成功。所以,每次调用API后,一定要检查返回值

mysql_real_connect:连上服务器

拿到句柄后,就该连服务器了。函数原型长这样:

MYSQL *mysql_real_connect(
    MYSQL *conn,          // 初始化后的句柄
    const char *host,     // 主机地址,如 "127.0.0.1"
    const char *user,     // 用户名
    const char *passwd,   // 密码
    const char *db,       // 数据库名
    unsigned int port,    // 端口,默认3306
    const char *unix_socket, // 套接字,一般传NULL
    unsigned long clientflag // 标志位,一般传0
);

举个例子:

MYSQL *conn = mysql_init(NULL);
if (conn == NULL) {
    fprintf(stderr, "初始化失败\n");
    return -1;
}

conn = mysql_real_connect(conn, "127.0.0.1", "root", "mypass", 
                          "testdb", 3306, NULL, 0);
if (conn == NULL) {
    fprintf(stderr, "连接失败: %s\n", mysql_error(conn));
    return -1;
}
printf("连接成功!\n");

小技巧:连接失败时,用 mysql_error(conn) 打印错误信息。它能告诉你具体是网络不通、密码错误还是数据库不存在。我在调试时全靠它定位问题。

mysql_query:发SQL语句

连接成功后,就可以用 mysql_query() 发送SQL了。它只接受一个参数——SQL字符串。

if (mysql_query(conn, "SELECT * FROM sensors")) {
    fprintf(stderr, "查询失败: %s\n", mysql_error(conn));
    return -1;
}

注意,mysql_query 不支持多条SQL语句一起发。你一次只能发一条。如果你需要批量操作,得循环调用。

警告:千万不要把用户输入直接拼接到SQL字符串里。比如 sprintf(query, "SELECT * FROM users WHERE name='%s'", user_input)。这会导致SQL注入攻击。我见过一个项目因此被删了整张表。正确的做法是用预处理语句(后面章节会讲)。

mysql_store_result:把结果拿回来

查询执行后,结果还在MySQL服务器上。你得用 mysql_store_result() 把它取到客户端内存里。

MYSQL_RES *result = mysql_store_result(conn);
if (result == NULL) {
    fprintf(stderr, "取结果失败: %s\n", mysql_error(conn));
    return -1;
}

取回来后,你可以用 mysql_num_fields() 获取列数,用 mysql_fetch_row() 一行一行地读数据。

int num_fields = mysql_num_fields(result);
MYSQL_ROW row;

while ((row = mysql_fetch_row(result))) {
    for (int i = 0; i < num_fields; i++) {
        printf("%s\t", row[i] ? row[i] : "NULL");
    }
    printf("\n");
}

读完别忘了释放结果集:

mysql_free_result(result);

完整示例:从连接到查询

下面是一个完整的例子。它连接数据库,查询一张叫 sensors 的表,打印所有数据。

#include <stdio.h>
#include <mysql/mysql.h>

int main() {
    MYSQL *conn;
    MYSQL_RES *result;
    MYSQL_ROW row;
    int num_fields;

    // 1. 初始化
    conn = mysql_init(NULL);
    if (conn == NULL) {
        fprintf(stderr, "初始化失败\n");
        return -1;
    }

    // 2. 连接
    conn = mysql_real_connect(conn, "127.0.0.1", "root", 
                              "mypass", "testdb", 3306, NULL, 0);
    if (conn == NULL) {
        fprintf(stderr, "连接失败: %s\n", mysql_error(conn));
        mysql_close(conn);
        return -1;
    }

    // 3. 查询
    if (mysql_query(conn, "SELECT id, name, value FROM sensors")) {
        fprintf(stderr, "查询失败: %s\n", mysql_error(conn));
        mysql_close(conn);
        return -1;
    }

    // 4. 取结果
    result = mysql_store_result(conn);
    if (result == NULL) {
        fprintf(stderr, "取结果失败: %s\n", mysql_error(conn));
        mysql_close(conn);
        return -1;
    }

    num_fields = mysql_num_fields(result);
    while ((row = mysql_fetch_row(result))) {
        for (int i = 0; i < num_fields; i++) {
            printf("%s\t", row[i] ? row[i] : "NULL");
        }
        printf("\n");
    }

    // 5. 清理
    mysql_free_result(result);
    mysql_close(conn);

    return 0;
}

常见错误与避坑

我总结了几条实战中容易踩的坑:

  • 忘记链接库:编译时加 -lmysqlclient。我刚开始总是忘记,报一堆未定义引用错误。
  • 忘记检查返回值:每个API调用都要检查。别偷懒。
  • 忘记释放结果集mysql_store_result 分配的内存,必须用 mysql_free_result 释放。否则内存泄漏。
  • 连接超时:默认8小时无活动会自动断开。长连接项目要注意重连机制。

一句话总结:MySQL C API 就是 init → connect → query → store_result → free → close。记住这个流程,你就能在C语言里操作数据库了。

好了,今天就聊到这儿。下一节我们会深入 mysql_fetch_rowmysql_num_fields 的细节,以及如何处理更复杂的查询结果。你先把今天这几个函数练熟,后面就好办了。


公众号:蓝海资料掘金营,微信deep3321