1:C/C++和MySQL的关系

MySQL是客户端.服务端的关系型数据库,C/C++作为客户端语言,通过MySQL提供的Connector/C客户端库与MySQL服务器进行通信。

  • MySQL 服务器:负责数据存储、SQL 解析、事务处理等核心功能,运行在后台服务进程中
  • Connector/C 库:封装了底层 TCP/IP 网络通信协议,提供了一套标准化的 C 语言 API,让 C/C++ 程序无需关心网络细节,直接通过函数调用操作数据库
  • C/C++ 程序:作为客户端,调用 Connector/C 库的 API,向 MySQL 服务器发送 SQL 语句并接收执行结果

为什么用 C/C++ 连接 MySQL?

  • 性能优势:C/C++ 是编译型语言,执行效率远高于 Python、Java 等解释型 / 半编译型语言,适合高性能数据库操作场景
  • 底层控制:可以精确控制内存分配、连接池管理、数据序列化等细节
  • 兼容性:几乎所有平台都支持 C/C++ 和 MySQL,跨平台性好

2:环境准备和库的引入

1:库准备

# Ubuntu/Debian 系统
sudo apt update
sudo apt install libmysqlclient-dev mysql-client

# CentOS/RHEL 系统
sudo yum install mysql-devel mysql

安装后:

  • 头文件路径:/usr/include/mysql/
  • 库文件路径:/usr/lib/x86_64-linux-gnu/(Ubuntu)或 /usr/lib64/mysql/(CentOS)

2:新建一个普通用户

使用 root 用户登录 MySQL,执行以下命令:

-- 创建用户
CREATE USER 'silin'@'localhost' IDENTIFIED BY '******';

-- 授予所有数据库的所有权限(实验环境使用,生产环境需精细化)
GRANT ALL PRIVILEGES ON *.* TO 'silin'@'localhost' WITH GRANT OPTION;

-- 刷新权限使配置生效
FLUSH PRIVILEGES;

-- 验证用户创建成功
SELECT user, host FROM mysql.user WHERE user='silin';

用户名和密码自己设置哈

3:MySQL C API详解

函数 作用 参数说明 返回值 注意事项
mysql_init(MYSQL *mysql) 初始化 MySQL 句柄 mysql:传入 NULL 则自动分配内存 成功返回 MYSQL* 句柄,失败返回 NULL 必须是第一个调用的函数
mysql_real_connect(...) 建立数据库连接 见课件,clientflag 通常传 0 成功返回 MYSQL* 句柄,失败返回 NULL 连接失败用 mysql_error(mysql) 获取错误信息
mysql_set_character_set(...) 设置连接字符集 charset:如 "utf8mb4"(支持 emoji) 成功返回 0,失败返回非 0 必须在连接成功后立即调用,解决中文乱码
mysql_query(...) 执行 SQL 语句 q:SQL 字符串(不需要分号结尾) 成功返回 0,失败返回非 0 不能执行多条 SQL 语句(除非开启多语句支持)
mysql_store_result(...) 获取查询结果集 mysql:连接句柄 成功返回 MYSQL_RES*,失败返回 NULL 必须调用 mysql_free_result() 释放内存
mysql_num_rows(...) 获取结果集行数 res:结果集指针 返回行数 仅对 mysql_store_result() 有效
mysql_num_fields(...) 获取结果集列数 res:结果集指针 返回列数
mysql_fetch_fields(...) 获取列信息 res:结果集指针 返回 MYSQL_FIELD* 数组 包含列名、类型、长度等信息
mysql_fetch_row(...) 获取一行数据 result:结果集指针 成功返回 MYSQL_ROW,失败返回 NULL 循环调用直到返回 NULL 遍历所有行
mysql_affected_rows(...) 获取受影响行数 mysql:连接句柄 返回受影响的行数 用于 INSERT、UPDATE、DELETE 操作
mysql_error(...) 获取错误信息 mysql:连接句柄 返回错误信息字符串 任何 API 失败后都可以调用
mysql_close(...) 关闭数据库连接 sock:连接句柄 无返回值 必须最后调用,释放所有资源

1:初始化与销毁接口

1:mysql_init:初始化MySQL句柄

这是第一个必须调用的函数,所有后续操作都依赖它返回的句柄。

// 函数原型
MYSQL *mysql_init(MYSQL *mysql);

参数详解

  • mysql:传入 NULL 时,函数会自动分配并初始化一个新的 MYSQL 结构体,返回其指针

  • 若传入已存在的 MYSQL 指针,函数会重新初始化该结构体(不推荐,容易内存泄漏)

返回值

  • 成功:返回指向 MYSQL 结构体的指针(后续所有接口的第一个参数都是它)

  • 失败:返回 NULL(内存不足时会失败)

注意事项

  • MYSQL 结构体是 MySQL C API 的核心,内部包含连接信息、错误信息、字符集、状态等所有数据

  • 不要手动修改 MYSQL 结构体的内部成员,所有操作都通过官方 API 完成

  • 初始化失败后无需调用 mysql_close,因为内存还没分配

2:mysql_close:关闭连接并释放资源

最后一个必须调用的函数,释放MySQL句柄的所有关联资源。

// 函数原型
void mysql_close(MYSQL *mysql);

参数详解

  • mysqlmysql_init 返回的有效句柄

注意事项

  • 即使连接失败(mysql_real_connect 返回 NULL),只要 mysql_init 成功,就必须调用 mysql_close

  • 调用后,MYSQL 指针变为野指针,不能再使用

  • 会自动释放未释放的结果集(MYSQL_RES),但建议手动提前释放

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

int main()
{
    MYSQL *mysql =mysql_init(nullptr);
    if(mysql == nullptr)
    {
        std::cerr << "my_sql init failed"<<std::endl;
        return 1;
    }
    std::cout << "my_sql init success"<<std::endl;

    mysql_close(mysql);
    return 0;
}

2:数据库连接接口

1:mysql_real_connect

初始化句柄后,必须调用此函数连接 MySQL 服务器,否则无法执行任何 SQL。

// 函数原型
MYSQL *mysql_real_connect(
    MYSQL *mysql,          // mysql_init返回的句柄
    const char *host,      // MySQL服务器地址:本地用"localhost"或"127.0.0.1",远程用IP
    const char *user,      // 数据库用户名:你的silin
    const char *passwd,    // 数据库密码:你的silin_10086
    const char *db,        // 要连接的数据库名:testdb
    unsigned int port,     // MySQL端口:默认3306
    const char *unix_socket, // Unix域套接字:本地连接用NULL
    unsigned long clientflag // 客户端标志:基础用法传0即可
);

返回值

  • 成功:返回与第一个参数相同的 MYSQL 指针

  • 失败:返回 NULL

关键注意事项

  1. localhost vs 127.0.0.1

    • localhost:使用 Unix 域套接字连接,不经过 TCP/IP,速度更快

    • 127.0.0.1:使用 TCP/IP 连接,和远程连接行为一致

  2. clientflag 常用值

    • CLIENT_MULTI_STATEMENTS:允许执行多条 SQL 语句(用分号分隔)

    • CLIENT_FOUND_ROWS:UPDATE 语句返回匹配的行数,而不是实际修改的行数

  3. 连接失败后必须调用 mysql_error 获取具体错误

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

int main()
{
    MYSQL *mysql = mysql_init(nullptr);
    if (mysql == nullptr)
    {
        std::cerr << "my_sql init failed" << std::endl;
        return 1;
    }

    if (mysql_real_connect(mysql, "localhost", "silin", "*****", "testdb", 3306, nullptr, 0) == nullptr)
    {
        std::cerr << "my_sql connect failed" <<mysql_error(mysql)<< std::endl;
        mysql_close(mysql);
        return 1;
    }
    std::cout << "连接成功!服务器版本:"<< mysql_get_server_info(mysql) << std::endl;
    mysql_close(mysql);
    return 0;
}

2:mysql_set_character_set

解决中文乱码的唯一正确方法,必须在连接成功后立即调用。

// 函数原型
int mysql_set_character_set(MYSQL *mysql, const char *csname);

参数详解

  • csname:字符集名称,推荐使用 utf8mb4(支持 emoji 和所有中文)

  • 不要使用 utf8:MySQL 的utf8是阉割版,最多只支持 3 字节,不包含 emoji 和生僻字

返回值

  • 成功:返回 0

  • 失败:返回非 0

// 连接成功后立即执行
if (mysql_set_character_set(mysql, "utf8mb4") != 0) {
    fprintf(stderr, "设置字符集失败:%s\n", mysql_error(mysql));
    mysql_close(mysql);
    return 1;
}
printf("当前字符集:%s\n", mysql_character_set_name(mysql));

3:SQL执行接口

1:mysql_query

最核心的执行接口,支持所有 DDL(建表、删表)和 DML(增删改查)语句。

// 函数原型
int mysql_query(MYSQL *mysql, const char *sql);

参数详解

  • sql:SQL 语句字符串,不需要以分号结尾

  • 不能执行多条 SQL 语句(除非开启了 CLIENT_MULTI_STATEMENTS 标志)

返回值

  • 成功:返回 0

  • 失败:返回非 0

关键注意事项

  1. SQL 注入风险:直接拼接用户输入的字符串会导致 SQL 注入,生产环境必须使用预处理语句mysql_stmt_* 系列)

  2. 语句长度限制:默认最大支持 1MB 的 SQL 语句,可通过服务器配置修改

  3. 执行结果处理

    • 非查询语句(INSERT/UPDATE/DELETE/CREATE):执行成功后,用 mysql_affected_rows 获取受影响行数

    • 查询语句(SELECT):执行成功后,必须用 mysql_store_resultmysql_use_result 获取结果集

const char *create_sql = "CREATE TABLE IF NOT EXISTS users ("
                         "id INT PRIMARY KEY AUTO_INCREMENT,"
                         "username VARCHAR(50) NOT NULL UNIQUE,"
                         "age INT NOT NULL,"
                         "create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP)";

if (mysql_query(mysql, create_sql) != 0) {
    fprintf(stderr, "建表失败:%s\n", mysql_error(mysql));
    mysql_close(mysql);
    return 1;
}
printf("建表成功\n");

2:mysql_affected_rows

用于 INSERT/UPDATE/DELETE 语句,返回执行后受影响的行数。

// 函数原型
my_ulonglong mysql_affected_rows(MYSQL *mysql);

返回值

  • 大于 0:实际受影响的行数

  • 0:没有匹配的行(UPDATE 时没有行被修改)

  • -1:执行出错

注意事项

  • my_ulonglong 是无符号长整型,打印时必须用 %lu 格式符

  • 对于 INSERT 语句,返回插入的行数;对于 DELETE 语句,返回删除的行数;对于 UPDATE 语句,默认返回实际修改的行数

const char *insert_sql = "INSERT INTO users(username, age) VALUES ('张三', 20)";
if (mysql_query(mysql, insert_sql) != 0) {
    fprintf(stderr, "插入失败:%s\n", mysql_error(mysql));
    mysql_close(mysql);
    return 1;
}
printf("插入成功,受影响行数:%lu\n", mysql_affected_rows(mysql));

4:查询结果集处理接口

这是最容易出错的部分,必须严格按照 "获取结果集→遍历数据→释放结果集" 的顺序执行

1:mysql_store_result

将查询结果全部读取到客户端内存中,是最常用的结果集获取方式。

// 函数原型
MYSQL_RES *mysql_store_result(MYSQL *mysql);

返回值

  • 成功:返回指向 MYSQL_RES 结果集的指针

  • 失败:返回 NULL

关键注意事项

  • 必须调用 mysql_free_result 释放结果集,否则会造成严重的内存泄漏

  • 结果集全部存储在客户端内存中,适合数据量不大的查询

  • 调用后,服务器端的结果集就被清空了

2:mysql_free_result

释放结果集

// 函数原型
void mysql_free_result(MYSQL_RES *result);

注意事项

  • 即使结果集为空(查询没有返回数据),只要 mysql_store_result 成功返回,就必须调用此函数
  • 调用后,MYSQL_RES 指针变为野指针,不能再使用

3:mysql_num_rows

获取结果集行数

// 函数原型
my_ulonglong mysql_num_rows(MYSQL_RES *result);

返回值

  • 结果集中的总行数
  • 注意:只有 mysql_store_result 成功后,此函数才会返回正确值

4:mysql_num_fileds

获取结果集列数

// 函数原型
unsigned int mysql_num_fields(MYSQL_RES *result);

返回值

  • 结果集中的总列数

5:mysql_fetch_fields

获取所有列的信息

返回一个 MYSQL_FIELD 结构体数组,包含每一列的名称、类型、长度等信息。

// 函数原型
MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *result);

MYSQL_FIELD 常用成员

typedef struct st_mysql_field {
    char *name;        // 列名
    char *table;       // 列所属的表名
    enum enum_field_types type; // 列的数据类型
    unsigned int length; // 列的最大长度
    unsigned int flags;  // 列的标志(如NOT NULL、PRIMARY KEY等)
} MYSQL_FIELD;

6:mysql_fetch_row

逐行获取结果数据

循环调用此函数,每次返回一行数据,直到返回 NULL 表示遍历完毕。

// 函数原型
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result);

返回值

  • 成功:返回 MYSQL_ROW 类型的行数据

  • 失败 / 遍历完毕:返回 NULL

关键说明

  • MYSQL_ROW 本质是 char ** 类型,即字符串数组,每个元素对应一列的值

  • 所有数据都以字符串形式返回,即使是数字类型(如 INT),需要自己转换

  • 如果某列的值为 NULL,对应的元素为 NULL

MYSQL_ROW row;
while ((row = mysql_fetch_row(res)) != NULL) {
    // row[0]是id,row[1]是username,row[2]是age,row[3]是create_time
    printf("%s\t%s\t%s\t%s\n", 
           row[0] ? row[0] : "NULL",
           row[1] ? row[1] : "NULL",
           row[2] ? row[2] : "NULL",
           row[3] ? row[3] : "NULL");
}

5:错误接口

1:mysql_error

获取错误信息字符串

// 函数原型
const char *mysql_error(MYSQL *mysql);

返回值

  • 返回最近一次 API 调用失败的错误信息字符串
  • 如果没有错误,返回空字符串

2:mysql_errno

获取错误码

// 函数原型
unsigned int mysql_errno(MYSQL *mysql);

返回值

  • 返回最近一次 API 调用失败的错误码
  • 如果没有错误,返回 0

6:整合接口(C语言全部实现)

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

// 数据库配置
#define DB_HOST "localhost"
#define DB_USER "silin"
#define DB_PASS "*****"
#define DB_NAME "testdb"
#define DB_PORT 3306

int main() {
    // 1. 初始化MySQL句柄
    MYSQL *mysql = mysql_init(NULL);
    if (mysql == NULL) {
        fprintf(stderr, "1. mysql_init 失败:内存不足\n");
        return 1;
    }
    printf("1. mysql_init 成功\n");

    // 2. 建立数据库连接
    if (mysql_real_connect(mysql, DB_HOST, DB_USER, DB_PASS, DB_NAME, DB_PORT, NULL, 0) == NULL) {
        fprintf(stderr, "2. mysql_real_connect 失败:%s\n", mysql_error(mysql));
        mysql_close(mysql);
        return 1;
    }
    printf("2. mysql_real_connect 成功,服务器版本:%s\n", mysql_get_server_info(mysql));

    // 3. 设置字符集为utf8mb4
    if (mysql_set_character_set(mysql, "utf8mb4") != 0) {
        fprintf(stderr, "3. 设置字符集失败:%s\n", mysql_error(mysql));
        mysql_close(mysql);
        return 1;
    }
    printf("3. 设置字符集成功,当前字符集:%s\n", mysql_character_set_name(mysql));

    // 4. 执行建表语句
    const char *create_sql = "CREATE TABLE IF NOT EXISTS users ("
                             "id INT PRIMARY KEY AUTO_INCREMENT,"
                             "username VARCHAR(50) NOT NULL UNIQUE,"
                             "age INT NOT NULL,"
                             "create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP)";
    if (mysql_query(mysql, create_sql) != 0) {
        fprintf(stderr, "4. 建表失败:%s\n", mysql_error(mysql));
        mysql_close(mysql);
        return 1;
    }
    printf("4. 建表成功\n");

    // 5. 插入数据
    const char *insert_sql = "INSERT INTO users(username, age) VALUES ('张三', 20), ('李四', 19), ('王五', 21)";
    if (mysql_query(mysql, insert_sql) != 0) {
        fprintf(stderr, "5. 插入数据失败:%s\n", mysql_error(mysql));
        mysql_close(mysql);
        return 1;
    }
    printf("5. 插入数据成功,受影响行数:%lu\n", mysql_affected_rows(mysql));

    // 6. 查询数据
    const char *select_sql = "SELECT * FROM users";
    if (mysql_query(mysql, select_sql) != 0) {
        fprintf(stderr, "6. 查询数据失败:%s\n", mysql_error(mysql));
        mysql_close(mysql);
        return 1;
    }
    printf("6. 查询数据成功\n");

    // 7. 获取结果集
    MYSQL_RES *res = mysql_store_result(mysql);
    if (res == NULL) {
        fprintf(stderr, "7. 获取结果集失败:%s\n", mysql_error(mysql));
        mysql_close(mysql);
        return 1;
    }
    printf("7. 获取结果集成功\n");

    // 8. 获取结果集信息
    my_ulonglong rows = mysql_num_rows(res);
    unsigned int fields = mysql_num_fields(res);
    printf("8. 结果集信息:总行数=%lu,总列数=%u\n", rows, fields);

    // 9. 打印列名
    MYSQL_FIELD *field = mysql_fetch_fields(res);
    printf("\n9. 列名:");
    for (unsigned int i = 0; i < fields; i++) {
        printf("%-15s", field[i].name);
    }
    printf("\n----------------------------------------\n");

    // 10. 遍历结果集
    printf("10. 数据内容:\n");
    MYSQL_ROW row;
    while ((row = mysql_fetch_row(res)) != NULL) {
        for (unsigned int i = 0; i < fields; i++) {
            printf("%-15s", row[i] ? row[i] : "NULL");
        }
        printf("\n");
    }

    // 11. 释放结果集
    mysql_free_result(res);
    printf("\n11. 结果集已释放\n");

    // 12. 关闭数据库连接
    mysql_close(mysql);
    printf("12. 数据库连接已关闭\n");

    return 0;
}

更多推荐