libmysqlclient-dev介紹(Linux 下的 Mysql C/C++ 開發包)
libmysqlclient-dev
是一個開發包,在Linux環境下為使用 MySQL C API 進行開發的c/c++程序員提供頭文件(如 mysql.h
)和靜態庫/動態庫的鏈接信息(如 libmysqlclient.so
)。
它是 MySQL 客戶端庫 libmysqlclient
的開發版本,不提供運行時功能,而是用于編譯和構建你的程序。
libmysqlclient-dev包含的內容:
類別 | 路徑或文件名 | 說明 |
---|---|---|
頭文件 | /usr/include/mysql/mysql.h | 主頭文件,聲明了所有 MySQL C API 接口函數,如 mysql_init() 等 |
/usr/include/mysql/mysql_version.h | 定義 MySQL 的版本號和宏,如 MYSQL_VERSION_ID | |
/usr/include/mysql/my_list.h | 定義鏈表結構,主要用于 MySQL 內部實現 | |
動態庫 | /usr/lib/x86_64-linux-gnu/libmysqlclient.so | 動態鏈接庫,編譯時使用 -lmysqlclient 鏈接,運行時動態加載 |
靜態庫 | /usr/lib/x86_64-linux-gnu/libmysqlclient.a | 靜態鏈接庫,將庫打包進可執行文件中(較少使用) |
搜索路徑 | /usr/include/mysql/ | MySQL C API 所有頭文件所在目錄,編譯時通常用 -I 指定 |
符號鏈接 | /usr/lib/x86_64-linux-gnu/libmysqlclient.so -> libmysqlclient.so.X.Y.Z | 指向具體版本庫文件的符號鏈接,供 -lmysqlclient 查找使用 |
libmysqlclient-dev的安裝
sudo apt install libmysqlclient-dev
安裝成功后的簡單示例:
test_mysql.c
:
#include <mysql/mysql.h>
#include <stdio.h>int main() {MYSQL* conn = mysql_init(NULL);if (!conn) {printf("Init failed\n");return 1;}printf("libmysqlclient 初始化成功\n");mysql_close(conn);return 0;
}
編譯:
gcc test_mysql.c -o test_mysql -lmysqlclient
<mysql.h>介紹
<mysql.h>
是MySQL C API(libmysqlclient)的主頭文件,它提供了訪問 MySQL 數據庫服務器所需的全部函數、宏、結構體和常量。
它是 C/C++ 中與 MySQL 數據庫交互的底層接口,你可以用它完成:
-
建立數據庫連接
-
執行 SQL 查詢
-
獲取查詢結果
-
處理錯誤
-
管理事務等
<mysql.h>中的結構體
常用結構體簡介
結構體名 | 類型 | 作用描述 | 使用場景舉例 |
---|---|---|---|
MYSQL | 連接對象 | 表示一個與數據庫的連接句柄,初始化、連接、關閉等操作都依賴它 | mysql_init → mysql_real_connect → 查詢/斷開 |
MYSQL_RES | 結果集對象 | 表示一次查詢后的結果集(SELECT 返回的所有數據) | mysql_store_result / mysql_use_result 得到的結果集 |
MYSQL_ROW | 行數據 | 表示查詢結果中的一行,每一列是一個 char* ,組成一個 char** | 遍歷結果集時使用 mysql_fetch_row 得到每一行 |
MYSQL_FIELD | 字段描述 | 表示表結構中一列的元信息(列名、類型、長度等) | 查詢字段信息用 mysql_fetch_fields() 獲取數組 |
MYSQL_STMT | 預處理語句句柄 | 表示一條準備執行的 SQL 預處理語句(prepared statement) | 用于 mysql_stmt_prepare , mysql_stmt_execute 等流程 |
MYSQL_BIND | 參數綁定對象 | 用于綁定預處理語句的輸入參數或輸出結果列 | 配合 mysql_stmt_bind_param / mysql_stmt_bind_result 使用 |
MYSQL_TIME | 時間結構體 | 表示日期和時間,適用于綁定時間類型字段 | 在 MYSQL_BIND 中處理 DATETIME / DATE 等類型字段 |
MYSQL
MYSQL
?表示一個數據庫連接句柄。它包含了與數據庫交互的所有必要信息,包括連接參數、狀態、通信通道等。通過 MYSQL
結構體,開發者可以管理數據庫連接、執行查詢、獲取結果等。
MYSQL結構體的定義
typedef struct MYSQL {NET net; // 通信參數unsigned char *connector_fd; // 用于 SSL 的連接器文件描述符char *host; // 主機名char *user; // 用戶名char *passwd; // 密碼char *unix_socket; // Unix 域套接字路徑char *server_version; // 服務器版本char *host_info; // 主機信息char *info; // 執行信息char *db; // 當前選擇的數據庫struct CHARSET_INFO *charset;// 字符集信息MYSQL_FIELD *fields; // 字段數組指針struct MEM_ROOT *field_alloc;// 用于字段分配的內存池uint64_t affected_rows; // 最近一次操作影響的行數uint64_t insert_id; // 插入操作返回的自增IDuint64_t extra_info; // 未使用的擴展信息unsigned long thread_id; // 服務器端的線程IDunsigned long packet_length; // 包長度unsigned int port; // 服務器端口unsigned long client_flag; // 客戶端標志unsigned long server_capabilities; // 服務器能力標志unsigned int protocol_version; // 協議版本unsigned int field_count; // 字段數量unsigned int server_status; // 服務器狀態unsigned int server_language; // 服務器使用的語言unsigned int warning_count; // 警告數量struct st_mysql_options options; // 客戶端連接選項enum mysql_status status; // 當前連接狀態enum enum_resultset_metadata resultset_metadata; // 結果集元數據狀態bool free_me; // 是否在 mysql_close 時釋放bool reconnect; // 是否自動重連char scramble[SCRAMBLE_LENGTH + 1]; // 會話級隨機字符串LIST *stmts; // 所有預處理語句的鏈表const struct MYSQL_METHODS *methods; // 連接使用的方法集合void *thd; // 線程句柄(MySQL 內部線程上下文)bool *unbuffered_fetch_owner; // 指向 MYSQL_RES 或 MYSQL_STMT 中的標志位,用于取消未緩沖結果集void *extension; // 擴展字段,供插件或其他擴展使用
} MYSQL;
?示例:
int main() {MYSQL *conn = mysql_init(nullptr); // 初始化 MYSQL 結構體// 建立連接(參數:host, user, password, db, port, unix_socket, client_flag)if (!mysql_real_connect(conn, "localhost", "root", "123456", "testdb", 3306, nullptr, 0)) {std::cerr << "Connection failed: " << mysql_error(conn) << std::endl;return 1;}
}
此時的conn
| conn字段 描述
| ------------------ | -----------------------------------------------
| `host` | `localhost`:連接的主機名或 IP。
| `user` | `root`:連接使用的用戶名。
| `passwd` | `123456`:連接使用的密碼。
| `unix_socket` | `nullptr`:Unix socket 文件路徑(僅在使用 socket 連接時有值)。
| `port` | `3306`:連接的端口號。
| `protocol_version` | `10`:MySQL 協議版本。
MYSQL_TIME
MYSQL_TIME
結構體在 MySQL 客戶端庫中用于表示日期和時間數據。它包含了日期、時間、時區等信息,常用于存儲和處理 MySQL 數據庫中涉及日期、時間、時間戳等類型的數據。
MYSQL_TIME結構體的定義
typedef struct MYSQL_TIME {unsigned int year; // 年份unsigned int month; // 月份(1-12)unsigned int day; // 日(1-31)unsigned int hour; // 小時(0-23)unsigned int minute; // 分鐘(0-59)unsigned int second; // 秒數(0-59)unsigned long second_part; // 微秒部分(0-999999)bool neg; // 是否為負時間(僅限 TIME 類型)enum enum_mysql_timestamp_type time_type; // 時間類型(如 DATE、DATETIME、TIME)int time_zone_displacement; // 時區偏移(單位:秒)
} MYSQL_TIME;
MYSQL_FIELD
MYSQL_FIELD
結構體是 MySQL 客戶端庫中用于描述數據庫中表的字段(列)的元數據結構。它包含了關于數據庫表列的詳細信息,如列的名稱、數據類型、長度、是否允許為 NULL
等。這個結構體是執行查詢時,檢索列信息的關鍵部分。
MYSQL_RES結構體的定義
typedef struct MYSQL_FIELD {char *name; // 列名char *org_name; // 原始列名(如果使用了別名)char *table; // 所在表名(如果該列來源于表字段)char *org_table; // 原始表名(如果使用了表別名)char *db; // 所在數據庫名char *catalog; // 所在目錄名(一般為 "def")char *def; // 默認值(由 mysql_list_fields 設置)unsigned long length; // 列寬(創建時指定的長度)unsigned long max_length; // 當前查詢結果中該字段的最大長度unsigned int name_length; // 列名的長度unsigned int org_name_length; // 原始列名的長度unsigned int table_length; // 表名的長度unsigned int org_table_length; // 原始表名的長度unsigned int db_length; // 數據庫名的長度unsigned int catalog_length; // 目錄名的長度unsigned int def_length; // 默認值的長度unsigned int flags; // 字段標志(如是否為主鍵、自增等)unsigned int decimals; // 小數位數(針對浮點數類型)unsigned int charsetnr; // 使用的字符集編號enum enum_field_types type; // 字段類型(定義在 mysql_com.h 中)void *extension; // 擴展字段(用于內部用途)
} MYSQL_FIELD;
?假設我們有一個實際的數據庫表,名為 users
,它包含如下字段:
| id | name | age | created\_at |
| -- | ----- | --- | ------------------- |
| 1 | Alice | 30 | 2025-05-01 10:00:00 |
| 2 | Bob | 25 | 2025-05-02 14:30:00 |
?MYSQL_FIELD
結構體存儲的內容:
| `MYSQL_FIELD` 字段 | 描述
| ---------------- | --------------------------------
| `name` | "id"
| `org_name` | "id"
| `table` | "users"
| `org_table` | "users"
| `db` | "my\_database"
| `length` | 11 (表示列寬度,對于整數類型可以是字符寬度)
| `max_length` | 11 (最大值,可能是查詢返回時的最大寬度)
| `type` | `MYSQL_TYPE_LONG` (表示該列數據類型是長整型)
| `flags` | `NOT_NULL` (表明該列不允許為 `NULL`)
| `decimals` | 0
| `charsetnr` | `CHARSET_UTF8` (表示字符集)
MYSQL_RES
用于表示查詢結果的一個數據結構。它包含了從數據庫中查詢得到的所有結果集數據。在執行查詢操作時,MySQL 會返回一個 MYSQL_RES
對象,里面保存了查詢的字段信息、行數據以及與結果相關的其他信息。通過 MYSQL_RES
結構體,我們可以訪問到結果集的元數據(如列信息)以及具體的行數據。
MYSQL_RES結構體的定義
typedef struct MYSQL_RES {uint64_t row_count; // 查詢結果中的總行數MYSQL_FIELD *fields; // 指向字段(列)描述信息數組struct MYSQL_DATA *data; // 實際的查詢結果數據MYSQL_ROWS *data_cursor; // 當前讀取到的結果行指針unsigned long *lengths; // 當前行中每個字段的長度數組MYSQL *handle; // 對應的 MYSQL 連接句柄,用于非緩沖讀取const struct MYSQL_METHODS *methods; // 內部使用的方法表指針MYSQL_ROW row; // 當前讀取的行數據(非緩沖模式)MYSQL_ROW current_row; // 當前行緩沖區(緩沖模式)struct MEM_ROOT *field_alloc; // 用于字段描述信息的內存分配器unsigned int field_count, current_field; // 字段總數,當前字段索引bool eof; // 指示是否已到達結果集末尾(用于 mysql_fetch_row)bool unbuffered_fetch_cancelled; // 標記是否被 mysql_stmt_close 取消enum enum_resultset_metadata metadata; // 結果集的元數據類型void *extension; // 擴展字段(MySQL 內部使用)
} MYSQL_RES;
?假設我們有一個名為 users
的數據庫表,結構如下:
| id | name | age | created\_at |
| -- | ----- | --- | ------------------- |
| 1 | Alice | 30 | 2025-05-01 10:00:00 |
| 2 | Bob | 25 | 2025-05-02 14:30:00 |
?假設我們執行的 SQL 查詢:
SELECT id, name, age, created_at FROM users;
查詢結果如下:
| id | name | age | created\_at |
| -- | ----- | --- | ------------------- |
| 1 | Alice | 30 | 2025-05-01 10:00:00 |
| 2 | Bob | 25 | 2025-05-02 14:30:00 |
MYSQL_RES
結構體存儲的內容:
| `MYSQL_RES` 字段 | 描述 |
| ---------------------------- | ----------------------------------------------------------
| `row_count` | 2 (表示查詢結果中的行數,這里有 2 行數據) | `fields` | 指向一個包含 4 個 `MYSQL_FIELD` 結構體的數組,分別表示 `id`、 `name`、`age` 和 `created_at` 四個字段。 | `data_cursor` | 用于遍歷查詢結果的指針,指向當前行數據。 | `lengths` | 指向一個數組,存儲每一列數據的長度。例如:`[11, 50, 3, 19]`, 分別表示 `id`、`name`、`age` 和 `created_at`字段的字符長度。| `handle` | 指向當前 MySQL 連接的句柄,用于標識與 MySQL 數據庫的連接。 | `methods` | 指向包含處理查詢結果方法的結構體,如 `fetch_row`。 | `row` | 當使用非緩沖查詢時,指向當前行數據。 | `current_row` | 用于存儲當前讀取的行數據。 | `field_count` | 表示查詢結果中字段的數量,這里是 4。 | `eof` | 表示查詢結果是否已經讀取到末尾。當所有行數據都被讀取后`true`。 | `unbuffered_fetch_cancelled` | 如果查詢是未緩沖查詢且結果已被取消,則為 `true`。 | `metadata` | 用于存儲查詢結果集的元數據。
MYSQL_BIND
MYSQL_BIND
結構體主要用于處理 MySQL 準備語句(Prepared Statements)的輸入參數和輸出結果的綁定。通過使用 MYSQL_BIND
,你可以將輸入參數傳遞給 MySQL 并從數據庫中獲取查詢結果。它在與 MYSQL_STMT
結構體結合時,提供了參數綁定功能,用于執行具有參數化查詢的 SQL 語句。
作用:
-
輸入參數綁定:當執行
INSERT
、UPDATE
等操作時,MYSQL_BIND
結構體可以將 C 語言的變量與 SQL 語句的參數進行綁定。你將一個 C 語言的變量傳遞給 MySQL,MySQL 執行查詢時會使用這個變量的值。 -
輸出結果綁定:當執行
SELECT
查詢時,查詢結果的列可以通過MYSQL_BIND
結構體與 C 語言中的變量綁定,將查詢結果存儲到相應的變量中。
?MYSQL_BIND結構體的定義
typedef struct MYSQL_BIND {unsigned long *length; // 指向輸出數據長度的指針(用于接收時獲取實際長度)bool *is_null; // 指向是否為 NULL 的標志指針void *buffer; // 實際存放或讀取數據的緩沖區指針bool *error; // 如果在取數據時發生截斷,用于記錄是否出錯的標志指針unsigned char *row_ptr; // 當前數據在行緩沖區中的位置指針void (*store_param_func)(NET *net, struct MYSQL_BIND *param); // 發送參數到服務器時使用的函數指針void (*fetch_result)(struct MYSQL_BIND *, MYSQL_FIELD *, unsigned char **row); // 從服務器讀取結果時的處理函數void (*skip_result)(struct MYSQL_BIND *, MYSQL_FIELD *, unsigned char **row); // 跳過結果時的處理函數unsigned long buffer_length; // buffer 的長度(取字符串或二進制類型時必須設置)unsigned long offset; // 用于字符串或二進制類型的偏移讀取位置unsigned long length_value; // 如果 length 指針為空,則使用此值表示長度unsigned int param_number; // 參數編號(用于錯誤消息或統計)unsigned int pack_length; // 內部使用的打包數據長度enum enum_field_types buffer_type; // 緩沖區中數據的類型(如 MYSQL_TYPE_STRING 等)bool error_value; // 當 error 為 NULL 時使用此字段表示錯誤bool is_unsigned; // 若為無符號整數類型,則此字段為 truebool long_data_used; // 如果調用過 mysql_send_long_data,則此字段為 truebool is_null_value; // 當 is_null 為 NULL 時使用此字段表示是否為 NULLvoid *extension; // 擴展字段,供 MySQL 內部使用
} MYSQL_BIND;
?輸入參數綁定示例:
在這個例子中,我們將數據插入到一個名為 users
的表中。假設 users
表有兩個字段:id
和 name
。我們通過 MYSQL_BIND
結構體綁定輸入參數。
| id | name |
| -- | ----- |
| 1 | Alice |
int main() {MYSQL *conn;MYSQL_STMT *stmt;MYSQL_BIND bind[2];// 假設已經連接到數據庫// 準備 SQL 查詢語句const char *query = "INSERT INTO users (id, name) VALUES (?, ?)";// 初始化 MYSQL_STMTstmt = mysql_stmt_init(conn);// 設置 id 和 name 的值unsigned long id = 2;char name[50] = "Bob";// 清空綁定數組memset(bind, 0, sizeof(bind));// 綁定 id 參數bind[0].buffer_type = MYSQL_TYPE_LONG; // 類型是 LONGbind[0].buffer = (char *)&id; // 綁定值bind[0].is_null = 0; // 表示參數非 NULL// 綁定 name 參數bind[1].buffer_type = MYSQL_TYPE_STRING; // 類型是 STRINGbind[1].buffer = (char *)name; // 綁定值bind[1].buffer_length = sizeof(name); // 字符串長度bind[1].is_null = 0; // 表示參數非 NULL// 將綁定的參數應用到語句中mysql_stmt_bind_param(stmt, bind);..........return 0;
}
?輸出參數綁定示例:?
在這個例子中,我們從名為 users
的表中查詢數據。假設 users
表有兩個字段:id
和 name
。我們將通過 MYSQL_BIND
結構體綁定輸出參數,并從查詢結果中獲取數據。
| id | name |
| -- | ----- |
| 1 | Alice |
int main() {MYSQL *conn;MYSQL_STMT *stmt;MYSQL_BIND bind[2]; // 綁定輸出參數// 假設已經連接到數據庫// 準備 SQL 查詢語句const char *query = "SELECT id, name FROM users WHERE id = 1";// 初始化 MYSQL_STMTstmt = mysql_stmt_init(conn);// 設置輸出變量unsigned long output_id;char output_name[50];// 清空綁定數組memset(bind, 0, sizeof(bind));// 綁定輸出參數 idbind[0].buffer_type = MYSQL_TYPE_LONG; // 類型是 LONGbind[0].buffer = (char *)&output_id; // 輸出的 id 參數bind[0].is_null = 0; // 非 NULLbind[0].length = 0;// 綁定輸出參數 namebind[1].buffer_type = MYSQL_TYPE_STRING; // 類型是 STRINGbind[1].buffer = (char *)output_name; // 輸出的 name 字符串bind[1].buffer_length = sizeof(output_name); // 輸出緩沖區長度bind[1].is_null = 0; // 非 NULLbind[1].length = 0;// 將綁定的輸出參數應用到語句中mysql_stmt_bind_result(stmt, bind); // 綁定輸出參數// 執行查詢mysql_stmt_execute(stmt);return 0;
}
此時bind[2]的結果是:
bind[0] | 描述 |
| ---------------------------- | --------------------------------------------------------- | `buffer_type` | `MYSQL_TYPE_LONG` (表示 `id` 字段的數據類型是 `LONG`,即整數 類型) | `buffer` | `&output_id` (指向存儲查詢結果的變量的指針,`output_id` 存儲 查詢結果中的 `id` 值) | `is_null` | `0` | `length` | `0` bind[1] | 描述 |
| ---------------------------- | --------------------------------------------------------| `buffer_type` | `MYSQL_TYPE_LONG` (表示 `id` 字段的數據類型是 `LONG`,即整 數類型) | `buffer` | `&output_id` (指向存儲查詢結果的變量的指針,`output_id` 存 儲查詢結果中的 `id` 值) | `is_null` | `0` | `length` | `0` | `buffer_length` | `0`
MYSQL_STMT
MYSQL_STMT
是一個結構體,用來操作預處理 SQL 語句()。你可以把它理解成一個“SQL 執行容器”,你把 SQL 丟進去,把參數綁上去,它幫你執行完,還能幫你取出結果。
舉個最直白的比喻:
想象你開了一家飯店,來了條訂單:
SELECT name, age FROM users WHERE id = ?
這時候:
-
MYSQL_STMT
就像是你廚房里的一口鍋-
你先用
mysql_stmt_prepare()
把 SQL(食譜)放進去,這叫“準備鍋”; -
然后你往鍋里加料(
mysql_stmt_bind_param()
把參數比如id=100
填進去); -
再點火(
mysql_stmt_execute()
執行); -
然后把煮出來的東西(查詢結果)裝盤(
mysql_stmt_bind_result()
和mysql_stmt_fetch()
); -
用完后洗鍋(
mysql_stmt_close()
)。
-
它具體做了哪些事?
操作名稱 | 簡單解釋 |
---|---|
mysql_stmt_prepare | 把 SQL 語句編譯好,準備執行(比如預編譯了 SELECT ... WHERE id = ? ) |
mysql_stmt_bind_param | 把你的輸入參數(例如 id=1 )綁定上 |
mysql_stmt_execute | 執行語句 |
mysql_stmt_bind_result | 把輸出字段綁定到你提供的變量上 |
mysql_stmt_fetch | 把結果讀到你綁定的變量里 |
mysql_stmt_close | 清理資源、關閉語句 |
?MYSQL_STMT結構體的定義
typedef struct MYSQL_STMT {struct MEM_ROOT *mem_root; /* 內存分配的根指針 */LIST list; /* 用于跟蹤所有語句的列表 */MYSQL *mysql; /* MySQL 連接句柄 */MYSQL_BIND *params; /* 輸入參數的綁定 */MYSQL_BIND *bind; /* 輸出參數的綁定 */MYSQL_FIELD *fields; /* 結果集元數據 */MYSQL_DATA result; /* 緩存的結果集 */MYSQL_ROWS *data_cursor; /* 當前行在緩存數據中的位置 *//*mysql_stmt_fetch() 調用此函數以提取一行數據(不同的提取方式:緩沖、非緩沖、游標提取)。*/int (*read_row_func)(struct MYSQL_STMT *stmt, unsigned char **row); /* 用于提取當前行數據的函數指針 *//* 語句執行后的 mysql->affected_rows 的副本 */uint64_t affected_rows;uint64_t insert_id; /* mysql->insert_id 的副本 */unsigned long stmt_id; /* 預處理語句的 ID */unsigned long flags; /* 游標類型等標志 */unsigned long prefetch_rows; /* 每次 COM_FETCH 提取的行數 *//*從執行/提取后 mysql->server_status 復制,用于獲取服務器端游標狀態。*/unsigned int server_status;unsigned int last_errno; /* 錯誤碼 */unsigned int param_count; /* 輸入參數的數量 */unsigned int field_count; /* 結果集的列數 */enum enum_mysql_stmt_state state; /* 語句狀態 */char last_error[MYSQL_ERRMSG_SIZE]; /* 錯誤信息 */char sqlstate[SQLSTATE_LENGTH + 1]; /* SQL 錯誤狀態碼 *//* 輸入參數類型是否已發送到服務器 */bool send_types_to_server;bool bind_param_done; /* 輸入緩沖區已提供 */unsigned char bind_result_done; /* 輸出緩沖區已提供 *//* 如果 mysql_stmt_close() 必須取消此結果,則為 true */bool unbuffered_fetch_cancelled;/*如果需要在執行 mysql_stmt_store_result 時計算 field->max_length,則此字段設置為 true。*/bool update_max_length;struct MYSQL_STMT_EXT *extension; /* 擴展字段,供 MySQL 內部使用 */
} MYSQL_STMT;
示例:
//假設已經連接好了
// 初始化語句
MYSQL_STMT *stmt = mysql_stmt_init(conn);// 準備 SQL(? 是參數占位符)
const char *sql = "SELECT name FROM users WHERE id = ?";
mysql_stmt_prepare(stmt, sql, strlen(sql));// 輸入參數綁定
MYSQL_BIND param;
int user_id = 1;
memset(¶m, 0, sizeof(param));
param.buffer_type = MYSQL_TYPE_LONG;
param.buffer = &user_id;
mysql_stmt_bind_param(stmt, ¶m);// 執行語句
mysql_stmt_execute(stmt);// 結果綁定
MYSQL_BIND result;
char name[50];
unsigned long length;
memset(&result, 0, sizeof(result));
result.buffer_type = MYSQL_TYPE_STRING;
result.buffer = name;
result.buffer_length = sizeof(name);
result.length = &length;
mysql_stmt_bind_result(stmt, &result);// 釋放資源
mysql_stmt_close(stmt);
mysql_close(conn);
此時的stmt為:
| stmt | 示例值 / 狀態 | 描述
| -------------------- | --------------------------------| ---------------------------- |
| `mysql` | 指向連接句柄(非空) | 當前已連接到 MySQL 數據庫。 | `query` | `"SELECT name FROM users WHERE id = ?"` | 原始 SQL 查詢語句。 | `param_count` | `1` | 只有一個輸入參數,即 `id`。 | `bind_param` | 指向綁定了 `user_id = 1` 的 `MYSQL_BIND` 結構體 | 輸入參數已綁定。 | `bind_result` | 指向綁定了 `name` 輸出緩沖區的 `MYSQL_BIND` 結構體 | 輸出參數已綁定。 | `state` | `STMT_EXECUTED` | 表示語句已執行。 |
| `insert_id` | `0`(無插入) | 對 INSERT 語句有效。 |
| `error / last_errno` | `0`(無錯誤) | 若執行出錯將有錯誤碼和信息。 |
| `data_cursor` | 指向當前行數據(非空) | `mysql_stmt_fetch` 后用于遍歷結果行。
| `read_buffer` | 內部分配 | 存儲查詢返回的行數據內容。 |
<mysql.h>中常用的方法
1. 初始化與連接管理
mysql_init
用于初始化一個 MYSQL
結構體,為后續建立數據庫連接做準備。它是使用 MySQL C API 的第一步。
MYSQL* mysql_init(MYSQL* mysql);
參數:
?mysql
:一個 MYSQL
指針,可以是已分配的結構體,也可以傳 NULL
(推薦傳 NULL,由庫自動分配)。
返回值:
-
成功:返回初始化后的
MYSQL *
指針。 -
失敗:返回
NULL
(通常是因為內存不足)。
mysql_real_connect
建立一個真實的數據庫連接。需要傳入主機地址、用戶名、密碼、數據庫名等信息。
MYSQL *mysql_real_connect(MYSQL *mysql,const char *host,const char *user,const char *passwd,const char *db,unsigned int port,const char *unix_socket,unsigned long client_flag
);
參數:
-
mysql
:由mysql_init()
初始化的連接句柄。 -
host
:數據庫服務器地址,如"localhost"
。 -
user
:用戶名。 -
passwd
:密碼。 -
db
:默認連接的數據庫名(可為NULL
)。 -
port
:端口號,默認 MySQL 為 3306。 -
unix_socket
:Unix socket 文件路徑,Linux 中可用,Windows 可傳NULL
。 -
client_flag
:用于控制連接行為的標志位,如:-
0
:默認連接方式。 -
CLIENT_MULTI_STATEMENTS
:支持多語句執行。 -
CLIENT_SSL
:啟用 SSL 等。
-
返回值:
-
成功:返回
MYSQL *
指針,表示連接成功。 -
失敗:返回
NULL
,調用mysql_error()
查看錯誤信息。
mysql_close
用于關閉一個已建立的數據庫連接并釋放資源,通常在程序結束時調用。
void mysql_close(MYSQL *mysql);
參數:
-
mysql
:由mysql_init()
或mysql_real_connect()
獲得的連接句柄。
返回值:
-
無返回值,調用后連接資源被釋放。
mysql_error
當連接或操作失敗時,用于獲取最近發生的錯誤信息(字符串形式)。
const char *mysql_error(MYSQL *mysql);
參數:
-
mysql
:連接句柄。
返回值:
-
返回最近一次 MySQL 操作失敗的錯誤信息字符串。
示例
#include <mysql/mysql.h>
#include <iostream>int main() {// 初始化連接MYSQL *conn = mysql_init(NULL);if (conn == NULL) {std::cerr << "mysql_init() failed\n";return 1;}// 建立連接if (mysql_real_connect(conn, "localhost", "root", "123456", "testdb", 3306, NULL, 0) == NULL) {std::cerr << "mysql_real_connect() failed: " << mysql_error(conn) << "\n";mysql_close(conn); // 即使連接失敗,也要釋放句柄return 1;}std::cout << "Connected to database successfully!" << std::endl;// 關閉連接mysql_close(conn);return 0;
}
mysql_options
用于在連接建立之前為MYSQL*連接句柄設置參數,比如超時、字符集、重連等。
int mysql_options(MYSQL *mysql, enum mysql_option option, const void *arg);
參數:
-
mysql
:連接句柄(mysql_init
初始化后)。 -
option
:選項類型,如:-
MYSQL_OPT_CONNECT_TIMEOUT
-
MYSQL_SET_CHARSET_NAME
-
這里不一一列出,具體參考mysql.h中的定義
-
-
arg
:指向具體的設置值,如字符集名"utf8mb4"
。
返回值:
-
0:成功。
-
非 0:失敗。
mysql_set_character_set
連接成功后,設置當前連接使用的字符集。
int mysql_set_character_set(MYSQL *mysql, const char *csname);
參數:
-
mysql
:連接句柄。 -
csname
:字符集名稱,如"utf8mb4"
。
返回值:
-
0:成功。
-
非 0:失敗。
mysql_get_character_set_info
獲取當前連接的字符集信息,包括名字、說明、最大字節數等。
const MYSQL_CHARSET_INFO *mysql_get_character_set_info(MYSQL *mysql);
參數:
-
mysql
:連接句柄。
返回值:
返回一個指向 MYSQL_CHARSET_INFO
的結構體指針,結構體包含如下字段:
struct charset_info_st {unsigned int number;unsigned int state;const char *csname; // 字符集名,如 "utf8mb4"const char *name; // 同上const char *comment; // 注釋,如 "UTF-8 Unicode"const char *dir; // 編碼文件路徑(客戶端庫)unsigned int mbminlen; // 最小字節數unsigned int mbmaxlen; // 最大字節數...
};
mysql_ping
測試當前連接是否有效;無效則嘗試自動重連(如果啟用自動重連)。
int mysql_ping(MYSQL *mysql);
參數:
-
mysql
:連接句柄。
返回值:
-
返回
0
表示連接有效 -
返回非零表示連接斷開或檢測失敗
mysql_thread_id
獲取當前連接在服務器端的線程 ID,可用于日志記錄或跟蹤問題。
unsigned long mysql_thread_id(MYSQL *mysql);
參數:
-
mysql
:連接句柄。
返回值:
-
返回該連接在服務器上的線程 ID
mysql_get_client_info
獲取當前使用的 MySQL 客戶端庫的版本號,返回字符串形式。例如 "8.0.33"
。
const char *mysql_get_client_info(void);
mysql_get_server_info
獲取當前連接的 MySQL 服務器版本號(即遠程服務器端的版本),返回字符串。
const char *mysql_get_server_info(MYSQL *mysql);
示例:
#include <mysql/mysql.h>
#include <iostream>int main() {MYSQL *conn = mysql_init(NULL);if (!conn) {std::cerr << "mysql_init failed\n";return 1;}// 設置連接超時時間(3秒)int timeout = 3;mysql_options(conn, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);// 設置連接前字符集(也可連接后使用 mysql_set_character_set 設置)const char *charset = "utf8mb4";mysql_options(conn, MYSQL_SET_CHARSET_NAME, charset);// 建立連接if (!mysql_real_connect(conn, "localhost", "root", "123456", "testdb", 3306, NULL, 0)) {std::cerr << "Connect failed: " << mysql_error(conn) << "\n";mysql_close(conn);return 1;}// 設置連接后字符集mysql_set_character_set(conn, "utf8mb4");// 打印連接信息std::cout << "Client version: " << mysql_get_client_info() << "\n";std::cout << "Server version: " << mysql_get_server_info(conn) << "\n";// 獲取字符集信息const MYSQL_CHARSET_INFO *cs = mysql_get_character_set_info(conn);std::cout << "Character set: " << cs->csname << ", comment: " << cs->comment << "\n";// 檢查連接是否有效if (mysql_ping(conn) == 0) {std::cout << "Ping successful. Connection is alive.\n";}// 打印線程 IDstd::cout << "Connection thread ID: " << mysql_thread_id(conn) << "\n";mysql_close(conn);return 0;
}
mysql_shutdown
此函數用于關閉 MySQL 服務器。必須具有管理員權限來執行此操作。
int mysql_shutdown(MYSQL *mysql, unsigned int shutdown_level, const char *shutdown_message);
參數:
-
mysql
:已連接的MYSQL
句柄。 -
shutdown_level
:關閉級別:-
SHUTDOWN_DEFAULT
:常規關閉。 -
SHUTDOWN_WAIT
:稍微延遲關閉(避免連接斷開)。 -
SHUTDOWN_GRACEFUL
:平滑關閉(防止斷開已建立的連接)。
-
-
shutdown_message
:可選的關閉消息(可為 NULL)。
返回值:
-
成功:返回 0。
-
失敗:返回非 0,調用
mysql_error()
獲取錯誤信息。
mysql_refresh
刷新 MySQL 服務器的緩存或狀態。常用于刷新日志、權限或其他內部狀態。
int mysql_refresh(MYSQL *mysql, unsigned int refresh_options);
參數說明:
-
mysql
:已連接的MYSQL
句柄。 -
refresh_options
:刷新選項,組合為 OR:-
REFRESH_GRANT
:刷新權限。 -
REFRESH_LOG
:刷新二進制日志。 -
REFRESH_TABLES
:刷新表緩存。 -
REFRESH_HOSTS
:刷新主機緩存。 -
REFRESH_STATUS
:刷新狀態信息。
-
返回值:
-
成功返回 0,失敗返回非 0。
mysql_kill
終止 MySQL 服務器中的線程。此操作需要管理員權限。
int mysql_kill(MYSQL *mysql, unsigned long thread_id);
參數說明:
-
mysql
:已連接的MYSQL
句柄。 -
thread_id
:要終止的線程 ID。
返回值:
-
成功:返回 0。
-
失敗:返回非 0,調用
mysql_error()
獲取錯誤信息。
mysql_stat
獲取 MySQL 服務器的狀態信息。此函數返回一系列關于服務器運行狀態的統計數據。
char *mysql_stat(MYSQL *mysql);
參數:
-
mysql
:已連接的MYSQL
句柄。
返回值:
-
返回服務器狀態信息的字符串。
-
失敗時返回 NULL,調用
mysql_error()
獲取錯誤信息。
示例:
#include <mysql/mysql.h>
#include <cstdio>
#include <cstring>int main() {MYSQL *conn = mysql_init(NULL);if (!mysql_real_connect(conn, "localhost", "root", "password", "test_db", 0, NULL, 0)) {printf("Connection failed: %s\n", mysql_error(conn));return 1;}// 獲取服務器狀態char *status = mysql_stat(conn);if (status) {printf("Server Status: %s\n", status);} else {printf("Error retrieving server status: %s\n", mysql_error(conn));}// 刷新服務器(刷新權限、日志等)if (mysql_refresh(conn, REFRESH_GRANT | REFRESH_LOG)) {printf("Error refreshing server: %s\n", mysql_error(conn));} else {printf("Server refreshed successfully.\n");}// 假設要終止線程 ID 為 5if (mysql_kill(conn, 5)) {printf("Error killing thread: %s\n", mysql_error(conn));} else {printf("Thread killed successfully.\n");}// 關閉服務器if (mysql_shutdown(conn, SHUTDOWN_DEFAULT, "Shutting down server")) {printf("Error shutting down server: %s\n", mysql_error(conn));} else {printf("Server shut down successfully.\n");}mysql_close(conn);return 0;
}
2. 執行 SQL(直接執行)
mysql_query
用于向數據庫發送一條以 C 字符串形式的 SQL 查詢。適用于不包含二進制數據的簡單 SQL 命令(如 SELECT
、INSERT
、UPDATE
等)。
int mysql_query(MYSQL *mysql, const char *stmt_str);
參數:
-
mysql
:指向已初始化并連接成功的MYSQL
連接句柄。 -
stmt_str
:SQL 查詢語句,以 C 字符串形式提供。
返回值:
-
成功:返回
0
。 -
失敗:返回非零,可通過
mysql_error()
獲取錯誤信息。
mysql_real_query
和 mysql_query
類似,但可以處理二進制數據(包括內嵌的空字符 \0
),適用于更復雜的 SQL 語句。
int mysql_real_query(MYSQL *mysql, const char *stmt_str, unsigned long length);
參數:
-
mysql
:數據庫連接句柄。 -
stmt_str
:指向 SQL 查詢語句的指針。 -
length
:語句的字節長度(不一定是strlen(stmt_str)
,尤其當語句中含有\0
時)。
返回值:
-
成功:返回
0
。 -
失敗:返回非零,可用
mysql_error()
獲取錯誤信息。
mysql_escape_string
對字符串進行轉義,防止 SQL 注入攻擊。適用于未連接數據庫的簡單轉義需求。
為什么需要"轉義字符串"?
當拼接 SQL 語句時,如果直接將用戶輸入的數據寫入 SQL 字符串中,就可能導致 SQL 注入攻擊。
示例:
const char* username = "O'Reilly";
char query[256];
sprintf(query, "INSERT INTO users(name) VALUES('%s')", username);
?拼接結果是:
INSERT INTO users(name) VALUES('O'Reilly')
由于名字中含有 '
,SQL 會認為 'O'
是一個字符串,而 Reilly'
就變成了非法語法,這會導致 SQL 報錯,甚至黑客可以通過精心構造的字符串攻擊數據庫。
解決辦法:用 轉義函數 處理字符串mysql_escape_string
這個函數的作用就是將輸入字符串中的特殊字符(比如 '
, "
, \
, NULL
字節等)轉義成安全的形式,以防止拼接 SQL 時出錯或被攻擊。
?? 注意:這個函數不依賴數據庫連接,只進行簡單的 ASCII 級別轉義,不考慮字符集編碼問題,所以只能在最簡單的場景中使用。
函數原型:
unsigned long mysql_escape_string(char *to, const char *from, unsigned long length);
參數:
-
to
:目標緩沖區(用于保存轉義后的字符串)。 -
from
:原始字符串。 -
length
:原始字符串長度。
返回值:
-
返回寫入
to
的字節數(不包括終止符\0
)。
mysql_real_escape_string
與 mysql_escape_string
類似,但使用連接信息(如字符集)進行轉義,它需要一個已經建立連接的 MYSQL *
參數。更推薦使用。
unsigned long mysql_real_escape_string(MYSQL *mysql,char *to,const char *from,unsigned long length);
參數:
-
mysql
:數據庫連接句柄(用于獲取字符集信息)。 -
to
:目標緩沖區。 -
from
:原始字符串。 -
length
:原始字符串長度。
返回值:
-
返回寫入
to
的字節數。
示例:
#include <mysql/mysql.h>
#include <stdio.h>
#include <string.h>int main() {MYSQL *conn = mysql_init(NULL);if (!mysql_real_connect(conn, "localhost", "root", "123456", "testdb", 0, NULL, 0)) {fprintf(stderr, "Connection failed: %s\n", mysql_error(conn));return 1;}// 用戶輸入需要轉義const char *user_input = "O'Reilly";char escaped[256];mysql_real_escape_string(conn, escaped, user_input, strlen(user_input));// 拼接 SQL 語句char query[512];snprintf(query, sizeof(query), "INSERT INTO users(name) VALUES('%s')", escaped);// 使用 mysql_real_query 執行語句if (mysql_real_query(conn, query, strlen(query))) {fprintf(stderr, "Query failed: %s\n", mysql_error(conn));} else {printf("Insert successful.\n");}mysql_close(conn);return 0;
}
3. 結果集處理(MYSQL_RES / MYSQL_ROW)
第一組:結果集的獲取與釋放
mysql_store_result
從服務器讀取完整結果集并緩存在客戶端內存中。
注意:必須先執行過一條會返回結果的 SQL 語句,比如 SELECT
,然后才能調用 mysql_store_result()
來獲取“那條語句”的結果。
MYSQL_RES *mysql_store_result(MYSQL *mysql);
參數:
-
mysql
:已連接的 MySQL 句柄。
返回值:
-
成功:返回
MYSQL_RES *
指針。 -
無結果或失敗:返回
NULL
。用mysql_errno()
區分錯誤或無結果。
使用流程
mysql_query(conn, "SELECT id, name FROM users"); // 1. 先執行 SQL 查詢
MYSQL_RES* res = mysql_store_result(conn); // 2. 再獲取結果集
mysql_use_result
初始化結果集,但不緩存整個結果集,適用于大結果集,節省內存,按行讀取。
MYSQL_RES *mysql_use_result(MYSQL *mysql);
?參數:
-
mysql
:已經連接成功的 MYSQL 對象指針。
返回值:
-
成功:返回指向
MYSQL_RES
的指針。 -
失敗或無結果:返回
NULL
,可以用mysql_errno()
判斷原因。
注意事項:使用完后必須使用 mysql_free_result()
釋放,否則可能導致連接阻塞。
mysql_use_result()
與 mysql_store_result()
的最大區別是——它不會一次性從服務器獲取并緩存整個結果集,而是每次調用 mysql_fetch_row()
時,從服務器取一行。
也就是說:
-
t不緩存全部行 → 節省客戶端內存。
-
不能隨機訪問 → 不支持
mysql_data_seek()
和mysql_row_seek()
。 -
?調用期間不能執行新的 SQL 查詢 → 因為結果還“留”在服務器端。
#include <mysql/mysql.h>
#include <iostream>int main() {.........// 執行查詢語句if (mysql_query(conn, "SELECT id, name FROM users")) {std::cerr << "mysql_query failed: " << mysql_error(conn) << "\n";mysql_close(conn);return 1;}// 使用 mysql_use_result 獲取結果(逐行獲取,節省內存)MYSQL_RES *res = mysql_use_result(conn);if (!res) {std::cerr << "mysql_use_result failed: " << mysql_error(conn) << "\n";mysql_close(conn);return 1;}// 獲取列數unsigned int num_fields = mysql_num_fields(res);// 遍歷結果集MYSQL_ROW row;while ((row = mysql_fetch_row(res))) {for (unsigned int i = 0; i < num_fields; ++i) {std::cout << (row[i] ? row[i] : "NULL") << "\t";}std::cout << "\n";}// 清理mysql_free_result(res);return 0;
}
mysql_free_result
釋放由 mysql_store_result()
或 mysql_use_result()
獲取的結果集,避免內存泄漏。
void mysql_free_result(MYSQL_RES *result);
參數:
-
result
:指向MYSQL_RES
的結果集指針。
第二組:逐行讀取數據
mysql_fetch_row
從結果集中提取下一行數據。
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result);
參數:
-
result
:由mysql_store_result()
或mysql_use_result()
返回的結果集。
返回值:
-
成功:返回類型為
MYSQL_ROW
(實質為char **
)的指針,表示一行數據。 -
結束或無數據:返回
NULL
。
mysql_fetch_lengths
獲取當前行中每一列的數據長度(以字節為單位)。
unsigned long *mysql_fetch_lengths(MYSQL_RES *result);
參數:
-
result
:當前結果集,且必須在調用了mysql_fetch_row()
之后調用本函數。
返回值:
-
成功:返回一個
unsigned long
類型的數組,每個元素表示當前行對應列的長度。 -
失敗:返回
NULL
。
示例:
const char* query = "SELECT id, title, artist FROM songs";
if (mysql_query(conn, query)) {std::cerr << "Query failed: " << mysql_error(conn) << "\n";mysql_close(conn);return 1;
}MYSQL_RES *res = mysql_store_result(conn);
if (!res) {std::cerr << "mysql_store_result failed: " << mysql_error(conn) << "\n";mysql_close(conn);return 1;
}MYSQL_ROW row;while ((row = mysql_fetch_row(res))) {unsigned long *lengths = mysql_fetch_lengths(res);......
}
第三組:字段和元數據
mysql_num_rows
返回結果集的總行數。只適用于 mysql_store_result
。
參數:
-
res
:結果集指針。
返回值:
-
返回一個
my_ulonglong
類型的整數,表示結果集中的行數。
mysql_num_fields
返回結果集中字段(列)的數量。
unsigned int mysql_num_fields(MYSQL_RES *res);
參數:
-
res
:結果集指針。
返回值:
-
字段總數(列數)。
mysql_fetch_field
逐個提取字段的結構體信息,如字段名、類型、長度等。
MYSQL_FIELD *mysql_fetch_field(MYSQL_RES *result);
參數:
-
result
:結果集指針。
返回值:
-
成功:返回
MYSQL_FIELD*
。 -
所有字段遍歷完畢:返回
NULL
。
mysql_fetch_fields
一次性返回所有字段信息的數組。
MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *res);
參數:
-
res
:結果集指針。
返回值:
-
成功:返回字段數組首地址(
MYSQL_FIELD*
)。 -
失敗:返回
NULL
。
mysql_fetch_field_direct
根據索引直接獲取字段信息。
MYSQL_FIELD *mysql_fetch_field_direct(MYSQL_RES *res, unsigned int fieldnr);
參數:
-
res
:結果集指針。 -
fieldnr
:字段索引(從 0 開始)。
返回值:
-
返回對應索引位置的字段信息指針。
mysql_field_seek
設置字段遍歷的偏移量,下一次 mysql_fetch_field()
會從該偏移開始。
MYSQL_FIELD_OFFSET mysql_field_seek(MYSQL_RES *res, MYSQL_FIELD_OFFSET offset);
參數:
-
res
:結果集指針。 -
offset
:字段偏移值(可由mysql_field_tell()
獲取)。
返回值:
-
返回設置前的位置(類型為
MYSQL_FIELD_OFFSET
)。
mysql_field_tell
獲取當前字段指針的位置,可用于后續調用 mysql_field_seek()
返回到當前位置。
MYSQL_FIELD_OFFSET mysql_field_tell(MYSQL_RES *res);
參數:
-
res
:結果集指針。
返回值:
-
返回當前字段位置偏移量。
示例:
| id | title | artist |
| -- | --------- | ----------- |
| 1 | Yesterday | The Beatles |
| 2 | Hello | Adele |
| 3 | Imagine | John Lennon |
#include <mysql/mysql.h>
#include <iostream>
#include <cstdlib>int main() {MYSQL *conn = mysql_init(nullptr);if (!conn) {std::cerr << "mysql_init failed\n";return 1;}// 建立連接if (!mysql_real_connect(conn, "localhost", "root", "123456", "test_db", 0, nullptr, 0)) {std::cerr << "Connection failed: " << mysql_error(conn) << "\n";return 1;}// 查詢語句const char* query = "SELECT id, title, artist FROM songs";if (mysql_query(conn, query)) {std::cerr << "Query failed: " << mysql_error(conn) << "\n";mysql_close(conn);return 1;}// 結果集MYSQL_RES *res = mysql_store_result(conn);if (!res) {std::cerr << "mysql_store_result failed: " << mysql_error(conn) << "\n";mysql_close(conn);return 1;}// 1. 獲取總行數my_ulonglong row_count = mysql_num_rows(res);std::cout << "總行數: " << row_count << "\n"; // 示例輸出: 總行數: 3// 2. 獲取字段總數unsigned int field_count = mysql_num_fields(res);std::cout << "字段數: " << field_count << "\n"; // 示例輸出: 字段數: 3// 3. 使用 mysql_fetch_fields 獲取所有字段信息MYSQL_FIELD *fields = mysql_fetch_fields(res);// 4. 使用 mysql_field_seek 和 mysql_fetch_field 重置字段位置并逐個讀取MYSQL_FIELD_OFFSET offset = mysql_field_seek(res, 0); // 重置位置MYSQL_FIELD *field;while ((field = mysql_fetch_field(res))) {......}// 5. 使用 mysql_field_tell 獲取當前位置(已走到結尾)MYSQL_FIELD_OFFSET current_pos = mysql_field_tell(res);std::cout << "\n當前字段偏移位置 = " << current_pos << "\n"; // 示例輸出: 當前字段偏移位置 = 3// 6. 使用 mysql_fetch_field_direct 直接訪問字段信息std::cout << "\n使用 fetch_field_direct:\n";for (unsigned int i = 0; i < field_count; ++i) {field = mysql_fetch_field_direct(res, i);std::cout << "字段[" << i << "]: 名稱 = " << field->name << ", 類型 = " << field->type << "\n";}mysql_free_result(res);mysql_close(conn);return 0;
}
4.預處理語句(MYSQL_STMT)
預處理語句:
預處理語句是數據庫提供的一種 高效、安全的執行 SQL 語句 的機制,它將 SQL 語句的結構和數據分開處理。
預處理語句先告訴數據庫:“我要做一件什么事(SQL結構)”,然后再告訴它:“我要用哪些數據來做這件事”。也就是說,預處理語句把一個完整的 SQL 操作分成 兩步走:
第一步:定義你要做的事情(SQL語句結構) |
---|
第二步:告訴它你要用哪些數據(填入參數) |
示例:
?假設有一條插入語句:
INSERT INTO users (name, age) VALUES ('Alice', 25);
在 C /c++代碼里用字符串拼接是這樣寫的:
char sql[100];
sprintf(sql, "INSERT INTO users (name, age) VALUES ('%s', %d);", name, age);
mysql_query(conn, sql);
問題:
-
這容易出錯(比如字符串中有
'
或特殊字符) -
還有嚴重的 SQL 注入風險!
改用預處理語句后把 SQL 寫成這樣:
INSERT INTO users (name, age) VALUES (?, ?);
-
?
是占位符,代表后面你再填入的參數。 -
SQL 結構不變,數據單獨綁定,不會被解釋為語句的一部分。
-
執行流程由你控制,像“先建模,再灌數據”。
預處理語句主要優勢:
①防止 SQL 注入攻擊(安全性高)
使用預處理語句時,SQL 語句中使用 ?
占位符 來代表參數,不再通過字符串拼接直接構造 SQL。數據(參數)與語句分離,數據庫會將參數 按值處理,不會被解釋為 SQL 指令。
不安全(傳統拼接):
char sql[200];
sprintf(sql, "SELECT * FROM users WHERE username = '%s'", user_input); // 易受注入攻擊SQL 注入是指:攻擊者把惡意的 SQL 代碼,偽裝成用戶輸入,拼接進原本的 SQL 語句中,從而控制數據庫行為,甚至獲取敏感數據、刪除表等。
示例:
用戶輸入的是:user_input = "Alice";
拼接后的 SQL 是:SELECT * FROM users WHERE username = 'Alice';
這個 SQL 沒有問題,會查找 username = 'Alice' 的用戶。但如果攻擊者輸入的是:user_input = "' OR '1'='1";
拼接后的 SQL 就變成了:SELECT * FROM users WHERE username = '' OR '1'='1';
這意味著:只要條件 1=1 成立(它永遠成立),就會返回數據庫中所有用戶信息!所以 SQL 注入本質是:
利用你把用戶輸入當作 SQL 的一部分來拼接字符串,偽造出攻擊性的 SQL。
安全(預處理):
SELECT * FROM users WHERE username = ?
然后通過 mysql_stmt_bind_param
將參數單獨傳入,數據庫會把參數值當作純文本。
②性能更高(尤其適合重復執行)
預處理語句將 SQL 的 解析、語法檢查和優化 階段只做一次。你可以多次 綁定不同參數 進行執行,數據庫會重用之前編譯好的語句結構。
適用場景:
-
同一條 SQL 要反復執行,例如插入大量數據(批量 INSERT)。
-
每次數據不同但 SQL 結構不變。
第一組:初始化與準備
mysql_stmt_init
初始化一個預處理語句句柄。
MYSQL_STMT *mysql_stmt_init(MYSQL *mysql);
參數:
-
mysql
:已連接的MYSQL
對象指針。
返回值:
-
成功:返回新分配的
MYSQL_STMT
指針。 -
失敗:返回
NULL
。
mysql_stmt_prepare
為預處理語句對象編譯 SQL 語句。
int mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, unsigned long length);
參數:
-
stmt
:預處理語句對象。 -
query
:SQL 查詢語句字符串(使用 ? 占位符)。 -
length
:SQL 字符串的長度(以字節為單位)。
返回值:
-
成功:返回 0。
-
失敗:返回非零,可使用
mysql_stmt_errno()
獲取錯誤碼。
示例:
MYSQL_STMT* stmt = mysql_stmt_init(conn);
const char* sql = "INSERT INFO songs (id, title, artist) VALUES (?, ?, ?)";
if (mysql_stmt_prepare(stmt, sql, strlen(sql))) {std::cout << "some error" << mysql_stmt_error(stmt));
}
第二組:參數綁定與執行
mysql_stmt_bind_param
將數據綁定到預處理語句的參數中。
int mysql_stmt_bind_param(MYSQL_STMT *stmt, MYSQL_BIND *bind);
參數:
-
stmt
:預處理語句對象。 -
bind
:MYSQL_BIND
數組,每個元素綁定一個參數。
返回值:
-
成功:返回 0。
-
失敗:返回非零。
mysql_stmt_execute
執行已準備好的語句。
int mysql_stmt_execute(MYSQL_STMT *stmt);
參數:
-
stmt
:已準備并綁定參數的語句句柄。
返回值:
-
成功:返回 0。
-
失敗:返回非零。
示例:
int id = 4;
char title[100] = "acdscadscas";
char artist[100] = "acdsdcd";MYSQL_BIND bind[3];
memset(bind, 0, sizeof(bind));bind[0].buffer_type = MYSQL_TYPE_LONG;
bind[0].buffer = &id;bind[1].buffer_type = MYSQL_TYPE_STRING;
bind[1].buffer = title;
bind[1].buffer_length = sizeof(title);bind[2].buffer_type = MYSQL_TYPE_STRING;
bind[2].buffer = artist;
bind[2].buffer_length = sizeof(artist);if (mysql_stmt_bind_param(stmt, bind) != 0 ||mysql_stmt_execute(stmt) != 0) {std::cout << "some error" << mysql_stmt_error(stmt));
}
第三組:結果綁定與提取
mysql_stmt_bind_result
將變量綁定到語句的輸出結果列。?
int mysql_stmt_bind_result(MYSQL_STMT *stmt, MYSQL_BIND *bind);
參數:
-
stmt
:執行了 SELECT 的語句句柄。 -
bind
:用于接收結果的變量數組。
返回值:
-
成功:返回 0。
-
失敗:返回非零。
mysql_stmt_fetch
逐行獲取結果。
int mysql_stmt_fetch(MYSQL_STMT *stmt);
參數:
-
stmt
:結果已綁定的語句句柄。
返回值:
-
成功:返回 0。
-
沒有更多行:返回
MYSQL_NO_DATA
。 -
錯誤:返回
1
。
示例:
MYSQL_STMT *select_stmt = mysql_stmt_init(conn);
const char *sql = "SELECT id, title FROM songs WHERE artist = ?";
mysql_stmt_prepare(select_stmt, sql, strlen(sql));char artist_filter[] = "The Beatles";
MYSQL_BIND param[1];
memset(param, 0, sizeof(param));
param[0].buffer_type = MYSQL_TYPE_STRING;
param[0].buffer = artist_filter;
param[0].buffer_length = strlen(artist_filter);mysql_stmt_bind_param(select_stmt, param);
mysql_stmt_execute(select_stmt);int id;
char title[100];
MYSQL_BIND result[2];
memset(result, 0, sizeof(result));
result[0].buffer_type = MYSQL_TYPE_LONG;
result[0].buffer = &id;
result[1].buffer_type = MYSQL_TYPE_STRING;
result[1].buffer = title;
result[1].buffer_length = sizeof(title);mysql_stmt_bind_result(select_stmt, result);while (mysql_stmt_fetch(select_stmt) == 0) {printf("id: %d, title: %s\n", id, title);
}
第四組:結果緩存與元數據相關函數
mysql_stmt_store_result
將結果集從服務器讀取并緩存到客戶端,適用于隨機訪問或多次訪問結果的場景。
int mysql_stmt_store_result(MYSQL_STMT *stmt);
參數:
-
stmt
:執行了SELECT
的語句句柄。
返回值:
-
成功:返回
0
。 -
失敗:返回非零,使用
mysql_stmt_errno()
獲取錯誤碼。
注意:
-
如果你需要使用
mysql_stmt_num_rows()
或者mysql_stmt_data_seek()
,必須先調用本函數。 -
類似于
mysql_store_result()
,但用于預處理語句。
mysql_stmt_result_metadata
獲取結果集的字段元信息(例如列名、類型、長度等)。
MYSQL_RES *mysql_stmt_result_metadata(MYSQL_STMT *stmt);
參數:
-
stmt
:準備并執行了 SELECT 的語句句柄。
返回值:
-
成功:返回
MYSQL_RES *
,可通過mysql_fetch_field()
、mysql_num_fields()
等分析字段結構。 -
失敗或無字段結果:返回
NULL
。
用途:
-
當你想動態綁定結果時(不知道返回字段數),可先調用本函數來獲知字段信息。
mysql_stmt_param_metadata
獲取語句的參數信息(占位符參數的數量、類型等)。
MYSQL_RES *mysql_stmt_param_metadata(MYSQL_STMT *stmt);
參數:
-
stmt
:已經調用mysql_stmt_prepare()
的語句句柄。
返回值:
-
成功:返回
MYSQL_RES *
,可用于查看參數數量和類型。 -
無參數或失敗:返回
NULL
。
示例:
mysql_stmt_prepare(stmt, sql, strlen(sql));// 綁定輸入參數
char artist[] = "The Beatles";
MYSQL_BIND param[1];
memset(param, 0, sizeof(param));
param[0].buffer_type = MYSQL_TYPE_STRING;
param[0].buffer = artist;
param[0].buffer_length = strlen(artist);
mysql_stmt_bind_param(stmt, param);
mysql_stmt_execute(stmt);// 獲取元數據(字段名、類型等)
MYSQL_RES *meta_result = mysql_stmt_result_metadata(stmt);// 獲取結果集中字段數量
unsigned int field_count = mysql_num_fields(meta_result);
// 假設 SQL 為:SELECT id, title FROM songs WHERE artist = ?
// 那么字段數量為 2(即 id 和 title)
/*field_count = 2
*/// 獲取字段數組,包含每個字段的元信息(字段名、類型等)
MYSQL_FIELD *fields = mysql_fetch_fields(meta_result);
/*fields[0].name = "id"fields[0].type = MYSQL_TYPE_LONG (對應數字類型 INT)fields[1].name = "title"fields[1].type = MYSQL_TYPE_STRING 或 MYSQL_TYPE_VAR_STRING(VARCHAR 類型)
*/// 打印字段元信息
for (unsigned int i = 0; i < field_count; i++) {printf("Column %d: name = %s, type = %d\n", i, fields[i].name, fields[i].type);
}/*上面循環的實際輸出將為:Column 0: name = id, type = 3Column 1: name = title, type = 253注:type = 3 表示 MYSQL_TYPE_LONG(整數)type = 253 表示 MYSQL_TYPE_VAR_STRING(VARCHAR)
*/// 獲取結果并緩存(必須在 fetch 前調用,允許隨機訪問結果)
mysql_stmt_store_result(stmt);
mysql_stmt_num_rows
獲取結果集中總行數(在 mysql_stmt_store_result()
之后使用)
my_ulonglong mysql_stmt_num_rows(MYSQL_STMT *stmt);
參數:
-
stmt
:已執行并緩存結果的語句句柄。
返回值:
-
成功:返回結果集中的總行數。
-
失敗:返回 0。
示例:
mysql_stmt_store_result(stmt);
my_ulonglong row_count = mysql_stmt_num_rows(stmt);
mysql_stmt_data_seek
移動結果集的內部行指針,用于“跳轉”讀取特定行(僅適用于 store_result()
緩存模式)
void mysql_stmt_data_seek(MYSQL_STMT *stmt, my_ulonglong offset);
參數:
-
stmt
:語句句柄 -
offset
:目標行索引(從 0 開始)
MYSQL_STMT *stmt = mysql_stmt_init(conn);
const char *sql = "SELECT id, title FROM songs";
mysql_stmt_prepare(stmt, sql, strlen(sql));
mysql_stmt_execute(stmt);// 綁定結果
int id;
char title[100];
MYSQL_BIND result[2];
memset(result, 0, sizeof(result));
result[0].buffer_type = MYSQL_TYPE_LONG;
result[0].buffer = &id;
result[1].buffer_type = MYSQL_TYPE_STRING;
result[1].buffer = title;
result[1].buffer_length = sizeof(title);
mysql_stmt_bind_result(stmt, result);// 緩存結果(這是使用 data_seek 的前提)
mysql_stmt_store_result(stmt);// 第一次 fetch 讀取結果集中的第一行
mysql_stmt_fetch(stmt);
printf("[第一次] id: %d, title: %s\n", id, title);
// 輸出:[第一次] id: 1, title: Yesterdaymysql_stmt_fetch(stmt); 讀取結果集中的第二行
printf("[第二次] id: %d, title: %s\n", id, title);
// 輸出:[第二次] id: 2, title: Hello// 使用 data_seek 跳回第 0 行(第一行)
mysql_stmt_data_seek(stmt, 0);// 再 fetch 一次,應該是第一行
mysql_stmt_fetch(stmt);
printf("[跳回后] id: %d, title: %s\n", id, title);
// 輸出:[跳回后] id: 1, title: Yesterday
mysql_stmt_free_result
釋放語句對象中存儲的結果集內存,釋放后不能再調用 fetch
int mysql_stmt_free_result(MYSQL_STMT *stmt);
參數:
-
stmt
:語句句柄
返回值:
-
成功:返回 0
-
失敗:返回非零
mysql_stmt_free_result(stmt);調用時機:在完成數據讀取后及時釋放內存。
第五組:預處理語句的輔助與控制函數
mysql_stmt_attr_set
設置語句句柄的屬性,例如設置最大緩沖行數(可控制 mysql_stmt_store_result
的行為)。
int mysql_stmt_attr_set(MYSQL_STMT *stmt, enum enum_stmt_attr_type attr_type, const void *attr);
?參數:
-
stmt
:預處理語句句柄。 -
attr_type
:屬性類型,常用值包括:-
STMT_ATTR_UPDATE_MAX_LENGTH
:是否更新字段的最大長度信息。 -
STMT_ATTR_CURSOR_TYPE
:設置游標類型(如CURSOR_TYPE_READ_ONLY
)。 -
STMT_ATTR_PREFETCH_ROWS
:設置預取行數(優化 fetch 行為)。
-
-
attr
:指向屬性值的指針。
返回值:
-
成功:
0
-
失敗:非零
mysql_stmt_attr_get
獲取語句當前某個屬性的值。
int mysql_stmt_attr_get(MYSQL_STMT *stmt, enum enum_stmt_attr_type attr_type, void *attr);
參數:
與 attr_set
類似,只是 attr
是用來接收屬性值的指針。
返回值:
成功返回 0
,失敗返回非零。
示例:
設置并獲取 STMT_ATTR_UPDATE_MAX_LENGTH 屬性
這個屬性的含義是:
是否在 mysql_stmt_store_result()
后自動更新字段的最大長度(比如字符串字段的最大長度)。
設置為 1
表示“更新字段最大長度信息”,設置為 0
表示“不更新”。
// 設置屬性:開啟字段最大長度自動更新
my_bool set_val = 1;
if (mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &set_val) != 0) {fprintf(stderr, "Failed to set stmt attribute\n");
}// 獲取屬性:驗證設置是否生效
my_bool get_val = 0;
if (mysql_stmt_attr_get(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &get_val) != 0) {fprintf(stderr, "Failed to get stmt attribute\n");
} else {printf("STMT_ATTR_UPDATE_MAX_LENGTH = %d\n", get_val); // 輸出應為 1,表示設置成功
}
mysql_stmt_errno
獲取最近一條預處理語句操作的錯誤碼。
unsigned int mysql_stmt_errno(MYSQL_STMT *stmt);
mysql_stmt_error
獲取最近一條語句操作的錯誤信息(字符串形式)。
const char *mysql_stmt_error(MYSQL_STMT *stmt);
mysql_stmt_reset
重置語句(清除綁定的參數與結果、狀態等,但不釋放語句)。
int mysql_stmt_reset(MYSQL_STMT *stmt);
mysql_stmt_close
關閉語句并釋放預處理語句句柄的所有資源。
int mysql_stmt_close(MYSQL_STMT *stmt);
示例:
MYSQL_STMT *stmt = mysql_stmt_init(conn);
const char *sql = "SELECT title FROM songs";
if (mysql_stmt_prepare(stmt, sql, strlen(sql))) {fprintf(stderr, "Prepare failed: %s\n", mysql_stmt_error(stmt));return;
}
........
// 清理
mysql_free_result(meta);
mysql_stmt_close(stmt);
5. 事務控制
事務是什么?
事務是一組操作的集合,事務會把所有操作作為一個整體一起向系統提交或撤銷操作請求,即這些操作要么同時成功,要么同時失敗。
一句話:事務就是一組必須“要么全做完,要么一個也不做”的數據庫操作。
比如:銀行轉賬操作 —— 張三轉賬給李四 1000 元,不能出現張三的錢扣了但李四沒收到的情況。
示例:
-- 1. 查詢張三賬戶余額
select * from account where name = '張三';
-- 2. 將張三賬戶余額-1000
update account set money = money - 1000 where name = '張三';
-- 此語句出錯后張三錢減少但是李四錢沒有增加
模擬sql語句錯誤
-- 3. 將李四賬戶余額+1000
update account set money = money + 1000 where name = '李四';這時如果沒有事務:
此時張三的錢就被扣了,李四的錢卻沒有增加,錢“憑空消失”,數據錯誤了!
所以必須讓這步操作打包成一組事務,要么一起成功、要么一起失敗回滾。
mysql_autocommit
這個函數是事務控制的開關,控制是否開啟“自動提交模式”。
-
默認情況下,MySQL 每執行一條
INSERT
、UPDATE
、DELETE
就會立即COMMIT
,這叫“自動提交”。 -
如果你希望在多條 SQL 執行后統一提交或撤銷(比如錢轉了一半不能讓數據庫寫進去),就需要關閉自動提交,手動控制事務。
int mysql_autocommit(MYSQL *mysql, my_bool mode);
參數:
-
mysql
:連接句柄。 -
mode
:-
1
(默認):開啟自動提交模式(每條語句執行后自動提交)。 -
0
:關閉自動提交,意味著你要手動用mysql_commit()
或mysql_rollback()
控制事務提交。
-
返回值:
-
成功返回
0
,失敗返回非 0。
mysql_autocommit(conn, 0); // 相當于 START TRANSACTION;
mysql_commit
這個函數提交事務,將你執行的所有數據更改操作真正“寫入數據庫”。
-
如果你執行了多條修改語句(例如插入了幾條訂單信息),但還沒調用
mysql_commit()
,那么這些修改還沒有真正寫入磁盤(處于事務緩存中)。 -
只有調用
mysql_commit()
,事務才算真正結束,數據才會變為持久化。
int mysql_commit(MYSQL *mysql);
返回值:
-
成功返回
0
,失敗返回非 0。
注意:
在使用事務時,如果你還沒有調用 mysql_commit()
提交事務,此時數據庫連接突然斷掉(比如網絡斷了、程序崩潰了、數據庫宕機了),那么:當前正在執行的事務會被自動取消,數據庫會幫你回滾!
示例:
假設執行了一系列數據庫操作,比如:
START TRANSACTION;
UPDATE users SET balance = balance - 100 WHERE id = 1;
UPDATE users SET balance = balance + 100 WHERE id = 2;
這時候你還沒調用 COMMIT;
—— 也就是說事務還在進行中。
但突然:
-
程序崩潰了,
-
數據庫斷網了,
-
或者數據庫服務掛了,
那么:這兩個 UPDATE 操作 都不會被保存,MySQL 會自動回滾這些未提交的修改。
mysql_rollback
這個函數是 commit()
的“反操作”:撤銷當前事務中的所有修改。如果你在執行多條語句后發現某一步出錯,就可以調用它,防止錯誤寫入數據庫。
int mysql_rollback(MYSQL *mysql);
返回值:
-
成功返回
0
,失敗返回非 0。
示例:
// 訂單支付邏輯
mysql_autocommit(conn, 0);if (mysql_query(conn, "INSERT INTO orders ...") ||mysql_query(conn, "UPDATE users SET balance = balance - 100 ...")) {// 有一步失敗,撤銷所有操作mysql_rollback(conn);
} else {mysql_commit(conn); // 所有操作成功,確認寫入
}
mysql_more_results
這個函數用于判斷你是否還有下一個結果集可讀取,通常和 mysql_next_result()
搭配。
通常用于多語句查詢(multi-statement),比如:
SELECT * FROM table1; SELECT * FROM table2;
此時執行后會有兩個結果集,而不是一個。
bool mysql_more_results(MYSQL *mysql);
返回值:
-
true
:還有下一個結果集。 -
false
:所有結果集都已讀取完。
mysql_next_result
這個函數讓你進入下一個結果集。它配合 mysql_more_results()
一起用。
比如執行的是:
SELECT * FROM users; SELECT * FROM songs;
執行一次 mysql_store_result()
只能取到 users
,想要取 songs
就需要用 mysql_next_result()
。
int mysql_next_result(MYSQL *mysql);
返回值:
-
成功:返回
0
,說明成功切換到下一個結果集。 -
沒有更多結果:返回
-1
。 -
錯誤:返回大于 0 的值。
mysql_autocommit(conn, 0); // 關閉自動提交,開啟事務// 執行多條 SQL(多語句查詢)
const char *multi_sql = "INSERT INTO log(msg) VALUES('start'); SELECT * FROM songs;";
if (mysql_query(conn, multi_sql)) {fprintf(stderr, "執行失敗:%s\n", mysql_error(conn));mysql_rollback(conn);
} else {// 提交事務mysql_commit(conn);// 獲取第一結果(INSERT 無結果)MYSQL_RES *res = mysql_store_result(conn);if (res) mysql_free_result(res); // 忽略 INSERT 的結果集// 有更多結果?while (mysql_more_results(conn)) {if (mysql_next_result(conn) == 0) {res = mysql_store_result(conn);if (res) {MYSQL_ROW row;while ((row = mysql_fetch_row(res))) {printf("歌曲名:%s\n", row[1]);}mysql_free_result(res);}}}
}mysql_autocommit(conn, 1); // 恢復自動提交模式