MySQL数据库:C语言连接MySQL教程

文章目录

新手友好|C语言连接MySQL保姆级教程:从环境搭建到底层原理全覆盖

哈喽各位码农小伙伴!这次咱们把PDF里所有知识点连锅端------从目录结构到底层函数指针,从环境踩坑到事务控制,给你扒得明明白白。既能当入门教程,也能当手册查,保证所有考点全覆盖。


一、战前准备:你需要的两套装备

在写代码之前,先把硬件软件备齐,缺一样都跑不起来。

1. 一台正常运行的MySQL服务

这个是前提,本地装一个或者远程有一个都行,确保账号密码、端口、库名都是对的,先用命令行或者客户端工具测通了再往下走。毕竟C语言调试起来不如高级语言方便,先把后端问题排除干净。

2. 官方SDK:MySQL Connector/C

这玩意儿就是MySQL官方给C语言量身定做的开发包,也常被叫 libmysqlclient。注意不是Connector/C++,也不是ODBC驱动 ,就找纯C语言的版本。可以去MySQL官网下载对应平台的包,Linux也可以直接用包管理器装mysql-devel。

库目录结构全拆解

下载解压完长这样,咱们先把每个目录是干啥的摸清楚,别到时候编译头文件都找不到:

复制代码
.
├── include  # 【头文件区】所有函数声明、结构体定义全在这
│   ├── big_endian.h、byte_order_generic.h  # 字节序兼容
│   ├── decimal.h、errmsg.h、my_alloc.h     # 基础工具组件
│   ├── mysql/                             # MySQL核心头文件目录
│   │   ├── mysql.h        ✅ 主头文件,写代码必包含
│   │   ├── client_authentication.h        # 客户端认证
│   │   ├── mysql_socket.h、mysql_ps.h     # 网络与协议层
│   │   ├── mysql_statement.h、mysql_transaction.h  # 语句与事务
│   │   └── ... 一堆内部头文件
│   ├── mysql_com.h                        # 通信协议定义
│   └── sql_common.h、sql_state.h          # SQL通用与状态码
└── lib      # 【库文件区】编译链接用,真正的实现代码
    ├── libmysqlclient.a          # 静态库,编译进程序里
    ├── libmysqlclient_r.a        # 线程安全版静态库
    ├── libmysqlclient.so         # 动态库,运行时加载
    ├── libmysqlclient_r.so       # 线程安全版动态库
    └── libmysqlclient.so.18、libmysqlclient.so.18.3.0  # 版本号软链接

一句话总结:include是说明书(函数声明),lib是干活的工具箱(代码实现)。头文件告诉你函数怎么调用、参数是什么,库文件里才是真正执行的二进制代码。


二、环境验证:先跑通Hello World

先别急着写CRUD,咱们用最少的代码验证库能不能正常引入,这步过不了后面全白搭。

1. 最简测试代码

新建test.c,就干一件事:打印客户端版本号

复制代码
#include <stdio.h>
#include <mysql.h> 

int main()
{
    printf("mysql client Version: %s\n", mysql_get_client_info());
    return 0;
}

mysql_get_client_info() 就是API给我们留的第一个入口,直接返回客户端库的版本字符串。

2. 编译命令详解

Linux下用gcc编译,记住这三个参数黄金搭档:

复制代码
gcc -o test test.c -I./include -L./lib -lmysqlclient
  • -I./include:告诉编译器「头文件在./include目录里」,去找mysql.h
  • -L./lib:告诉链接器「库文件在./lib目录里」,去找libmysqlclient
  • -lmysqlclient:指定要链接的库名,注意要去掉前缀lib和后缀.so

3. 经典踩坑:动态库找不到怎么办?

编译成功了,一运行直接给你报个错:

复制代码
./test: error while loading shared libraries: libmysqlclient.so.18: cannot open shared object file: No such file or directory

别慌,这是Linux动态库的经典问题。你可以用 ldd test 命令查看程序依赖的动态库,会看到libmysqlclient.so.18显示not found。

原因 :编译时我们用-L告诉了链接器库的位置,但运行时系统默认只去/usr/lib、/usr/local/lib这些系统目录找,不知道你库放在当前目录。

三种解决方案:

  1. 临时方案(当前终端有效) ```
    export LD_LIBRARY_PATH=./lib

    手动给系统追加一个动态库搜索路径。
    2. 永久方案(推荐)
    把库路径写到/etc/ld.so.conf文件里,然后执行sudo ldconfig刷新缓存。
    3. 简单粗暴方案
    把libmysqlclient.so*所有文件直接拷贝到/usr/lib目录下。

    搞定之后再运行,就能看到版本输出了:

mysql client Version: 6.1.6

复制代码
到这一步,恭喜你,环境已经完全打通!

---

## 三、底层原理:两个核心结构体

在讲API之前,先把两个最核心的结构体讲透,这是理解整个C API的关键。很多人用了很久都不知道里面是什么,其实PDF里早就暗示了。

### 1. MYSQL:连接的「大管家」

`MYSQL` 是整个API的核心句柄,后面几乎所有函数都要传它。你可以把它理解成一个超级大管家,里面存了和连接相关的所有信息:

- 基本信息:主机地址、端口、用户名、数据库名、字符集
- 连接状态:是否连上、错误码、错误信息
- **核心成员:`st_mysql_methods` 函数指针表**

这里就是PDF里提到的关键点:这个结构体里存了一堆函数指针,比如`read_rows`、`write_row`之类的。**连接建立成功之前,这些指针都是空的;当`mysql_real_connect`连接成功后,会根据你的连接方式(TCP/IP还是Unix套接字),把对应的实现函数赋值给这些指针。**

为什么这么设计?这就是C语言里的「多态」啊!一套接口,多种实现。不管你是用TCP连远程,还是用套接字连本地,上层调用的API都是一样的,底层自动切换实现。后面要讲的`mysql_store_result`,内部就是调用这个函数指针表里的`read_rows`来拉数据的。

### 2. MYSQL_RES:结果集的「容器」

当你执行查询语句后,所有数据都会装在`MYSQL_RES`结构体里返回给你。

- 它是**全量加载**的:执行`mysql_store_result`的时候,会把所有查询结果一次性从服务器拉到客户端内存里
- 它是**堆上分配**的:内部调用`malloc`申请内存,所以用完必须释放,而且不能用系统的`free`,必须用库提供的`mysql_free_result`,不然内存泄漏没跑儿

---

## 四、完整操作流水线:API逐详解

C语言操作MySQL是严格的流水线,顺序不能乱:
**初始化 → 建立连接 → 设置字符集 → 执行SQL → 获取结果集 → 读取数据 → 释放结果集 → 关闭连接**

下面咱们按顺序逐个拆解,每个函数都讲透。

### 4.1 初始化句柄:mysql_init

MYSQL *mysql_init(MYSQL *mysql);

复制代码
- **作用**:创建并初始化一个MYSQL连接对象。
- **参数**:新手直接传`NULL`就行,库会自动帮你分配内存并返回指针。如果你自己定义了MYSQL变量,也可以传地址进去初始化。
- **返回值**:成功返回MYSQL指针,失败返回NULL。
- **示例**:```
MYSQL *conn = mysql_init(NULL);
if (conn == NULL) {
    printf("初始化失败!\n");
    return -1;
}

4.2 建立连接:mysql_real_connect

初始化完只是个空壳,这一步才是真正通过TCP/IP网络连上数据库服务器。

复制代码
MYSQL *mysql_real_connect(
    MYSQL *mysql,       // 刚才初始化的连接句柄
    const char *host,   // 数据库主机地址,本地用"127.0.0.1"
    const char *user,   // 数据库用户名
    const char *passwd, // 数据库密码
    const char *db,     // 要连接的数据库名
    unsigned int port,  // 端口号,默认3306
    const char *unix_socket, // Unix套接字,本地连接可用,传NULL就行
    unsigned long clientflag // 客户端选项标志,新手传0
);
  • 返回值:成功返回传入的mysql指针,失败返回NULL。
  • 底层细节 :连接成功后,st_mysql_methods里的函数指针就都被赋值了,后面的数据操作全靠它们。
必做操作:解决中文乱码

连接成功后第一件事,设置字符集为utf8!

复制代码
mysql_set_character_set(conn, "utf8");

MySQL客户端默认字符集是latin1,不设置的话,查中文全是问号乱码,踩过的人都懂。

4.3 执行SQL:mysql_query

连接建立好之后,就可以给服务器发SQL指令了。

复制代码
int mysql_query(MYSQL *mysql, const char *sql);
  • 作用:执行一条SQL语句。

  • 参数 :第二个参数就是SQL字符串,注意末尾不要加分号; ,也不要加\g。

  • 返回值 :0 表示执行成功,非0表示执行失败。

  • 调试技巧 :失败了可以用mysql_error(conn)打印具体错误原因。

    int ret = mysql_query(conn, "select * from student");
    if (ret != 0) {
    printf("SQL执行失败:%s\n", mysql_error(conn));
    return -1;
    }

4.4 获取结果集:mysql_store_result

如果执行的是select、show这类查询语句,执行成功后要把结果取出来。

复制代码
MYSQL_RES *mysql_store_result(MYSQL *mysql);
  • 作用 :调用st_mysql_methods里的read_rows函数指针,把所有查询结果一次性拉到客户端内存,封装成MYSQL_RES返回。
  • 返回值:成功返回结果集指针,失败返回NULL。
  • ⚠️ 重要提醒 :这个函数内部会申请堆内存,用完必须调用mysql_free_result()释放 ,绝对不能直接用free(),会造成内存泄漏或者程序崩溃。

4.5 结果集元信息

拿到结果集后,可以先获取行数、列数、列名这些元数据。

获取结果行数
复制代码
my_ulonglong mysql_num_rows(MYSQL_RES *res);

返回结果集中有多少条记录,注意类型是my_ulonglong,也就是无符号长整型,printf要用%llu格式化。

获取结果列数
复制代码
unsigned int mysql_num_fields(MYSQL_RES *res);

返回结果集中有多少个字段(列)。

获取列名(字段信息)
复制代码
MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *res);

返回一个MYSQL_FIELD结构体数组,每个元素对应一列的信息,包括字段名name、字段类型type、字段长度等。最常用的就是拿列名打印表头:

复制代码
int fields = mysql_num_fields(res);
MYSQL_FIELD *field = mysql_fetch_fields(res);
for(int i = 0; i < fields; i++){
    printf("%s\t", field[i].name);
}
printf("\n");

4.6 逐行读取数据:mysql_fetch_row

这是最常用的函数,用来一行一行读取结果集中的数据。

复制代码
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result);
  • MYSQL_ROW 是什么? 本质就是 char **,也就是字符串数组。你可以把它当成二维数组用,每个元素对应一列的值。

  • 划重点 :不管数据库里存的是int、float、date还是text,取出来全是字符串! 要转数字自己用atoi()、atof()转换。

  • 用法:循环调用,每调用一次返回下一行,全部读完返回NULL。

    MYSQL_ROW line;
    int i = 0;
    while( (line = mysql_fetch_row(res)) != NULL ){
    int j = 0;
    for(; j < fields; j++){
    printf("%s\t", line[j] ? line[j] : "NULL");
    }
    printf("\n");
    }

小细节:如果字段值是NULL,取出来就是空指针,直接打印会段错误,所以要判断一下。

4.7 收尾工作:释放与关闭

用完一定要记得打扫战场,顺序别搞反:先释放结果集,再关闭连接。

复制代码
mysql_free_result(res);  // 释放结果集内存
mysql_close(conn);       // 关闭数据库连接,释放连接句柄

五、进阶能力:事务控制

MySQL C API原生支持事务操作,三个函数就够了,原型如下:

复制代码
my_bool mysql_autocommit(MYSQL *mysql, my_bool auto_mode);  // 设置自动提交
my_bool mysql_commit(MYSQL *mysql);                        // 提交事务
my_bool mysql_rollback(MYSQL *mysql);                      // 回滚事务
  • 要使用手动事务,先关闭自动提交:mysql_autocommit(conn, 0)
  • 一系列SQL执行完,没问题就mysql_commit(conn)提交
  • 中间出错了就mysql_rollback(conn)回滚

六、完整可运行示例代码

下面给大家一个完整的、带错误处理的查询示例,直接复制改配置就能跑:

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <mysql.h>

int main()
{
    // 1. 初始化连接句柄
    MYSQL *conn = mysql_init(NULL);
    if (conn == NULL) {
        printf("MySQL初始化失败\n");
        return -1;
    }

    // 2. 建立数据库连接
    if (mysql_real_connect(
            conn,
            "127.0.0.1",  // 主机地址
            "root",       // 用户名
            "123456",     // 密码
            "test_db",    // 数据库名
            3306,         // 端口
            NULL,         // unix_socket
            0             // clientflag
        ) == NULL)
    {
        printf("连接失败:%s\n", mysql_error(conn));
        mysql_close(conn);
        return -1;
    }

    // 3. 设置字符集,防止中文乱码
    mysql_set_character_set(conn, "utf8");
    printf("数据库连接成功!\n\n");

    // 4. 执行查询SQL
    const char *sql = "select id, name, age from student";
    if (mysql_query(conn, sql) != 0) {
        printf("SQL执行失败:%s\n", mysql_error(conn));
        mysql_close(conn);
        return -1;
    }

    // 5. 获取结果集
    MYSQL_RES *res = mysql_store_result(conn);
    if (res == NULL) {
        printf("获取结果集失败:%s\n", mysql_error(conn));
        mysql_close(conn);
        return -1;
    }

    // 6. 打印表头
    unsigned int col_count = mysql_num_fields(res);
    MYSQL_FIELD *fields = mysql_fetch_fields(res);
    for (unsigned int i = 0; i < col_count; i++) {
        printf("%-10s", fields[i].name);
    }
    printf("\n--------------------------\n");

    // 7. 逐行打印数据
    MYSQL_ROW row;
    while ((row = mysql_fetch_row(res)) != NULL) {
        for (unsigned int i = 0; i < col_count; i++) {
            printf("%-10s", row[i] ? row[i] : "NULL");
        }
        printf("\n");
    }

    // 8. 打印统计信息并释放资源
    printf("\n共 %llu 条记录\n", mysql_num_rows(res));
    mysql_free_result(res);
    mysql_close(conn);

    printf("连接已关闭\n");
    return 0;
}

七、新手避坑指南(血泪总结)

1. 用free()释放结果集

MYSQL_RES是库内部分配的复杂结构体,不是简单的malloc,必须用mysql_free_result()释放,直接free必炸。

2. 中文乱码全是问号

连接成功后立刻调用mysql_set_character_set(conn, "utf8"),别等查完数据才想起来。默认字符集是latin1,不支持中文。

3. 编译过了运行找不到库

LD_LIBRARY_PATH这个坑踩过的人能绕地球三圈。记住:-L是管编译时的,运行时系统不认,必须配置动态库路径。

4. SQL语句末尾加分号

很多新手习惯性写"select * from t;",虽然部分版本兼容,但规范是不加,遇到复杂SQL或者多语句执行容易出问题。

5. 不判断返回值一路裸奔

每个API都可能失败,一定要判断返回值,配合mysql_error()打印错误,不然出问题根本不知道哪错了。

6. 以为MYSQL_ROW里存的是数字

所有字段取出来都是字符串!int要自己atoi,float要自己atof,别直接当数字用,不然会出奇怪的bug。


结语

用C语言操作MySQL,步骤确实比Java、Python多,既要管编译链接,又要管内存释放,还要处理各种底层细节。但它也有不可替代的优势:没有额外封装、性能损耗极低、内存完全可控,在嵌入式开发、高性能网关、数据库代理这些追求极致性能的场景里,依然是刚需。

这篇把PDF里所有知识点------从目录结构到函数指针,从基础API到事务控制------全部覆盖了。掌握了这些,增删改查都不在话下,剩下的就是多写多练,踩坑踩多了自然就熟了

相关推荐
IvorySQL14 小时前
PostgreSQL 日报|修复截断 zstd 备份检测(10 月 9 日)
数据库·postgresql
刘胡子大叔14 小时前
SQL 脚本的导入顺序
数据库·sql
代码什么用15 小时前
Spring对IoC的实现
数据库·spring
沐晓时光15 小时前
C语言入门,深入理解指针(3)
c语言·开发语言
SuperByteMaster16 小时前
wfi进入之前没有清除basepri, 导致中断优先级不够,wfi 退不出来
c语言
hz5678917 小时前
涉密视频会议设备配置指南:终端、音视频采集与配套设施选型
服务器·网络·数据库·安全·实时音视频·信息与通信·智能硬件
广州浮点FLOATLIC17 小时前
许可证服务器迁移后软件打不开:研发 IT 怎样定位连接问题
linux·服务器·数据库
程序员Sunday17 小时前
MySQL 为什么使用 B+ 树索引?把范围查询、回表和覆盖索引连起来
数据库·mysql
Navigator_Z17 小时前
LeetCode //C - 1286. Iterator for Combination
c语言·算法·leetcode
半杯咖啡半行码17 小时前
Qt开发实战:数据库、MV 模式、QProcess与串口通信全攻略
数据库·qt