MySQL C语言连接 - 从入门到实战

1. 📚 前言

面试官:"说说在C/C++项目中,你是怎么连接和操作MySQL数据库的?"

在学习了MySQL的基础SQL语法之后,我们往往需要在实际的C/C++项目中将数据库操作集成进来。MySQL官方提供了C语言的客户端库(Connector/C),让我们能够直接在代码中连接数据库、执行SQL语句并处理结果集。

本文将基于MySQL官方提供的C接口库,从环境准备、库的引入、核心API详解到完整的代码示例,带你彻底掌握MySQL的C语言连接。文章末尾还准备了经典的面试题与解答,助你面试无忧。

2. 🛠️ 准备工作

2.1 确保MySQL服务有效

在开始编码之前,首先要确保你的MySQL服务已经正确安装并正在运行。可以通过以下命令检查:

bash 复制代码
# 检查MySQL服务状态
systemctl status mysqld
# 或
service mysql status

# 登录MySQL测试
mysql -u root -p

2.2 下载MySQL Connector/C库

MySQL官方提供了C语言的连接库,你可以从MySQL官网下载对应平台的Connector/C(或libmysqlclient)。下载后的库文件目录结构如下:

bash 复制代码
[JL@MiWiFi-R1CL-srv lib]$ tree .
.
├── include
│   ├── big_endian.h
│   ├── byte_order_generic.h
│   ├── byte_order_generic_x86.h
│   ├── decimal.h
│   ├── errmsg.h
│   ├── keycache.h
│   ├── little_endian.h
│   ├── m_ctype.h
│   ├── m_string.h
│   ├── my_alloc.h
│   ├── my_byteorder.h
│   ├── my_compiler.h
│   ├── my_config.h
│   ├── my_dbug.h
│   ├── my_dir.h
│   ├── my_getopt.h
│   ├── my_global.h
│   ├── my_list.h
│   ├── my_pthread.h
│   ├── mysql
│   │   ├── client_authentication.h
│   │   ├── client_plugin.h
│   │   ├── client_plugin.h.pp
│   │   ├── get_password.h
│   │   ├── plugin_auth_common.h
│   │   ├── plugin_trace.h
│   │   ├── psi
│   │   │   ├── mysql_file.h
│   │   │   ├── mysql_idle.h
│   │   │   ├── mysql_mdl.h
│   │   │   ├── mysql_memory.h
│   │   │   ├── mysql_ps.h
│   │   │   ├── mysql_socket.h
│   │   │   ├── mysql_sp.h
│   │   │   ├── mysql_stage.h
│   │   │   ├── mysql_statement.h
│   │   │   ├── mysql_table.h
│   │   │   ├── mysql_thread.h
│   │   │   ├── mysql_transaction.h
│   │   │   ├── psi_base.h
│   │   │   ├── psi.h
│   │   │   ├── psi_base.h
│   │   │   ├── psi.h
│   │   │   └── psi_memory.h
│   │   ├── service_my_snprintf.h
│   │   └── service_mysql_alloc.h
│   ├── mysql_com.h
│   ├── mysql_com_server.h
│   ├── mysqld_ername.h
│   ├── mysqld_error.h
│   ├── mysql_embed.h
│   ├── mysql.h
│   ├── mysql_time.h
│   ├── mysql_version.h
│   ├── my_sys.h
│   ├── my_xml.h
│   ├── sql_common.h
│   ├── sql_state.h
│   ├── sslopt-case.h
│   ├── sslopt-longopts.h
│   ├── sslopt-vars.h
│   └── typelib.h
└── lib
    ├── libmysqlclient.a
    ├── libmysqlclient_r.a -> libmysqlclient.a
    ├── libmysqlclient_r.so -> libmysqlclient.so
    ├── libmysqlclient_r.so.18 -> libmysqlclient.so.18
    ├── libmysqlclient_r.so.18.3.0 -> libmysqlclient.so.18.3.0
    ├── libmysqlclient.so -> libmysqlclient.so.18
    ├── libmysqlclient.so.18 -> libmysqlclient.so.18.3.0
    └── libmysqlclient.so.18.3.0

💡 说明include目录包含了所有函数声明(头文件),lib目录包含了所有函数实现(静态库.a和动态库.so)。

2.3 在MySQL中创建测试用户和数据库

为了后续的代码测试,我们需要在MySQL中创建一个专用的用户和数据库:

sql 复制代码
-- 1. 创建一个可以在C/C++中访问的账户
CREATE USER 'cpp_user'@'localhost' IDENTIFIED BY 'your_password';

-- 2. 创建一个新数据库
CREATE DATABASE IF NOT EXISTS cpp_test_db;

-- 3. 为该用户授予权限
GRANT ALL ON cpp_test_db.* TO 'cpp_user'@'localhost';

-- 4. 刷新权限
FLUSH PRIVILEGES;

3. 🔗 尝试链接MySQL客户端库

3.1 编写第一个测试程序

创建一个test.c文件,使用mysql_get_client_info()函数验证库的引入是否成功:

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

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

3.2 编译与运行

bash 复制代码
# 编译:-I指定头文件路径,-L指定库文件路径,-lmysqlclient链接MySQL客户端库
[JL@MiWiFi-R1CL-srv lib]$ gcc -o test test.c -I./include -L./lib -lmysqlclient

# 查看生成的文件
[JL@MiWiFi-R1CL-srv lib]$ ls
include  lib  test  test.c

# 直接运行(可能会报错)
[JL@MiWiFi-R1CL-srv lib]$ ./test
./test: error while loading shared libraries: libmysqlclient.so.18: cannot open 
shared object file: No such file or directory

⚠️ 常见错误 :运行时提示找不到动态库,这是因为系统默认的动态库搜索路径中没有包含我们的lib目录。

3.3 解决动态库加载问题

使用export LD_LIBRARY_PATH临时指定动态库查找路径:

bash 复制代码
# 设置环境变量,告诉系统去哪里找动态库
[JL@MiWiFi-R1CL-srv lib]$ export LD_LIBRARY_PATH=./lib

# 再次运行,成功!
[JL@MiWiFi-R1CL-srv lib]$ ./test
mysql client Version: 6.1.6

💡 小知识 :可以使用ldd命令查看可执行文件依赖的动态库:

bash 复制代码
ldd ./test

至此,库的引入工作已经完成,接下来我们开始熟悉核心API接口。

4. 🧩 MySQL C接口核心API详解

4.1 初始化:mysql_init()

要使用MySQL C库,必须先进行初始化!

c 复制代码
MYSQL *mysql_init(MYSQL *mysql);
  • 参数 :通常传入NULL,函数会内部自动分配一个MYSQL对象。

  • 返回值 :指向初始化后的MYSQL结构体的指针。

  • 示例

    c 复制代码
    MYSQL *mfp = mysql_init(NULL);
    if (mfp == NULL) {
        fprintf(stderr, "mysql_init failed!\n");
        return -1;
    }

🔍 深入理解MYSQL是C API中一个非常重要的结构体变量,里面包含了非常丰富的信息,如portdbnamecharset等连接基本参数。它还包含了一个名为st_mysql_methods的结构体变量,该变量里面保存着很多函数指针,这些函数指针将会在数据库连接成功以后的各种数据操作中被调用。

4.2 连接数据库:mysql_real_connect()

初始化完毕之后,必须先连接数据库,才能进行后续操作(MySQL网络部分是基于TCP/IP的)。

c 复制代码
MYSQL *mysql_real_connect(MYSQL *mysql, 
                          const char *host,       // 主机地址,如"localhost"或"127.0.0.1"
                          const char *user,       // 用户名
                          const char *passwd,     // 密码
                          const char *db,         // 数据库名
                          unsigned int port,      // 端口号,MySQL默认为3306
                          const char *unix_socket,// Unix套接字,通常传NULL
                          unsigned long clientflag); // 客户端标志,通常传0
  • 参数说明:各参数基本顾名思意。

  • 返回值:成功返回指向该连接的指针,失败返回NULL。

  • 示例

    c 复制代码
    MYSQL *mfp = mysql_init(NULL);
    MYSQL *conn = mysql_real_connect(mfp, "localhost", "cpp_user", 
                                     "your_password", "cpp_test_db", 
                                     3306, NULL, 0);
    if (conn == NULL) {
        fprintf(stderr, "mysql_real_connect failed: %s\n", mysql_error(mfp));
        return -1;
    }

⚠️ 中文乱码问题 :建立好连接之后,获取英文没有问题,但如果获取中文可能会出现乱码。这是因为MySQL连接的默认字符集是latin1,我们需要将其设置为utf8

c 复制代码
mysql_set_character_set(mfp, "utf8");

4.3 执行SQL语句:mysql_query()

连接成功后,就可以下发SQL命令了。

c 复制代码
int mysql_query(MYSQL *mysql, const char *q);
  • 参数

    • mysqlmysql_real_connect返回的连接句柄。
    • q:要执行的SQL语句字符串,如"SELECT * FROM table"
  • 返回值:成功返回0,失败返回非0。

  • 示例

    c 复制代码
    if (mysql_query(mfp, "SELECT * FROM users") != 0) {
        fprintf(stderr, "mysql_query failed: %s\n", mysql_error(mfp));
        return -1;
    }

4.4 获取查询结果:mysql_store_result()

SQL执行完以后,如果是查询语句(SELECT),我们还需要读取数据;如果是UPDATE、INSERT等语句,则只需检查操作是否成功即可。

c 复制代码
MYSQL_RES *mysql_store_result(MYSQL *mysql);
  • 工作原理 :该函数会调用MYSQL变量中的st_mysql_methods中的read_rows函数指针来获取查询的结果。同时该函数会返回MYSQL_RES这样一个变量,该变量主要用于保存查询的结果。

  • ⚠️ 内存泄漏警告 :该函数malloc了一片内存空间来存储查询过来的数据,所以我们一定要记得free(result),否则肯定会造成内存泄漏。

  • 示例

    c 复制代码
    MYSQL_RES *res = mysql_store_result(mfp);
    if (res == NULL) {
        fprintf(stderr, "mysql_store_result failed: %s\n", mysql_error(mfp));
        return -1;
    }

💡 重要提示 :MySQL将所有数据读取出来的时候,全部都当作字符串处理。MYSQL_RES可以当作一个char **xxx[]来理解。

4.5 获取结果行数:mysql_num_rows()

c 复制代码
my_ulonglong mysql_num_rows(MYSQL_RES *res);

返回结果集中的行数。

4.6 获取结果列数:mysql_num_fields()

c 复制代码
unsigned int mysql_num_fields(MYSQL_RES *res);

返回结果集中的列数。

4.7 获取列名:mysql_fetch_fields()

c 复制代码
MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *res);

返回一个MYSQL_FIELD结构体数组,每个元素包含列名、类型等信息。

示例:打印所有列名

c 复制代码
int fields = mysql_num_fields(res);
MYSQL_FIELD *field = mysql_fetch_fields(res);
int i = 0;
for(; i < fields; i++){
    cout << field[i].name << " ";
}
cout << endl;

4.8 获取结果内容:mysql_fetch_row()

c 复制代码
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result);
  • 返回值MYSQL_ROW其实就是char **,可以当作一个二维数组来使用。每次调用返回下一行数据,当没有更多行时返回NULL。

完整示例:遍历查询结果

c 复制代码
int nums = mysql_num_rows(res);
int fields = mysql_num_fields(res);

// 打印列名
MYSQL_FIELD *field = mysql_fetch_fields(res);
int i = 0;
for(; i < fields; i++){
    cout << field[i].name << " ";
}
cout << endl;

// 打印每一行数据
i = 0;
MYSQL_ROW line;
for(; i < nums; i++){
    line = mysql_fetch_row(res);
    int j = 0;
    for(; j < fields; j++){
        cout << line[j] << " ";
    }
    cout << endl;
}

4.9 关闭MySQL连接:mysql_close()

操作完成后,记得关闭连接,释放资源。

c 复制代码
void mysql_close(MYSQL *sock);

4.10 事务相关操作

MySQL C API还支持事务等常用操作:

c 复制代码
my_bool STDCALL mysql_autocommit(MYSQL *mysql, my_bool auto_mode);
my_bool STDCALL mysql_commit(MYSQL *mysql);
my_bool STDCALL mysql_rollback(MYSQL *mysql);

5. 💻 完整代码示例

下面是一个完整的示例程序,演示了从初始化、连接、查询到关闭的完整流程:

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

int main() {
    // 1. 初始化MySQL句柄
    MYSQL *mfp = mysql_init(NULL);
    if (mfp == NULL) {
        fprintf(stderr, "mysql_init failed!\n");
        return -1;
    }

    // 2. 连接数据库
    MYSQL *conn = mysql_real_connect(mfp, "localhost", "cpp_user", 
                                     "your_password", "cpp_test_db", 
                                     3306, NULL, 0);
    if (conn == NULL) {
        fprintf(stderr, "mysql_real_connect failed: %s\n", mysql_error(mfp));
        mysql_close(mfp);
        return -1;
    }
    printf("✅ 数据库连接成功!\n");

    // 3. 设置字符集为utf8,解决中文乱码
    mysql_set_character_set(mfp, "utf8");

    // 4. 创建一个测试表并插入数据
    const char *create_sql = "CREATE TABLE IF NOT EXISTS users ("
                             "id INT AUTO_INCREMENT PRIMARY KEY,"
                             "name VARCHAR(50) NOT NULL,"
                             "age INT NOT NULL"
                             ")";
    if (mysql_query(mfp, create_sql) != 0) {
        fprintf(stderr, "创建表失败: %s\n", mysql_error(mfp));
        mysql_close(mfp);
        return -1;
    }
    printf("✅ 表创建/已存在\n");

    // 5. 插入测试数据
    const char *insert_sql = "INSERT INTO users (name, age) VALUES "
                             "('张三', 25), ('李四', 30), ('王五', 28)";
    if (mysql_query(mfp, insert_sql) != 0) {
        fprintf(stderr, "插入数据失败: %s\n", mysql_error(mfp));
    } else {
        printf("✅ 插入数据成功,影响行数: %lu\n", 
               (unsigned long)mysql_affected_rows(mfp));
    }

    // 6. 执行查询
    if (mysql_query(mfp, "SELECT * FROM users") != 0) {
        fprintf(stderr, "查询失败: %s\n", mysql_error(mfp));
        mysql_close(mfp);
        return -1;
    }

    // 7. 获取查询结果
    MYSQL_RES *res = mysql_store_result(mfp);
    if (res == NULL) {
        fprintf(stderr, "mysql_store_result failed: %s\n", mysql_error(mfp));
        mysql_close(mfp);
        return -1;
    }

    // 8. 获取列数和行数
    int num_fields = mysql_num_fields(res);
    int num_rows = mysql_num_rows(res);
    printf("✅ 查询结果: %d 行, %d 列\n", num_rows, num_fields);

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

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

    // 11. 释放结果集
    mysql_free_result(res);

    // 12. 关闭连接
    mysql_close(mfp);
    printf("✅ 连接已关闭,程序结束。\n");

    return 0;
}

编译运行

bash 复制代码
gcc -o mysql_demo mysql_demo.c -I./include -L./lib -lmysqlclient
export LD_LIBRARY_PATH=./lib
./mysql_demo

6. 🖥️ MySQL图形化界面推荐

在实际开发中,除了使用命令行和C API,图形化工具也能大大提高效率:

  • Navicat:功能强大的数据库管理工具,支持多种数据库,有破解版可用。
  • MySQL Workbench:MySQL官方提供的免费图形化管理工具,功能全面。

7. 🏗️ MySQL连接池原理与网站数据流动

7.1 什么是连接池?

在实际的高并发Web应用中,每次用户请求都创建和销毁数据库连接是非常低效的。连接池(Connection Pool) 应运而生。

连接池的核心思想:预先创建一批数据库连接,放入一个"池子"中。当有请求需要操作数据库时,从池中取出一个空闲连接使用;使用完毕后,将连接归还到池中,而不是关闭它。

7.2 连接池的工作流程

7.3 简易连接池设计思路

cpp 复制代码
class ConnectionPool {
private:
    std::string sql;
    std::function<void(MYSQL_RES*)> cb;  // 回调函数
    // ... 连接池管理逻辑
};

// 任务队列 + 连接池
// 启动线程池 + mysql连接池
// while循环处理任务

💡 推荐项目:可以在GitHub上搜索"mysql连接池"或"hicar"等关键词,查看开源项目源码,深入理解连接池的实现。

7.4 用户注册与登录的数据流动

以用户注册和登录为例,看看数据是如何流动的:

  1. 用户注册

    • 用户在浏览器填写注册信息(用户名、密码等)
    • 前端发送POST请求到后端服务器
    • 后端从连接池获取一个数据库连接
    • 执行INSERT INTO users VALUES(...) SQL语句
    • 返回注册成功/失败信息给前端
  2. 用户登录

    • 用户输入账号密码
    • 后端从连接池获取连接
    • 执行SELECT * FROM users WHERE name='xxx' AND password='yyy'
    • 验证通过则创建Session/Token,返回登录成功
    • 验证失败则返回错误信息

8. 📝 总结

本文详细介绍了MySQL C语言连接(Connector/C)的完整使用流程,从环境准备、库的引入、核心API详解到完整的代码示例,涵盖了面试中常见的知识点:

知识点 核心要点
mysql_init() 初始化MySQL句柄,必须最先调用
mysql_real_connect() 建立TCP/IP连接,设置字符集解决中文乱码
mysql_query() 执行任意SQL语句
mysql_store_result() 获取查询结果集,注意释放内存
mysql_fetch_row() 逐行读取结果数据
mysql_close() 关闭连接,释放资源
连接池 提高性能,复用连接

9. ❓ 经典面试题与解答

面试题1:mysql_store_result()mysql_use_result()有什么区别?

解答

  • mysql_store_result():一次性从服务器读取所有结果并存储在客户端内存中。适用于结果集较小的情况,可以立即获取所有数据,并且可以随机访问。
  • mysql_use_result():逐行从服务器读取结果,不占用客户端大量内存。适用于结果集非常大的情况,但处理速度较慢,且不能随机访问。

面试题2:MySQL C API中如何解决中文乱码问题?

解答

在建立连接后,调用mysql_set_character_set(mysql, "utf8")将连接的字符集设置为UTF-8。MySQL默认的字符集是latin1,不支持中文,需要显式设置。也可以在创建数据库时指定字符集:

sql 复制代码
CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

面试题3:什么是MySQL连接池?为什么需要它?

解答

连接池是一种管理数据库连接的技术,它预先创建并维护一组数据库连接,应用程序可以从池中获取和归还连接,而不是每次都创建和销毁。

优点

  1. 减少开销:避免了频繁创建和销毁连接的系统开销。
  2. 提高响应速度:连接已经建立好,请求到来时可以直接使用。
  3. 资源控制:可以限制最大连接数,防止数据库被过多连接压垮。

面试题4:在C语言中,使用MySQL API时如何防止SQL注入?

解答

使用mysql_real_escape_string()函数对用户输入的特殊字符进行转义,然后再拼接到SQL语句中。更推荐的方式是使用预处理语句(Prepared Statement),MySQL C API提供了相应的接口(如mysql_stmt_prepare()mysql_stmt_bind_param()等),可以有效防止SQL注入。

面试题5:mysql_query()执行UPDATE/INSERT/DELETE后,如何获取受影响的行数?

解答

使用mysql_affected_rows()函数。该函数返回最近一次UPDATE、INSERT或DELETE操作所影响的行数。对于SELECT语句,它返回的是行数,但通常我们使用mysql_num_rows()来获取。

c 复制代码
my_ulonglong affected = mysql_affected_rows(mysql);
printf("受影响的行数: %lu\n", (unsigned long)affected);
相关推荐
Meteors.2 小时前
Android性能优化:01. 指标体系 + 分析工具链
android
jike_20263 小时前
安卓平台免费录音转文字工具深度测评:5 款 APP 功能对比与选型指南
android·智能电视
Database_Cool_3 小时前
云数据库控制台好不好用、能不能可视化操作:阿里云 RDS MySQL 控制台体验详解
数据库·mysql·阿里云
Code Man3 小时前
Windows 下使用 Appium
android·windows·appium
码农coding3 小时前
android 12 中的VSYNC接收
android
GitLqr4 小时前
Flutter 3.44 性能飞跃:深度解析 Android Platform View 的 HCPP 新特性
android·flutter·性能优化
码农coding4 小时前
android 12 SurfaceFlinger中的CompositionEngine
android
code_pgf4 小时前
C/C++ 中 `typedef` 关键字详解
c语言·c++
番茄炒鸡蛋加糖4 小时前
MySQL 实战调优& 分表基础
数据库·mysql