MySQL事务处理:mysql_autocommit、mysql_commit、mysql_rollback、保存点(SAVEPOINT)
事务处理,说白了就是让一堆SQL操作要么全部成功,要么全部失败。我刚开始用MySQL写C程序时,总觉得事务是个可有可无的东西——直到有一次,我在银行转账的demo里忘了加事务,结果测试时程序在扣款后、加款前崩溃了。嗯,从那以后,我再也不敢轻视事务了。
今天我们就来聊聊MySQL C API中事务处理的四个核心函数:mysql_autocommit、mysql_commit、mysql_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_commit或mysql_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图来展示事务处理的完整流程:
7. 实际项目中的避坑指南
最后,分享几个我在实际项目中踩过的坑:
- 连接断开导致隐式回滚:如果事务未提交时连接断开,MySQL会自动回滚。我曾经在长事务中没处理好网络异常,结果数据丢了。解决方案是加上重试机制。
- 不要混用存储引擎:MyISAM不支持事务,调用
mysql_commit和mysql_rollback不会报错,但也不会生效。我建议统一使用InnoDB。 - 保存点名称冲突:如果在一个事务中重复使用相同的保存点名称,MySQL会覆盖之前的保存点。记得给保存点起不同的名字。
- 事务超时:MySQL有
innodb_lock_wait_timeout参数,默认50秒。如果事务持有锁太久,会被强制回滚。我在做批量更新时就遇到过这个问题,后来把大事务拆成了小事务。
总结一下:事务处理是保证数据一致性的基石。mysql_autocommit控制开关,mysql_commit确认修改,mysql_rollback撤销操作,保存点提供更精细的回滚控制。掌握这四个工具,你的C语言数据库程序就能在数据安全方面站稳脚跟。