基於 MySQL C API(libmysqlclient)實現事務控制,涵蓋自動提交關閉、事務啟動、業務 SQL 執行、提交/回滾決策、錯誤碼處理、連接斷開重連等工程級最佳實踐。
# 安裝開發庫
yum install -y mysql-devel # CentOS
apt-get install -y libmysqlclient-dev # Ubuntu
# 編譯鏈接
gcc -o txn_demo txn_demo.c $(mysql_config --cflags --libs)
# 或手動
gcc -o txn_demo txn_demo.c -I/usr/include/mysql -L/usr/lib64/mysql -lmysqlclient -lz -lssl -lcrypto -lpthread -ldl
MYSQL *conn = mysql_init(NULL);
if (!mysql_real_connect(conn, "127.0.0.1", "user", "pass", "db", 3306, NULL, 0)) {
fprintf(stderr, "Connect failed: %s\n", mysql_error(conn));
return -1;
}
// 1. 關閉自動提交(關鍵:必須在事務前設置)
if (mysql_query(conn, "SET autocommit = 0")) {
log_error("Disable autocommit failed: %s", mysql_error(conn));
return -1;
}
// 2. 顯式啟動事務(可選,關閉 autocommit 後首條 DML 隱式啟動)
if (mysql_query(conn, "START TRANSACTION")) {
log_error("Start transaction failed: %s", mysql_error(conn));
return -1;
}
// 3. 執行業務 SQL 序列
const char *sqls[] = {
"INSERT INTO account_log (user_id, amount, type) VALUES (1001, -500, 'withdraw')",
"UPDATE account SET balance = balance - 500 WHERE user_id = 1001 AND balance >= 500",
"INSERT INTO notify_queue (user_id, msg) VALUES (1001, 'Withdraw success')"
};
for (int i = 0; i < 3; ++i) {
if (mysql_query(conn, sqls[i])) {
log_error("SQL[%d] failed: %s", i, mysql_error(conn));
goto ROLLBACK;
}
// 可選:檢查影響行數
if (mysql_affected_rows(conn) == 0 && i == 1) { // 更新餘額失敗(餘額不足)
log_warn("Insufficient balance, rolling back");
goto ROLLBACK;
}
}
// 4. 提交事務
if (mysql_query(conn, "COMMIT")) {
log_error("Commit failed: %s", mysql_error(conn));
goto ROLLBACK;
}
log_info("Transaction committed successfully");
mysql_close(conn);
return 0;
ROLLBACK:
// 5. 回滾事務
if (mysql_query(conn, "ROLLBACK")) {
log_error("Rollback failed: %s", mysql_error(conn));
} else {
log_info("Transaction rolled back");
}
mysql_close(conn);
return -1;
txn_demo.c#include
#include
#include
#include
#define LOG_ERROR(fmt, ...) fprintf(stderr, "[ERROR] " fmt "\n", ##__VA_ARGS__)
#define LOG_INFO(fmt, ...) fprintf(stdout, "[INFO] " fmt "\n", ##__VA_ARGS__)
static int execute_sql(MYSQL *conn, const char *sql, const char *ctx) {
if (mysql_query(conn, sql)) {
LOG_ERROR("%s failed: %s (errno=%d)", ctx, mysql_error(conn), mysql_errno(conn));
return -1;
}
return 0;
}
int main() {
MYSQL *conn = mysql_init(NULL);
if (!conn) {
LOG_ERROR("mysql_init failed");
return EXIT_FAILURE;
}
// 連接配置(建議讀取配置文件/環境變量)
const char *host = "127.0.0.1";
const char *user = "app_user";
const char *pass = "StrongPass123!";
const char *db = "app_db";
unsigned int port = 3306;
// 連接超時設置
mysql_options(conn, MYSQL_OPT_CONNECT_TIMEOUT, (const char *)"5");
// 自動重連(謹慎:事務中斷後重連會導致事務隱式回滾)
// my_bool reconnect = 1; mysql_options(conn, MYSQL_OPT_RECONNECT, &reconnect);
if (!mysql_real_connect(conn, host, user, pass, db, port, NULL, 0)) {
LOG_ERROR("Connect failed: %s", mysql_error(conn));
mysql_close(conn);
return EXIT_FAILURE;
}
LOG_INFO("Connected to MySQL %s", mysql_get_server_info(conn));
// 事務邏輯
if (execute_sql(conn, "SET autocommit = 0", "Disable autocommit")) goto cleanup;
if (execute_sql(conn, "START TRANSACTION", "Start transaction")) goto cleanup;
// 業務 SQL
if (execute_sql(conn, "INSERT INTO account_log (user_id, amount, type) VALUES (1001, -500, 'withdraw')", "Insert log")) goto rollback;
if (execute_sql(conn, "UPDATE account SET balance = balance - 500 WHERE user_id = 1001 AND balance >= 500", "Update balance")) goto rollback;
if (execute_sql(conn, "INSERT INTO notify_queue (user_id, msg) VALUES (1001, 'Withdraw success')", "Insert notify")) goto rollback;
// 檢查更新行數(餘額不足保護)
if (mysql_affected_rows(conn) == 0) {
LOG_WARN("Balance update affected 0 rows, insufficient funds");
goto rollback;
}
if (execute_sql(conn, "COMMIT", "Commit")) goto cleanup;
LOG_INFO("Transaction committed");
mysql_close(conn);
return EXIT_SUCCESS;
rollback:
execute_sql(conn, "ROLLBACK", "Rollback");
LOG_INFO("Transaction rolled back");
cleanup:
mysql_close(conn);
return EXIT_FAILURE;
}
| 主題 | 建議 |
|---|---|
| 連接池 | 生產環境必須使用連接池(如 libmysqlclient 無內置池,需自行實現或用第三方庫 mysql-cppconn-pool、drogon::orm::DbClient)。 |
| 預處理語句 | 用 MYSQL_STMT + mysql_stmt_prepare/bind_param/execute 防注入、提升重複執行性能。 |
| 錯誤碼處理 | 關鍵 errno:1213 死鎖(重試)、1205 鎖等待超時(重試/降級)、1062 唯一鍵衝突(業務判斷)、2006/2013 連接斷開(重連重試)。 |
| 事務隔離級別 | 連接級設置:SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;全局 transaction_isolation。 |
| 超時控制 | mysql_options(MYSQL_OPT_READ_TIMEOUT/WRITE_TIMEOUT),避免長時間阻塞。 |
| 多語句事務 | 單次 mysql_query 不支持多語句,需逐條執行或啟用 CLIENT_MULTI_STATEMENTS(慎用)。 |
| SAVEPOINT | 嵌套事務模擬:SAVEPOINT sp1; ROLLBACK TO SAVEPOINT sp1; RELEASE SAVEPOINT sp1; |
MYSQL_OPT_RECONNECT=1 在事務中斷後重連,事務狀態丟失,建議關閉並自行處理重連邏輯。MYSQL_TYPE_LONG vs MYSQL_TYPE_LONGLONG、字符串長度需含終止符。mysql_free_result 或循環 mysql_next_result 清空。