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

從零構建 MySQL 交互式查詢客戶端:C 語言實戰指南

     編輯:MySQL 資料庫相關知識

前言

構建一個簡易的 MySQL 交互式查詢客戶端,是理解 MySQL C API 協議流程、結果集處理、錯誤恢復的最佳實踐。本文基於經典的「客戶端程式框架」演進思路,引導你從 mysql_init() 到完整的 REPL (Read-Eval-Print Loop) 實現,涵蓋查詢執行、結果集格式化顯示、記憶體管理等核心環節。

核心架構圖


+------------------+     +------------------+     +------------------+
|  初始化階段       |     |  事件循環         |     |  清理階段         |
|  mysql_init()    |---->|  while(1) {      |---->|  mysql_close()   |
|  mysql_options() |     |   read_line()    |     |  free_result()   |
|  mysql_real_     |     |   if(quit) break;|     |                  |
|  connect()       |     |   mysql_query()  |     +------------------+
+------------------+     |   process_res()  |           ^
                         |   print_table()  |           |
                         +------------------+           |
                                    |                   |
                                    v                   |
                            +------------------+        |
                            | 錯誤處理分支      |--------+
                            | mysql_errno()    |
                            | mysql_error()    |
                            +------------------+

關鍵數據結構

typedef struct {
    MYSQL *conn;           // 連線句柄
    MYSQL_RES *result;     // 結果集
    MYSQL_ROW row;         // 當前行
    unsigned int num_fields; // 欄位數
    MYSQL_FIELD *fields;   // 欄位元數據
    char query_buf[8192];  // 查詢緩衝區
} ClientContext;

核心函數實現

1. 連線建立與配置

MYSQL *establish_connection(const char *host, const char *user, 
                            const char *pass, const char *db, unsigned int port) {
    MYSQL *mysql = mysql_init(NULL);
    if (!mysql) return NULL;

    // 關鍵選項
    mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, (char[]){10});
    mysql_options(mysql, MYSQL_SET_CHARSET_NAME, "utf8mb4");
    mysql_options(mysql, MYSQL_OPT_RECONNECT, (char[]){1});  // 自動重連

    unsigned long flags = CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS;

    if (!mysql_real_connect(mysql, host, user, pass, db, port, NULL, flags)) {
        fprintf(stderr, "Connect failed: %s\n", mysql_error(mysql));
        mysql_close(mysql);
        return NULL;
    }
    return mysql;
}

2. 查詢執行與錯誤處理


int execute_query(MYSQL *mysql, const char *query) {
    if (mysql_query(mysql, query)) {
        fprintf(stderr, "Query failed [%d]: %s\n", mysql_errno(mysql), mysql_error(mysql));
        // 針對特定錯誤碼嘗試重連
        if (mysql_errno(mysql) == CR_SERVER_GONE_ERROR || 
            mysql_errno(mysql) == CR_SERVER_LOST) {
            fprintf(stderr, "Attempting reconnect...\n");
            if (mysql_ping(mysql) == 0) {
                return mysql_query(mysql, query) ? -1 : 0; // 重試一次
            }
        }
        return -1;
    }
    return 0;
}

3. 結果集處理與格式化輸出 (核心難點)


void process_and_print_result(MYSQL *mysql) {
    MYSQL_RES *result = mysql_store_result(mysql); // 緩衝全量結果集
    // mysql_use_result() 為流式讀取,適合超大結果集,但鎖定連線

    if (!result) {
        // 非 SELECT 語句 (INSERT/UPDATE/DDL) 或錯誤
        if (mysql_field_count(mysql) == 0) {
            printf("Query OK, %lu rows affected. (Insert ID: %lu)\n", 
                   mysql_affected_rows(mysql), mysql_insert_id(mysql));
        } else {
            fprintf(stderr, "Error retrieving result set: %s\n", mysql_error(mysql));
        }
        return;
    }

    unsigned int num_fields = mysql_num_fields(result);
    MYSQL_FIELD *fields = mysql_fetch_fields(result);

    // 1. 計算每列最大寬度 (含標題)
    unsigned int *col_width = calloc(num_fields, sizeof(unsigned int));
    for (unsigned int i = 0; i < num_fields; i++) {
        col_width[i] = strlen(fields[i].name);
    }

    MYSQL_ROW row;
    // 暫存所有行以計算寬度 (大結果集慎用,或分兩遍)
    MYSQL_ROW *rows = NULL;
    unsigned long row_count = 0;
    while ((row = mysql_fetch_row(result))) {
        rows = realloc(rows, (row_count + 1) * sizeof(MYSQL_ROW));
        rows[row_count++] = row;
        for (unsigned int i = 0; i < num_fields; i++) {
            unsigned int len = row[i] ? strlen(row[i]) : 4; // NULL 顯示為 "NULL"
            if (len > col_width[i]) col_width[i] = len;
        }
    }

    // 2. 打印分隔線
    auto print_separator = [&]() {
        printf("+");
        for (unsigned int i = 0; i < num_fields; i++) {
            for (unsigned int w = 0; w < col_width[i] + 2; w++) printf("-");
            printf("+");
        }
        printf("\n");
    };

    // 3. 打印標題
    print_separator();
    printf("|");
    for (unsigned int i = 0; i < num_fields; i++) {
        printf(" %-*s |", col_width[i], fields[i].name);
    }
    printf("\n");
    print_separator();

    // 4. 打印數據行
    for (unsigned long r = 0; r < row_count; r++) {
        row = rows[r];
        printf("|");
        for (unsigned int i = 0; i < num_fields; i++) {
            const char *val = row[i] ? row[i] : "NULL";
            printf(" %-*s |", col_width[i], val);
        }
        printf("\n");
    }
    print_separator();
    printf("%lu rows in set.\n", row_count);

    // 5. 釋放資源
    free(rows);
    free(col_width);
    mysql_free_result(result);
}

4. 主事件循環 (REPL)


void run_interactive_loop(MYSQL *mysql) {
    char line[8192];
    printf("MySQL Interactive Client (type 'exit' or 'quit' or Ctrl+D to quit)\n");
    printf("mysql> ");

    while (fgets(line, sizeof(line), stdin)) {
        // 移除換行符
        line[strcspn(line, "\n")] = 0;

        // 跳過空行
        if (strlen(line) == 0) {
            printf("mysql> ");
            continue;
        }

        // 退出命令
        if (strcasecmp(line, "exit") == 0 || strcasecmp(line, "quit") == 0) {
            break;
        }

        // 執行查詢
        if (execute_query(mysql, line) == 0) {
            process_and_print_result(mysql);
            
            // 處理多語句結果集
            while (mysql_more_results(mysql) && mysql_next_result(mysql) == 0) {
                process_and_print_result(mysql);
            }
        }

        printf("mysql> ");
    }
    printf("\nBye!\n");
}

5. 完整 main() 入口


int main(int argc, char *argv[]) {
    // 簡化:實際應從配置檔/環境變數/命令列讀取
    const char *host = "localhost";
    const char *user = "root";
    const char *pass = getenv("MYSQL_PWD"); // 從環境變數讀取密碼,避免進程列表洩露
    const char *db = "test";
    unsigned int port = 3306;

    if (!pass) {
        fprintf(stderr, "Please set MYSQL_PWD environment variable.\n");
        return 1;
    }

    MYSQL *mysql = establish_connection(host, user, pass, db, port);
    if (!mysql) return 1;

    run_interactive_loop(mysql);

    mysql_close(mysql);
    return 0;
}

編譯與鏈接

# 安裝開發庫
# Ubuntu/Debian: apt-get install libmysqlclient-dev
# CentOS/RHEL: yum install mysql-devel

# 編譯
gcc -o mysql_client mysql_client.c $(mysql_config --cflags --libs)

# 運行
MYSQL_PWD=your_password ./mysql_client

進階擴展方向

  1. 命令歷史與補全:整合 readline / libedit 庫。
  2. 流式結果集處理:大數據導出場景改用 mysql_use_result() + 分塊寫入文件,避免記憶體暴漲。
  3. Prepared Statements:支援參數化查詢,防注入、提升重複執行效能。
  4. SSL/TLS 加密:mysql_ssl_set() 配置客戶端憑證。
  5. 異步非阻塞:mysql_real_connect_nonblocking() + poll/epoll 實現高併發客戶端。
  6. 插件化架構:支援自定義輸出格式(CSV、JSON、XML、HTML)、自定義命令(\d、\dt 等)。

結語

從零構建 MySQL 交互式客戶端,不僅是 API 調用的堆疊,更是對「連線生命週期、結果集緩衝策略、錯誤恢復機制、記憶體安全」的綜合考驗。本文提供的完整代碼骨架已包含生產級關鍵點:自動重連、多結果集處理、動態列寬格式化、安全密碼讀取。在此基礎上擴展,即可開發出適合嵌入式設備、自動化測試框架、資料庫中間件代理等場景的專用客戶端工具。

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