MySQL事务处理:mysql_autocommit、mysql_commit、mysql_rollback、保存点(SAVEPOINT)

事务处理,说白了就是让一堆SQL操作要么全部成功,要么全部失败。我刚开始用MySQL写C程序时,总觉得事务是个可有可无的东西——直到有一次,我在银行转账的demo里忘了加事务,结果测试时程序在扣款后、加款前崩溃了。嗯,从那以后,我再也不敢轻视事务了。

今天我们就来聊聊MySQL C API中事务处理的四个核心函数:mysql_autocommitmysql_commitmysql_rollback,以及保存点(SAVEPOINT)的使用。这些函数说白了就是帮你控制事务的开关、提交、回滚和部分回滚。

1. 事务的基本概念

先简单回顾一下事务的ACID特性:

  • 原子性(Atomicity):事务里的操作,要么全做,要么全不做。
  • 一致性(Consistency):事务前后,数据必须符合所有规则。
  • 隔离性(Isolation):并发事务之间互不干扰。
  • 持久性(Durability):事务一旦提交,数据就永久保存。

在MySQL中,默认是自动提交模式——每执行一条SQL,它就立刻提交。这在我们做批量更新时非常危险。你想想看,如果第一条UPDATE成功了,第二条失败了,数据就处于半残废状态。

核心要点:事务处理的关键在于关闭自动提交,然后手动控制提交或回滚。

2. mysql_autocommit:关闭自动提交

这个函数用来设置事务的自动提交模式。原型很简单:

my_bool mysql_autocommit(MYSQL *mysql, my_bool mode);

参数mode为0时关闭自动提交,为1时开启。我个人习惯在需要事务处理时,先调用这个函数把自动提交关掉:

MYSQL *conn = mysql_init(NULL);
mysql_real_connect(conn, "localhost", "root", "pass", "testdb", 0, NULL, 0);

// 关闭自动提交
if (mysql_autocommit(conn, 0)) {
    fprintf(stderr, "关闭自动提交失败: %s\n", mysql_error(conn));
    // 处理错误...
}

关闭自动提交后,所有SQL操作都会暂存在一个事务中,直到你调用mysql_commitmysql_rollback

小技巧:如果你只是临时需要事务,记得在事务结束后把自动提交重新打开。我曾经见过有人关掉后忘了开,结果后续所有操作都变成了手动提交模式,排查了半天才发现。

3. mysql_commit:提交事务

当你确认所有操作都正确无误时,调用mysql_commit将事务中的修改永久写入数据库:

my_bool mysql_commit(MYSQL *mysql);

看一个完整的转账示例:

// 假设有两个账户:from_account 和 to_account
// 转账100元

mysql_autocommit(conn, 0);  // 关闭自动提交

// 扣款
char sql1[256];
snprintf(sql1, sizeof(sql1),
    "UPDATE accounts SET balance = balance - 100 WHERE account_id = 1");
if (mysql_query(conn, sql1)) {
    mysql_rollback(conn);  // 出错就回滚
    // 处理错误...
}

// 加款
char sql2[256];
snprintf(sql2, sizeof(sql2),
    "UPDATE accounts SET balance = balance + 100 WHERE account_id = 2");
if (mysql_query(conn, sql2)) {
    mysql_rollback(conn);  // 出错就回滚
    // 处理错误...
}

// 全部成功,提交
if (mysql_commit(conn)) {
    fprintf(stderr, "提交失败: %s\n", mysql_error(conn));
}

这里有个细节:mysql_commit返回非0值表示提交失败。我在项目中遇到过磁盘满导致提交失败的情况,所以千万别忽略这个返回值。

4. mysql_rollback:回滚事务

当操作过程中出现任何错误,调用mysql_rollback撤销所有未提交的修改:

my_bool mysql_rollback(MYSQL *mysql);

回滚后,数据库恢复到事务开始前的状态。注意,回滚只对未提交的操作有效。一旦提交了,就回不去了。

警告mysql_rollback只能回滚DML语句(INSERT、UPDATE、DELETE)。对于DDL语句(CREATE TABLE、ALTER TABLE等),MySQL会隐式提交当前事务,无法回滚。我曾经在事务里执行了DROP TABLE,然后想回滚...嗯,结果你懂的。

5. 保存点(SAVEPOINT)

有时候你不想回滚整个事务,只想撤销部分操作。这时候保存点就派上用场了。保存点允许你在事务中设置一个标记点,然后回滚到那个点。

MySQL C API没有专门的保存点函数,但你可以直接执行SQL语句:

// 设置保存点
mysql_query(conn, "SAVEPOINT sp1");

// 执行一些操作
mysql_query(conn, "INSERT INTO logs VALUES (1, '操作A')");

// 设置第二个保存点
mysql_query(conn, "SAVEPOINT sp2");

// 再执行一些操作
mysql_query(conn, "INSERT INTO logs VALUES (2, '操作B')");

// 如果操作B有问题,回滚到sp2
mysql_query(conn, "ROLLBACK TO SAVEPOINT sp2");

// 提交事务(只保留了操作A)
mysql_commit(conn);

保存点有几个特点:

  • 一个事务中可以设置多个保存点
  • 回滚到保存点后,该保存点之后的保存点会被释放
  • 提交事务会释放所有保存点
  • 保存点名称在事务内唯一

我的经验:保存点特别适合批量导入数据的场景。比如你要导入1000条记录,每100条设置一个保存点。如果某批数据出错,只需要回滚到上一个保存点,而不是全部重来。我在做数据迁移工具时就用这个策略,效率提升了不少。

6. 完整的事务处理流程

下面我用一张SVG图来展示事务处理的完整流程:

MySQL事务处理流程图 开始事务 mysql_autocommit(conn, 0) 执行SQL操作(INSERT/UPDATE/DELETE) 设置保存点:SAVEPOINT sp1(可选) 操作成功? mysql_commit() mysql_rollback() ROLLBACK TO SAVEPOINT(可选) 事务结束 必选步骤 可选步骤

7. 实际项目中的避坑指南

最后,分享几个我在实际项目中踩过的坑:

  • 连接断开导致隐式回滚:如果事务未提交时连接断开,MySQL会自动回滚。我曾经在长事务中没处理好网络异常,结果数据丢了。解决方案是加上重试机制。
  • 不要混用存储引擎:MyISAM不支持事务,调用mysql_commitmysql_rollback不会报错,但也不会生效。我建议统一使用InnoDB。
  • 保存点名称冲突:如果在一个事务中重复使用相同的保存点名称,MySQL会覆盖之前的保存点。记得给保存点起不同的名字。
  • 事务超时:MySQL有innodb_lock_wait_timeout参数,默认50秒。如果事务持有锁太久,会被强制回滚。我在做批量更新时就遇到过这个问题,后来把大事务拆成了小事务。

总结一下:事务处理是保证数据一致性的基石。mysql_autocommit控制开关,mysql_commit确认修改,mysql_rollback撤销操作,保存点提供更精细的回滚控制。掌握这四个工具,你的C语言数据库程序就能在数据安全方面站稳脚跟。


公众号:蓝海资料掘金营,微信deep3321