首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫

MySQL C API 查詢處理完整指南:mysql_real_query、結果集管理、預處理語句與元資料應用

     編輯:MySQL 資料庫相關知識

前言

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 選擇、通用查詢處理器模式、二進位資料編碼、結果集元資料應用與格式化輸出實作。

一、查詢處理核心流程

  1. 構造查詢:含二進位資料需用 mysql_real_query + 長度;純文字可用 mysql_query + 空終止字串
  2. 發送執行:mysql_real_query(conn, sql, len) 或 mysql_query(conn, sql)
  3. 判斷結果類型:
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 vs mysql_real_query 選擇指南

特性mysql_querymysql_real_query
查詢字串空終止(不含 \0)計數字串(指標+長度,可含 \0)
二進位資料不支援(\0 會截斷)完整支援
構造便利性可用 sprintf、strcat 等標準 C 字串函數需自行管理長度,建議搭配 snprintf 到固定緩衝區
適用場景純文字 SQL、無二進位參數含 BLOB/二進位參數、動態構造長度未知

最佳實踐:統一使用 mysql_real_query,以 snprintf(buf, sizeof(buf), ...) 構造,長度用 strlen(buf) 或記錄 snprintf 回傳值。

三、結果集處理:store_result vs use_result

特性mysql_store_resultmysql_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 中斷。

四、通用查詢處理器模式(支援任意 SQL)

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;
}

五、結果集列處理與 MYSQL_ROW 細節

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));
}

MYSQL_ROW 關鍵特性

  • 型別為 char **(指標陣列),非 char *
  • 字串不保證空終止(含二進位資料時),必須配合 mysql_fetch_lengths 取得真實長度
  • 所有值以文字形式回傳,數值需自行轉換(strtoll, strtod)
  • NULL 值以 NULL 指標表示,必須檢查

六、二進位資料處理:編碼與解碼

寫入二進位資料:mysql_real_escape_string

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")。

七、結果集元資料驅動的格式化輸出(類 mysql 客戶端)

元資料結構 MYSQL_FIELD

  • 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:小數位數

格式化輸出演算法

  1. 遍歷 MYSQL_FIELD 計算各欄顯示寬度 = max(strlen(name), max_length, strlen("NULL"))
  2. 列印表頭:| %-*s | 格式
  3. 列印分隔線:+---+---+
  4. 逐列列印資料:數值右對齊、字串左對齊、NULL 顯示 NULL
  5. 列印列數統計
// 計算欄寬
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);

預處理優勢:

  • SQL 注入免疫(參數與碼分離)
  • 重複執行只解析一次,效能提升
  • 二進位參數無需編碼,直接傳輸
  • 伺服器端快取執行計畫

九、錯誤處理與資源管理鐵律

  1. 每個 API 呼叫檢查回傳值,失敗即 mysql_error(conn) / mysql_stmt_error(stmt)
  2. mysql_free_result、mysql_stmt_close、mysql_close 必須成對呼叫,防止記憶體洩漏
  3. 長期運行程式:定期 mysql_ping 檢測連線存活,斷線自動重連
  4. 多執行緒:每緒獨立 MYSQL* 連線物件,不可跨緒共用

結語

MySQL C API 雖然介面樸素,但提供了完全控制查詢執行、資料傳輸、記憶體管理、預處理快取的底層能力。掌握 store_result/use_result 選擇準則、MYSQL_ROW 二進位安全處理、元資料驅動的動態格式化、預處理語句完整流程,是編寫健壯、高效能資料庫應用的核心技能。建議將上述通用查詢處理器、格式化輸出、預處理封裝整理為專案內部資料庫存取庫,統一錯誤處理、資源管理、日誌記錄標準,避免重複造輪子並降低維護成本。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved