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命令查看可执行文件依赖的动态库:
bashldd ./test
至此,库的引入工作已经完成,接下来我们开始熟悉核心API接口。
4. 🧩 MySQL C接口核心API详解
4.1 初始化:mysql_init()
要使用MySQL C库,必须先进行初始化!
c
MYSQL *mysql_init(MYSQL *mysql);
-
参数 :通常传入
NULL,函数会内部自动分配一个MYSQL对象。 -
返回值 :指向初始化后的
MYSQL结构体的指针。 -
示例 :
cMYSQL *mfp = mysql_init(NULL); if (mfp == NULL) { fprintf(stderr, "mysql_init failed!\n"); return -1; }
🔍 深入理解 :
MYSQL是C API中一个非常重要的结构体变量,里面包含了非常丰富的信息,如port、dbname、charset等连接基本参数。它还包含了一个名为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。
-
示例 :
cMYSQL *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:
cmysql_set_character_set(mfp, "utf8");
4.3 执行SQL语句:mysql_query()
连接成功后,就可以下发SQL命令了。
c
int mysql_query(MYSQL *mysql, const char *q);
-
参数 :
mysql:mysql_real_connect返回的连接句柄。q:要执行的SQL语句字符串,如"SELECT * FROM table"。
-
返回值:成功返回0,失败返回非0。
-
示例 :
cif (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),否则肯定会造成内存泄漏。 -
示例 :
cMYSQL_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 用户注册与登录的数据流动
以用户注册和登录为例,看看数据是如何流动的:
-
用户注册:
- 用户在浏览器填写注册信息(用户名、密码等)
- 前端发送POST请求到后端服务器
- 后端从连接池获取一个数据库连接
- 执行
INSERT INTO users VALUES(...)SQL语句 - 返回注册成功/失败信息给前端
-
用户登录:
- 用户输入账号密码
- 后端从连接池获取连接
- 执行
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连接池?为什么需要它?
解答 :
连接池是一种管理数据库连接的技术,它预先创建并维护一组数据库连接,应用程序可以从池中获取和归还连接,而不是每次都创建和销毁。
优点:
- 减少开销:避免了频繁创建和销毁连接的系统开销。
- 提高响应速度:连接已经建立好,请求到来时可以直接使用。
- 资源控制:可以限制最大连接数,防止数据库被过多连接压垮。
面试题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);