首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> MySQL 資料庫 >> MySQL 綜合教學

C 語言操作 MySQL 事務完整示例:啟動、提交、回滾與錯誤處理

     編輯:MySQL 綜合教學

C 語言操作 MySQL 事務完整示例:啟動、提交、回滾與錯誤處理

基於 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

二、 核心 API 流程

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;

五、 常見坑點與排查

  1. 忘記關閉 autocommit:每條 SQL 獨立提交,無法回滾。
  2. 自動重連導致事務靜默回滾:MYSQL_OPT_RECONNECT=1 在事務中斷後重連,事務狀態丟失,建議關閉並自行處理重連邏輯。
  3. 長事務持有鎖導致阻塞:業務邏輯移出事務、拆分為多個短事務、用樂觀鎖/版本號替代悲觀鎖。
  4. 預處理參數綁定類型不匹配:MYSQL_TYPE_LONG vs MYSQL_TYPE_LONGLONG、字符串長度需含終止符。
  5. 結果集未讀取完畢再執行下一條 SQL:需先 mysql_free_result 或循環 mysql_next_result 清空。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved