目录
- 环境准备
- 安装MySQL服务器
- 安装MySQL C连接库
- 包含头文件和链接库
- 核心API介绍
- 初始化连接
- 执行SQL语句
- 处理查询结果
- 释放资源
- 完整代码示例
- 创建数据库和表
- C语言完整代码(增、删、改、查)
- 预处理语句
- 什么是预处理语句
- 为什么需要它(防止SQL注入)
- 代码示例
- 最佳实践与注意事项
环境准备
a. 安装MySQL服务器
你的系统上需要有一个正在运行的MySQL服务器,如果你还没有,请从 MySQL官网 下载并安装。

b. 安装MySQL C连接库
你需要安装C语言连接MySQL所需的客户端库和头文件,这个库通常被称为 libmysqlclient。
-
在 Ubuntu/Debian 上:
sudo apt-get update sudo apt-get install libmysqlclient-dev
libmysqlclient-dev包含了编译所需的头文件(如mysql.h)和库文件(如libmysqlclient.so)。 -
在 CentOS/RHEL/Fedora 上:
(图片来源网络,侵删)sudo yum install mysql-devel # 或者对于较新的系统 sudo dnf install mysql-devel
-
在 macOS 上 (使用 Homebrew):
brew install mysql-client
安装后,你可能需要设置一些链接,以便编译器能找到它,可以参考 Homebrew 的输出提示。
-
在 Windows 上: 最简单的方式是下载 "MySQL for Visual C++" 的压缩包,它包含了所有必要的库和头文件,将其解压到一个固定目录(如
C:\mysql-connector-c-6.1.x-winx64),然后在你的IDE(如Visual Studio)中配置包含目录和库目录。
c. 包含头文件和链接库
在你的C代码中,你需要包含MySQL的头文件:

#include <mysql/mysql.h> // 在Linux/macOS上 // #include <winsock2.h> // 在Windows上,可能需要包含这个 // #include <mysql.h> // 在Windows上,路径可能不同
在编译时,你需要链接 mysqlclient 库。
-
使用
gcc编译:gcc your_program.c -o your_program -I/usr/include/mysql -L/usr/lib/x86_64-linux-gnu -lmysqlclient
-I: 指定头文件路径。-L: 指定库文件路径。-lmysqlclient: 链接libmysqlclient库。
如果你的库在标准路径下(如通过
apt安装),-I和-L参数可以省略:gcc your_program.c -o your_program -lmysqlclient
核心API介绍
C语言的MySQL API是一系列C函数,你需要按顺序调用它们来完成数据库操作。
a. 初始化连接
MYSQL *mysql_init(MYSQL *mysql);
初始化一个 MYSQL 对象句柄,通常传入 NULL 让它分配一个新的。
MYSQL *mysql_real_connect(MYSQL *mysql, const char *host,
const char *user, const char *passwd,
const char *db, unsigned int port,
const char *unix_socket, unsigned long clientflag);
建立到数据库服务器的连接。
mysql:mysql_init()返回的句柄。host: 服务器地址(如 "localhost")。user: 用户名。passwd: 密码。db: 默认连接的数据库名(可以为NULL,后续再选)。port: 端口号(通常为 0,使用默认的 3306)。unix_socket: Unix域套接字路径(通常为NULL)。clientflag: 客户端标志(通常为 0)。
连接成功返回 MYSQL* 指针,失败返回 NULL。
b. 执行SQL语句
int mysql_query(MYSQL *mysql, const char *stmt_str);
执行一个SQL语句,这个函数主要用于执行不返回结果集的语句(如 INSERT, UPDATE, DELETE, CREATE)。
- 成功返回 0。
- 失败返回非 0。
MYSQL_RES *mysql_store_result(MYSQL *mysql);
对于会返回结果集的查询(如 SELECT),在执行 mysql_query 后,调用此函数将整个结果集从服务器获取到客户端内存,返回一个 MYSQL_RES 结果集指针。
c. 处理查询结果
如果查询成功并返回了结果集(mysql_store_result 返回非 NULL),你需要遍历它。
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result);
获取结果集的下一行数据,返回一个字符串数组(MYSQL_ROW),其中每个元素对应一列的数据,当没有更多行时返回 NULL。
unsigned int mysql_num_fields(MYSQL_RES *result);
获取结果集中的列数。
my_ulonglong mysql_affected_rows(MYSQL *mysql);
获取 INSERT, UPDATE, DELETE 语句影响的行数。
d. 释放资源
非常重要! 忘记释放资源会导致内存泄漏。
void mysql_free_result(MYSQL_RES *result);
释放 mysql_store_result 分配的结果集内存。
void mysql_close(MYSQL *mysql);
关闭数据库连接并释放 MYSQL 句柄。
完整代码示例
这个例子将演示如何连接数据库,创建表,插入数据,查询数据,更新数据和删除数据。
a. 准备数据库
在MySQL中执行以下SQL:
CREATE DATABASE IF NOT EXISTS test_db;
USE test_db;
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
age INT,
email VARCHAR(100)
);
b. C语言完整代码 (mysql_example.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mysql/mysql.h> // 包含MySQL头文件
// 定义数据库连接信息
#define DB_HOST "localhost"
#define DB_USER "root"
#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() {
MYSQL *con = mysql_init(NULL); // 1. 初始化连接句柄
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, 0) == NULL) {
finish_with_error(con);
}
printf("Connected to MySQL successfully!\n");
// 3. 执行SQL - 创建表 (如果不存在)
if (mysql_query(con, "DROP TABLE IF EXISTS users; CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50), age INT, email VARCHAR(100));")) {
finish_with_error(con);
}
printf("Table 'users' created or already exists.\n");
// 4. 执行SQL - 插入数据
const char *insert_sql = "INSERT INTO users (name, age, email) VALUES ('Alice', 30, 'alice@example.com'), ('Bob', 25, 'bob@example.com');";
if (mysql_query(con, insert_sql)) {
finish_with_error(con);
}
printf("Inserted 2 rows into 'users'.\n");
// 5. 执行SQL - 查询数据
if (mysql_query(con, "SELECT * FROM users")) {
finish_with_error(con);
}
// 6. 获取结果集
MYSQL_RES *result = mysql_store_result(con);
if (result == NULL) {
finish_with_error(con);
}
int num_fields = mysql_num_fields(result);
// 7. 遍历结果集
MYSQL_ROW row;
MYSQL_FIELD *field;
printf("\n--- Query Results ---\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");
// 8. 释放结果集
mysql_free_result(result);
// 9. 执行SQL - 更新数据
const char *update_sql = "UPDATE users SET age = 31 WHERE name = 'Alice'";
if (mysql_query(con, update_sql)) {
finish_with_error(con);
}
printf("Updated 1 row in 'users'.\n");
// 10. 执行SQL - 删除数据
const char *delete_sql = "DELETE FROM users WHERE name = 'Bob'";
if (mysql_query(con, delete_sql)) {
finish_with_error(con);
}
printf("Deleted 1 row from 'users'.\n");
// 11. 关闭连接
mysql_close(con);
printf("Connection closed.\n");
return 0;
}
c. 编译和运行
# 编译 gcc mysql_example.c -o mysql_example -lmysqlclient # 运行 (确保你的MySQL服务正在运行) ./mysql_example
预处理语句
直接拼接SQL字符串(sprintf(sql, "INSERT ... VALUES('%s', %d)", name, age))是极其危险的,会导致 SQL注入 攻击,预处理语句是防止SQL注入的标准方法。
它的工作原理是:
- 发送SQL模板给服务器,其中参数用 占位。
- 服务器解析并编译这个模板。
- 程序分别发送每个参数值给服务器。
- 服务器将参数安全地绑定到模板中并执行。
预处理语句代码示例
// ... (前面的连接代码相同) ...
// 1. 准备预处理语句
if (mysql_query(con, "INSERT INTO users (name, age, email) VALUES (?, ?, ?)")) {
finish_with_error(con);
}
MYSQL_STMT *stmt = mysql_stmt_init(con);
if (stmt == NULL) {
finish_with_error(con);
}
const char *stmt_str = "INSERT INTO users (name, age, email) VALUES (?, ?, ?)";
if (mysql_stmt_prepare(stmt, stmt_str, strlen(stmt_str))) {
fprintf(stderr, "mysql_stmt_prepare() failed\n");
mysql_stmt_close(stmt);
finish_with_error(con);
}
printf("Statement prepared.\n");
// 2. 绑定参数
// 定义变量来存放要绑定的数据
char name[50] = "Charlie";
int age = 28;
char email[100] = "charlie@example.com";
// 绑定变量到SQL语句的占位符
// MYSQL_BIND 结构体数组,每个元素对应一个 ?
MYSQL_BIND bind[3];
memset(bind, 0, sizeof(bind));
// 绑定 name (字符串)
bind[0].buffer_type = MYSQL_TYPE_STRING;
bind[0].buffer = (char *)name;
bind[0].buffer_length = strlen(name);
bind[0].length = &bind[0].buffer_length; // 让mysql自动计算长度
// 绑定 age (整数)
bind[1].buffer_type = MYSQL_TYPE_LONG;
bind[1].buffer = (char *)&age;
bind[1].is_null = 0; // 不为NULL
// 绑定 email (字符串)
bind[2].buffer_type = MYSQL_TYPE_STRING;
bind[2].buffer = (char *)email;
bind[2].buffer_length = strlen(email);
bind[2].length = &bind[2].buffer_length;
if (mysql_stmt_bind_param(stmt, bind)) {
fprintf(stderr, "mysql_stmt_bind_param() failed\n");
mysql_stmt_close(stmt);
finish_with_error(con);
}
// 3. 执行预处理语句
if (mysql_stmt_execute(stmt)) {
fprintf(stderr, "mysql_stmt_execute() failed\n");
mysql_stmt_close(stmt);
finish_with_error(con);
}
printf("Inserted 1 row using prepared statement.\n");
// 4. 关闭预处理语句
mysql_stmt_close(stmt);
// ... (关闭连接) ...
最佳实践与注意事项
- 错误处理:始终检查每个MySQL API函数的返回值,如果失败,立即使用
mysql_error(con)获取错误信息并处理。 - 资源释放:遵循“谁分配,谁释放”的原则。
mysql_init分配的句柄由mysql_close释放,mysql_store_result分配的结果集由mysql_free_result释放。mysql_stmt_init分配的由mysql_stmt_close释放。 - 使用预处理语句:对于任何包含用户输入的SQL查询,必须使用预处理语句来防止SQL注入,这是最重要的安全实践。
- 多线程:
MYSQL和MYSQL_STMT结构体不是线程安全的,每个线程都应该拥有自己独立的连接句柄。 - 连接管理:在高性能应用中,频繁地建立和关闭连接开销很大,应该使用 连接池 技术来复用数据库连接。
- 字符集:为了避免乱码,在连接后立即设置正确的字符集。
mysql_set_character_set(con, "utf8mb4");
