環境準備(Linux)
1. 安裝開發庫
# CentOS/RHEL
yum install mysql-devel
# Ubuntu/Debian
apt-get install libmysqlclient-dev
# 驗證頭文件與庫文件
find / -name mysql.h # 通常 /usr/include/mysql/mysql.h
find / -name libmysqlclient* # 通常 /usr/lib/mysql/ 或 /usr/lib64/mysql/
2. 編譯鏈接命令
gcc -I/usr/include/mysql -L/usr/lib/mysql -lmysqlclient main.c -o main
# -I: 頭文件路徑
# -L: 庫文件路徑
# -l: 鏈接庫 (去掉 lib 前綴和 .so/.a 後綴)
3. 運行時動態庫路徑
# 臨時
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/mysql
# 永久 (~/.bashrc)
echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/mysql' >> ~/.bashrc
# 或編譯時嵌入 rpath(推薦)
gcc -Wl,-rpath,/usr/lib/mysql -I/usr/include/mysql -L/usr/lib/mysql -lmysqlclient main.c -o main
完整 CRUD 示例程序
#include
#include
#include
#define CHECK_MYSQL(conn, stmt) \
do { if (stmt) { \
fprintf(stderr, "%s: %s\n", #stmt, mysql_error(conn)); \
mysql_close(conn); exit(1); } } while(0)
int main() {
MYSQL *conn = mysql_init(NULL);
CHECK_MYSQL(conn, conn == NULL);
// 連接數據庫
if (!mysql_real_connect(conn, "localhost", "root", "password",
"mylibrary", 0, NULL, 0)) {
CHECK_MYSQL(conn, 1); // 觸發錯誤處理
}
// 設置字符集
CHECK_MYSQL(conn, mysql_query(conn, "SET NAMES 'utf8'"));
// 執行查詢:統計各出版社圖書數量
const char *sql =
"SELECT COUNT(titleID), publName "
"FROM publishers, titles "
"WHERE publishers.publID = titles.publID "
"GROUP BY publishers.publID "
"ORDER BY publName";
CHECK_MYSQL(conn, mysql_query(conn, sql));
// 獲取結果集
MYSQL_RES *result = mysql_store_result(conn);
CHECK_MYSQL(conn, result == NULL);
printf("找到 %llu 條記錄\n", (unsigned long long)mysql_num_rows(result));
// 遍歷行
MYSQL_ROW row;
while ((row = mysql_fetch_row(result))) {
for (int i = 0; i < mysql_num_fields(result); i++) {
printf("%s\t", row[i] ? row[i] : "[NULL]");
}
printf("\n");
}
// 釋放資源(逆序)
mysql_free_result(result);
mysql_close(conn);
return 0;
}
關鍵 API 流程圖
mysql_init() → mysql_real_connect() → mysql_query()
→ mysql_store_result() / mysql_use_result()
→ mysql_fetch_row() 循環
→ mysql_free_result()
→ mysql_close()
核心函數詳解
| 函數 | 作用 | 注意事項 |
mysql_init | 初始化 MYSQL 結構體 | 傳 NULL 自動分配,需配對 mysql_close |
mysql_real_connect | 建立連接 | 參數:host, user, passwd, db, port, unix_socket, client_flag |
mysql_query | 執行 SQL | 返回 0 成功,非 0 失敗;需配合 mysql_error 獲取詳情 |
mysql_store_result | 將完整結果集讀取到客戶端 | 適用小/中結果集;大結果集用 mysql_use_result 流式讀取 |
mysql_fetch_row | 獲取下一行 | 返回 MYSQL_ROW (char**),NULL 表示結束 |
mysql_num_fields | 列數 | 配合 row[i] 遍歷列 |
mysql_free_result | 釋放結果集內存 | 必須調用,否則洩漏 |
mysql_close | 關閉連接並釋放結構體 | 程序結束前必須調用 |
錯誤處理最佳實踐
// 宏封裝統一錯誤處理
#define MYSQL_CHECK(conn, expr, cleanup) \
do { if (expr) { \
fprintf(stderr, "MySQL Error [%s:%d]: %s\n", __FILE__, __LINE__, mysql_error(conn)); \
cleanup; \
return -1; } } while(0)
// 使用示例
MYSQL_CHECK(conn, mysql_query(conn, sql), mysql_close(conn));
MYSQL_CHECK(conn, !(result = mysql_store_result(conn)), mysql_close(conn));
預處理語句——防 SQL 注入、提升性能
MYSQL_STMT *stmt = mysql_stmt_init(conn);
const char *sql = "INSERT INTO books(title, author, price) VALUES(?, ?, ?)";
MYSQL_CHECK(conn, mysql_stmt_prepare(stmt, sql, strlen(sql)), mysql_stmt_close(stmt));
MYSQL_BIND bind[3] = {0};
char title[100]; char author[50]; double price;
// 綁定參數...
mysql_stmt_bind_param(stmt, bind);
// 循環執行
for (int i = 0; i < n; i++) {
// 設置 title, author, price
MYSQL_CHECK(conn, mysql_stmt_execute(stmt), mysql_stmt_close(stmt));
}
mysql_stmt_close(stmt);
常見坑避坑指南
| 坑 | 現象 | 解決 |
| 字符集亂碼 | 中文存入變亂碼 | 連接後立即 SET NAMES 'utf8mb4';表/庫字符集統一 utf8mb4 |
| 結果集未釋放 | 內存洩漏、後續查詢失敗 | 每個 store_result 必配對 free_result |
| 長連接斷開 | MySQL server has gone away | 啟用自動重連 mysql_options(conn, MYSQL_OPT_RECONNECT, &on) 或心跳檢測 |
| 大結果集 OOM | 店內存耗盡 | 改用 mysql_use_result 流式讀取,逐行處理後釋放 |
| 編找不到庫 | undefined reference to mysql_init | 確認 -lmysqlclient 順序在源文件後,-L 路徑正確 |