我们将涵盖从环境准备、核心 API 使用、完整代码示例到编译和常见问题解决的全过程。

(图片来源网络,侵删)
目录
- 准备工作
- 安装 MySQL/MariaDB 服务器
- 安装 C 语言开发库
- 创建测试数据库和用户
- 核心概念与 API
mysql_init()- 初始化连接句柄mysql_real_connect()- 建立连接mysql_query()/mysql_real_query()- 执行 SQL 语句mysql_store_result()/mysql_use_result()- 获取结果集mysql_fetch_row()- 遍历结果集mysql_num_fields()/mysql_num_rows()- 获取结果集信息mysql_free_result()- 释放结果集mysql_close()- 关闭连接
- 完整 C 语言示例代码
- 创建表
- 插入数据
- 查询数据
- 更新数据
- 删除数据
- 如何编译 C 程序
- 使用
gcc和-lmysqlclient选项
- 使用
- 处理错误与最佳实践
- 检查每个 API 调用的返回值
- 使用
mysql_error()获取错误信息
- 进阶话题
- 预处理语句 防止 SQL 注入
- 多线程安全
准备工作
在开始编写 C 代码之前,必须确保你的 Linux 系统上安装了必要的软件。
a. 安装 MySQL/MariaDB 服务器
以 Ubuntu/Debian 为例:
# 更新软件包列表 sudo apt update # 安装 MySQL 服务器 sudo apt install mysql-server # 安装后,安全配置向导(建议运行) sudo mysql_secure_installation
以 CentOS/RHEL 为例:
# 安装 MySQL 官方仓库 sudo yum localinstall https://dev.mysql.com/get/mysql80-community-release-el8-7.noarch.rpm -y # 安装 MySQL 服务器 sudo yum install mysql-community-server -y # 启动并设置开机自启 sudo systemctl start mysqld sudo systemctl enable mysqld
b. 安装 C 语言开发库和 MySQL 客户端开发头文件
这个包包含了编译 C 程序所需的头文件(如 mysql.h)和库文件(如 libmysqlclient.so)。

(图片来源网络,侵删)
Ubuntu/Debian:
sudo apt install libmysqlclient-dev
CentOS/RHEL:
sudo yum install mysql-devel
c. 创建测试数据库和用户
为了安全,不要使用 root 用户连接数据库,我们创建一个专用的数据库和用户。
-
登录到 MySQL:
(图片来源网络,侵删)mysql -u root -p
-
执行以下 SQL 命令:
-- 创建一个名为 test_db 的数据库 CREATE DATABASE test_db; -- 创建一个新用户 'c_user',密码设为 'your_password',并授予其对 test_db 的所有权限 CREATE USER 'c_user'@'localhost' IDENTIFIED BY 'your_password'; -- 授予权限 GRANT ALL PRIVILEGES ON test_db.* TO 'c_user'@'localhost'; -- 刷新权限使生效 FLUSH PRIVILEGES; -- 退出 EXIT;
核心概念与 API
C 语言操作 MySQL 的基本流程是:初始化 -> 连接 -> 执行查询 -> 处理结果 -> 释放资源 -> 关闭连接。
| API 函数 | 作用 | 关键点 |
|---|---|---|
MYSQL *mysql_init(MYSQL *mysql) |
初始化一个 MYSQL 句柄结构体。 |
在连接前必须调用,如果参数为 NULL,它会分配一个新的结构体并返回指针。 |
MYSQL *mysql_real_connect(...) |
尝试与 MySQL 服务器建立连接。 | 需要传入 mysql_init() 返回的句柄,以及主机、用户、密码、数据库等信息。 |
int mysql_query(MYSQL *mysql, const char *stmt_str) |
执行一个 SQL 语句。 | 语句以字符串形式传入,不能包含二进制数据,对于简单查询足够。 |
int mysql_real_query(...) |
执行一个 SQL 语句。 | 以 stmt_length 长度的字符串形式传入,可以包含二进制数据,更安全。 |
MYSQL_RES *mysql_store_result(MYSQL *mysql) |
立即获取并完整存储查询结果集。 | 对于 SELECT 查询使用,如果结果集很大,会消耗较多内存。 |
MYSQL_RES *mysql_use_result(MYSQL *mysql) |
逐行获取查询结果集,不一次性加载到内存。 | 适用于非常大的结果集,可以节省内存,但会占用服务器资源,且不能在获取结果时执行其他查询。 |
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result) |
从结果集中获取下一行数据。 | MYSQL_ROW 实际上是一个 char **,即字符串数组。 |
unsigned int mysql_num_fields(MYSQL_RES *result) |
获取结果集中的列数。 | 用于确定 mysql_fetch_row() 返回的数组长度。 |
my_ulonglong mysql_num_rows(MYSQL_RES *result) |
获取结果集中的行数。 | 只有在调用 mysql_store_result() 之后才能准确获取。 |
void mysql_free_result(MYSQL_RES *result) |
释放由 mysql_store_result() 或 mysql_use_result() 分配的结果集内存。 |
非常重要! 忘记释放会导致内存泄漏。 |
void mysql_close(MYSQL *mysql) |
关闭与 MySQL 服务器的连接并释放 MYSQL 句柄。 |
程序结束时必须调用。 |
完整 C 语言示例代码
下面是一个完整的 main.c 文件,它演示了创建表、增删改查所有基本操作。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mysql/mysql.h> // 包含 MySQL 头文件
// 定义数据库连接信息
#define DB_HOST "localhost"
#define DB_USER "c_user"
#define DB_PASS "your_password" // <-- 修改成你自己的密码
#define DB_NAME "test_db"
// 打印错误信息并退出
void finish_with_error(MYSQL *con) {
fprintf(stderr, "Error: %s\n", mysql_error(con));
mysql_close(con);
exit(1);
}
int main(int argc, char **argv) {
MYSQL *con; // 定义一个 MySQL 连接句柄
MYSQL_RES *result; // 定义一个结果集指针
MYSQL_ROW row; // 定义一个行指针
// 1. 初始化连接句柄
con = mysql_init(NULL);
if (con == NULL) {
fprintf(stderr, "mysql_init() failed\n");
exit(1);
}
// 2. 建立连接
if (mysql_real_connect(con, DB_HOST, DB_USER, DB_PASS, DB_NAME, 0, NULL, CLIENT_MULTI_STATEMENTS) == NULL) {
finish_with_error(con);
}
printf("Successfully connected to MySQL database.\n");
// --- 3. 执行 SQL 语句 ---
// a. 创建表
if (mysql_query(con, "DROP TABLE IF EXISTS employees;")) {
finish_with_error(con);
}
if (mysql_query(con, "CREATE TABLE employees (id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50), age INT, salary DOUBLE);")) {
finish_with_error(con);
}
printf("Table 'employees' created or already exists.\n");
// b. 插入数据 (使用多条语句)
const char *insert_sql = "INSERT INTO employees (name, age, salary) VALUES ('Alice', 30, 75000.50);"
"INSERT INTO employees (name, age, salary) VALUES ('Bob', 25, 62000.00);"
"INSERT INTO employees (name, age, salary) VALUES ('Charlie', 35, 90000.75);";
if (mysql_query(con, insert_sql)) {
finish_with_error(con);
}
printf("3 records inserted.\n");
// c. 查询数据
if (mysql_query(con, "SELECT * FROM employees")) {
finish_with_error(con);
}
// 获取结果集
result = mysql_store_result(con);
if (result == NULL) {
finish_with_error(con);
}
// 获取列数
int num_fields = mysql_num_fields(result);
// 打印表头
MYSQL_FIELD *field;
printf("\n--- Employee Data ---\n");
while ((field = mysql_fetch_field(result))) {
printf("%-15s", field->name);
}
printf("\n");
// 遍历并打印每一行数据
while ((row = mysql_fetch_row(result))) {
for(int i = 0; i < num_fields; i++) {
printf("%-15s", row[i] ? row[i] : "NULL");
}
printf("\n");
}
printf("---------------------\n");
// 释放结果集
mysql_free_result(result);
// d. 更新数据
const char *update_sql = "UPDATE employees SET salary = salary * 1.1 WHERE name = 'Alice';";
if (mysql_query(con, update_sql)) {
finish_with_error(con);
}
printf("Alice's salary updated.\n");
// e. 删除数据
const char *delete_sql = "DELETE FROM employees WHERE age < 30;";
if (mysql_query(con, delete_sql)) {
finish_with_error(con);
}
printf("Employees under 30 deleted.\n");
// 再次查询以显示更新后的结果
if (mysql_query(con, "SELECT id, name, salary FROM employees")) {
finish_with_error(con);
}
result = mysql_store_result(con);
if (result == NULL) {
finish_with_error(con);
}
num_fields = mysql_num_fields(result);
printf("\n--- Updated Employee Data ---\n");
while ((field = mysql_fetch_field(result))) {
printf("%-10s", field->name);
}
printf("\n");
while ((row = mysql_fetch_row(result))) {
for(int i = 0; i < num_fields; i++) {
printf("%-10s", row[i] ? row[i] : "NULL");
}
printf("\n");
}
printf("-----------------------------\n");
mysql_free_result(result);
// 4. 关闭连接
mysql_close(con);
printf("MySQL connection closed.\n");
return 0;
}
如何编译 C 程序
使用 gcc 编译器时,需要链接 MySQL 客户端库,关键选项是 -lmysqlclient。
打开终端,进入你的代码所在目录,然后运行:
gcc main.c -o mysql_app -lmysqlclient
gcc main.c: 指定要编译的源文件。-o mysql_app: 指定输出的可执行文件名为mysql_app。-lmysqlclient: 告诉链接器去链接libmysqlclient.so库,这个库通常在/usr/lib/x86_64-linux-gnu/或类似路径下。
如果编译时提示找不到头文件或库,请确保你已经正确安装了 libmysqlclient-dev 包。
运行编译好的程序:
./mysql_app
你应该能看到程序输出的信息,包括连接成功、创建表、插入数据、查询结果等。
处理错误与最佳实践
- 始终检查返回值:
mysql_init,mysql_real_connect,mysql_query,mysql_store_result等函数在失败时都会返回错误状态(NULL或非零值),你的代码应该像示例中一样,检查每个调用的返回值,并在出错时妥善处理。 - 使用
mysql_error():当 API 调用失败时,mysql_error(con)会返回一个描述性错误字符串,这对于调试至关重要。 - 释放资源:
mysql_free_result()和mysql_close()必须被调用,否则会导致内存泄漏和连接资源未释放,一个良好的做法是使用goto或do { ... } while(0)结构来确保清理代码一定会执行。 - 使用预处理语句防止 SQL 注入:上面的示例直接拼接 SQL 字符串,这非常危险,容易受到 SQL 注入攻击,在生产环境中,强烈推荐使用预处理语句。
进阶话题:预处理语句
预处理语句可以将 SQL 语句和数据分开发送到服务器,服务器会先编译 SQL 语句,然后只将数据作为参数发送,这大大提高了安全性,并允许数据库服务器缓存执行计划,提高重复执行的效率。
基本流程:
mysql_stmt_prepare(): 准备一个 SQL 语句模板,使用 作为占位符。mysql_stmt_param_count(): 获取参数数量。mysql_stmt_bind_param(): 将 C 语言变量绑定到 占位符。mysql_stmt_execute(): 执行语句。mysql_stmt_bind_result(): 将结果集的列绑定到 C 语言变量。mysql_stmt_fetch(): 获取结果。
这是一个更安全、更高效的编程模型,对于复杂的 C 应用程序来说是必须掌握的技能,你可以查阅 MySQL 官方文档中关于 "C API Prepared Statement" 的章节来学习更详细的使用方法。
