MySQL C API 是所有高階連接器(Connector/J、Connector/Python、PHP mysqli、Go driver 等)的基石。深入理解查詢發送機制、結果集生命週期、記憶體管理、二進位資料處理、預處理語句、元資料驅動的動態結果顯示,不僅能編寫高效能的 C/C++ 資料庫應用,更能排查上層驅動程式的疑難雜症。本文系統解析 mysql_real_query/mysql_query 差異、mysql_store_result vs mysql_use_result 選擇、通用查詢處理器模式、二進位資料編碼、結果集元資料應用與格式化輸出實作。
mysql_real_query + 長度;純文字可用 mysql_query + 空終止字串mysql_real_query(conn, sql, len) 或 mysql_query(conn, sql)if (mysql_field_count(conn) == 0) {
// 非結果集查詢:INSERT/UPDATE/DELETE/DDL
affected = mysql_affected_rows(conn);
} else {
// 結果集查詢:SELECT/SHOW/DESCRIBE/EXPLAIN
MYSQL_RES *res = mysql_store_result(conn); // 或 mysql_use_result
// 處理結果集...
mysql_free_result(res);
}
| 特性 | mysql_query | mysql_real_query |
|---|---|---|
| 查詢字串 | 空終止(不含 \0) | 計數字串(指標+長度,可含 \0) |
| 二進位資料 | 不支援(\0 會截斷) | 完整支援 |
| 構造便利性 | 可用 sprintf、strcat 等標準 C 字串函數 | 需自行管理長度,建議搭配 snprintf 到固定緩衝區 |
| 適用場景 | 純文字 SQL、無二進位參數 | 含 BLOB/二進位參數、動態構造長度未知 |
最佳實踐:統一使用 mysql_real_query,以 snprintf(buf, sizeof(buf), ...) 構造,長度用 strlen(buf) 或記錄 snprintf 回傳值。
| 特性 | mysql_store_result | mysql_use_result |
|---|---|---|
| 資料傳輸 | 一次性全取回客戶端記憶體 | 串流式,逐列按需取回 |
| 記憶體佔用 | 高(整個結果集) | 低(單列緩衝) |
| mysql_fetch_row | 從本地緩衝取出,不會出錯 | 每次網路往返,可能發生錯誤 |
| 隨機存取 | 支援 mysql_data_seek、mysql_row_seek | 僅支援循序讀取 |
| mysql_num_rows | 立即可用,準確 | 讀完所有列前為 0 |
| 伺服器端鎖定 | 釋放快(讀完即釋放表鎖) | 持續到讀完所有列(長交易風險) |
| 適用場景 | 中小結果集、需隨機存取、互動式瀏覽 | 巨大結果集、串流處理、記憶體受限 |
| 客戶端類型 | 互動式、GUI、報表產生器 | 批次處理、ETL、資料遷移工具 |
關鍵差異:mysql_use_result 要求必須讀完所有列才能發送下一個查詢,否則發生「Commands out of sync」錯誤。mysql_store_result 可隨時 mysql_free_result 中斷。
int process_query(MYSQL *conn, const char *sql) {
if (mysql_real_query(conn, sql, strlen(sql))) {
fprintf(stderr, "Query failed: %s\n", mysql_error(conn));
return -1;
}
MYSQL_RES *res = mysql_store_result(conn);
if (res) {
// 有結果集
process_result_set(res);
mysql_free_result(res);
} else {
// 無結果集或錯誤
if (mysql_field_count(conn) == 0) {
// 正常的非結果集查詢
printf("Affected rows: %lu\n", (unsigned long)mysql_affected_rows(conn));
} else {
// 結果集查詢但 store_result 失敗(記憶體不足/網路錯誤)
fprintf(stderr, "Result set retrieval failed: %s\n", mysql_error(conn));
return -1;
}
}
return 0;
}
void process_result_set(MYSQL_RES *res) {
MYSQL_ROW row;
unsigned int num_fields = mysql_num_fields(res);
MYSQL_FIELD *fields = mysql_fetch_fields(res);
while ((row = mysql_fetch_row(res))) {
unsigned long *lengths = mysql_fetch_lengths(res);
for (unsigned int i = 0; i < num_fields; i++) {
if (row[i] == NULL) {
printf("NULL\t");
} else {
// 二進位資料不可用 %s,需用 lengths[i]
fwrite(row[i], 1, lengths[i], stdout);
putchar('\t');
}
}
putchar('\n');
}
printf("Total rows: %lu\n", (unsigned long)mysql_num_rows(res));
}
char **(指標陣列),非 char *mysql_fetch_lengths 取得真實長度strtoll, strtod)NULL 值以 NULL 指標表示,必須檢查char *escaped = malloc(2 * data_len + 1);
mysql_real_escape_string(conn, escaped, binary_data, data_len);
snprintf(query, sizeof(query),
"INSERT INTO t (blob_col) VALUES ('%s')", escaped);
free(escaped);
或使用預處理語句(推薦,避免編碼開銷與 SQL 注入):
MYSQL_STMT *stmt = mysql_stmt_init(conn);
mysql_stmt_prepare(stmt, "INSERT INTO t (blob_col) VALUES (?)", -1);
MYSQL_BIND bind = {0};
bind.buffer_type = MYSQL_TYPE_BLOB;
bind.buffer = binary_data;
bind.buffer_length = data_len;
mysql_stmt_bind_param(stmt, &bind);
mysql_stmt_execute(stmt);
直接用 mysql_fetch_row 取得的指標與 mysql_fetch_lengths 長度,fwrite 或 memcpy 處理,勿用 printf("%s")。
name:欄位名稱type:MYSQL_TYPE_LONG, MYSQL_TYPE_VAR_STRING, MYSQL_TYPE_BLOB...max_length:結果集中該欄位最大顯示寬度(store_result 才有效)flags:NOT_NULL_FLAG, PRI_KEY_FLAG, AUTO_INCREMENT_FLAG...decimals:小數位數MYSQL_FIELD 計算各欄顯示寬度 = max(strlen(name), max_length, strlen("NULL"))| %-*s | 格式+---+---+NULL// 計算欄寬
for (i = 0; i < num_fields; i++) {
MYSQL_FIELD *f = &fields[i];
unsigned int w = strlen(f->name);
if (f->max_length > w) w = f->max_length;
if (f->flags & NOT_NULL_FLAG) {
if (4 > w) w = 4; // "NULL"
}
col_width[i] = w;
}
// 列印表頭
for (i = 0; i < num_fields; i++) {
printf("| %-*s ", col_width[i], fields[i].name);
}
puts("|");
// 列印資料列
while ((row = mysql_fetch_row(res))) {
for (i = 0; i < num_fields; i++) {
if (row[i] == NULL) {
printf("| %-*s ", col_width[i], "NULL");
} else if (IS_NUM(fields[i].type)) {
printf("| %*s ", col_width[i], row[i]); // 右對齊
} else {
printf("| %-*s ", col_width[i], row[i]); // 左對齊
}
}
puts("|");
}
// 1. 準備
MYSQL_STMT *stmt = mysql_stmt_init(conn);
mysql_stmt_prepare(stmt, "SELECT id, name FROM users WHERE age > ? AND status = ?", -1);
// 2. 綁定參數
MYSQL_BIND param[2] = {0};
int age = 18;
char status[] = "active";
param[0].buffer_type = MYSQL_TYPE_LONG;
param[0].buffer = &age;
param[1].buffer_type = MYSQL_TYPE_STRING;
param[1].buffer = status;
param[1].buffer_length = strlen(status);
mysql_stmt_bind_param(stmt, param);
// 3. 綁定結果
MYSQL_BIND result[2] = {0};
int id;
char name[100];
unsigned long name_len;
result[0].buffer_type = MYSQL_TYPE_LONG;
result[0].buffer = &id;
result[1].buffer_type = MYSQL_TYPE_STRING;
result[1].buffer = name;
result[1].buffer_length = sizeof(name);
result[1].length = &name_len;
mysql_stmt_bind_result(stmt, result);
// 4. 執行與取回
mysql_stmt_execute(stmt);
while (mysql_stmt_fetch(stmt) == 0) {
printf("ID: %d, Name: %.*s\n", id, (int)name_len, name);
}
mysql_stmt_close(stmt);
預處理優勢:
mysql_error(conn) / mysql_stmt_error(stmt)mysql_free_result、mysql_stmt_close、mysql_close 必須成對呼叫,防止記憶體洩漏mysql_ping 檢測連線存活,斷線自動重連MYSQL* 連線物件,不可跨緒共用MySQL C API 雖然介面樸素,但提供了完全控制查詢執行、資料傳輸、記憶體管理、預處理快取的底層能力。掌握 store_result/use_result 選擇準則、MYSQL_ROW 二進位安全處理、元資料驅動的動態格式化、預處理語句完整流程,是編寫健壯、高效能資料庫應用的核心技能。建議將上述通用查詢處理器、格式化輸出、預處理封裝整理為專案內部資料庫存取庫,統一錯誤處理、資源管理、日誌記錄標準,避免重複造輪子並降低維護成本。