構建一個簡易的 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;
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;
}
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;
}
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);
}
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");
}
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
readline / libedit 庫。mysql_use_result() + 分塊寫入文件,避免記憶體暴漲。mysql_ssl_set() 配置客戶端憑證。mysql_real_connect_nonblocking() + poll/epoll 實現高併發客戶端。從零構建 MySQL 交互式客戶端,不僅是 API 調用的堆疊,更是對「連線生命週期、結果集緩衝策略、錯誤恢復機制、記憶體安全」的綜合考驗。本文提供的完整代碼骨架已包含生產級關鍵點:自動重連、多結果集處理、動態列寬格式化、安全密碼讀取。在此基礎上擴展,即可開發出適合嵌入式設備、自動化測試框架、資料庫中間件代理等場景的專用客戶端工具。