事务是数据库编程中的一个核心概念,它确保一组数据库操作要么全部成功执行,要么全部不执行,从而维护数据库的一致性。以下是关于数据库事务的详细介绍:
事务的基本特性 (ACID)
-
原子性 (Atomicity): 事务是不可分割的工作单位,要么全部执行,要么全部不执行
-
一致性 (Consistency): 事务执行前后,数据库从一个一致状态变到另一个一致状态
-
隔离性 (Isolation): 多个事务并发执行时,一个事务的执行不应影响其他事务
-
持久性 (Durability): 一旦事务提交,其结果就是永久性的
事务的基本操作
BEGIN TRANSACTION; -- 开始事务
-- 执行SQL语句...
COMMIT; -- 提交事务
-- 或
ROLLBACK; -- 回滚事务
在不同语言中的实现
Python (使用SQLite为例)
import sqlite3
conn = sqlite3.connect('example.db')
cursor = conn.cursor()
try:
# 开始事务
cursor.execute("BEGIN TRANSACTION")
# 执行SQL操作
cursor.execute("INSERT INTO users VALUES (?, ?)", (1, 'Alice'))
cursor.execute("UPDATE accounts SET balance = balance - 100 WHERE user_id = 1")
# 提交事务
conn.commit()
except Exception as e:
# 发生错误时回滚
conn.rollback()
print(f"Transaction failed: {e}")
finally:
conn.close()
Java (JDBC)
Connection conn = null;
try {
conn = DriverManager.getConnection(DB_URL);
conn.setAutoCommit(false); // 开始事务
// 执行SQL操作
Statement stmt = conn.createStatement();
stmt.executeUpdate("INSERT INTO users VALUES (1, 'Alice')");
stmt.executeUpdate("UPDATE accounts SET balance = balance - 100 WHERE user_id = 1");
conn.commit(); // 提交事务
} catch (SQLException e) {
if (conn != null) {
conn.rollback(); // 回滚事务
}
e.printStackTrace();
} finally {
if (conn != null) {
conn.setAutoCommit(true);
conn.close();
}
}
事务隔离级别
不同的数据库系统支持不同的事务隔离级别:
-
READ UNCOMMITTED: 最低隔离级别,可能读取到未提交的数据变更(脏读)
-
READ COMMITTED: 只能读取已提交的数据(大多数数据库的默认级别)
-
REPEATABLE READ: 确保在同一事务中多次读取同样数据结果一致
-
SERIALIZABLE: 最高隔离级别,完全串行化执行
设置隔离级别的SQL示例:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
保存点(Savepoint)
在复杂事务中,可以设置保存点以便部分回滚:
BEGIN TRANSACTION;
INSERT INTO table1 VALUES (1);
SAVEPOINT my_savepoint;
INSERT INTO table1 VALUES (2);
ROLLBACK TO my_savepoint; -- 只回滚到保存点
INSERT INTO table1 VALUES (3);
COMMIT;
MySQL Connector/C++ 中使用事务
在 MySQL Connector/C++ 中使用事务,主要是通过 sql::Connection
对象来控制事务的开始、提交和回滚。MySQL Connector/C++ 是 C++ 程序连接 MySQL 数据库的一种方式,它支持标准的数据库操作,包括事务处理。
基本概念
1. 事务(Transaction):
事务是一组 SQL 操作,要么全部成功,要么全部失败。用于保证数据的一致性和完整性。
2. ACID 属性:
- 原子性:事务中的所有操作要么都执行,要么都不执行。
- 一致性:事务必须使数据库从一个一致状态变到另一个一致状态。
- 隔离性:多个事务并发执行时彼此隔离。
- 持久性:一旦事务提交,其结果是永久性的。
事务控制语句
在 Connector/C++ 中,事务控制通过以下方法实现:
方法 | 描述 |
---|---|
con->setAutoCommit(false); | 关闭自动提交,开启事务模式 |
con->commit(); | 提交事务 |
con->rollback(); | 回滚事务 |
事务流程步骤
- 获取数据库连接
- 设置
autoCommit = false
- 执行多条 SQL 语句
- 如果全部成功,调用
commit()
- 如果发生异常或错误,调用
rollback()
示例代码
银行转账(A 转账给 B)
#include <mysql_driver.h>
#include <mysql_connection.h>
#include <cppconn/statement.h>
#include <cppconn/exception.h>
#include <iostream>
int main() {
try {
sql::mysql::MySQL_Driver *driver;
sql::Connection *con;
sql::Statement *stmt;
// 获取驱动并建立连接
driver = sql::mysql::get_mysql_driver_instance();
con = driver->connect("tcp://127.0.0.1:3306", "root", "password");
con->setSchema("testdb"); // 使用数据库 testdb
// 关闭自动提交,开启事务
con->setAutoCommit(false);
std::cout << "Auto Commit OFF" << std::endl;
stmt = con->createStatement();
// 假设 A 账户转出 500 给 B
stmt->executeUpdate("UPDATE accounts SET balance = balance - 500 WHERE name = 'A'");
stmt->executeUpdate("UPDATE accounts SET balance = balance + 500 WHERE name = 'B'");
// 提交事务
con->commit();
std::cout << "Transaction committed." << std::endl;
delete stmt;
delete con;
} catch (sql::SQLException &e) {
std::cerr << "SQL Error: " << e.what() << std::endl;
std::cerr << "Error Code: " << e.getErrorCode() << std::endl;
std::cerr << "SQL State: " << e.getSQLState() << std::endl;
// 出现异常,回滚事务
if (e.getErrorCode() != 0) {
try {
std::cerr << "Rolling back transaction..." << std::endl;
con->rollback();
} catch (...) {
std::cerr << "Rollback failed." << std::endl;
}
}
}
return 0;
}
注意事项
- 默认是自动提交(autoCommit = true),需要手动关闭才能使用事务。
- 异常处理很重要:任何 SQL 错误都应该触发回滚操作。
- 释放资源:使用完
Statement
,ResultSet
,Connection
后要记得delete
。 - 长事务影响性能:避免长时间不提交事务,否则可能导致锁等待或死锁。
设置事务隔离级别(可选)
在 MySQL Connector/C++ 中,可以设置事务的隔离级别来控制一个事务中的操作如何与并发事务中的操作进行交互。MySQL 支持四种标准的事务隔离级别:读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。通过设置不同的隔离级别,可以避免如脏读、不可重复读和幻读等问题。
设置事务隔离级别的步骤
- 获取数据库连接:首先需要有一个有效的数据库连接。
- 关闭自动提交模式:使用
setAutoCommit(false)
方法关闭自动提交模式以手动管理事务。 - 设置事务隔离级别:使用
setTransactionIsolation()
方法指定所需的事务隔离级别。 - 执行事务:执行 SQL 操作。
- 提交或回滚事务:根据需要调用
commit()
或rollback()
方法。
示例代码
以下是一个简单的示例,展示了如何在 MySQL Connector/C++ 中设置事务隔离级别:
#include <mysql_driver.h>
#include <mysql_connection.h>
#include <cppconn/statement.h>
#include <cppconn/exception.h>
#include <iostream>
int main() {
try {
sql::mysql::MySQL_Driver *driver;
sql::Connection *con;
// 创建驱动并建立连接
driver = sql::mysql::get_mysql_driver_instance();
con = driver->connect("tcp://127.0.0.1:3306", "username", "password");
con->setSchema("your_database"); // 使用特定数据库
// 关闭自动提交
con->setAutoCommit(false);
// 设置事务隔离级别为可重复读(Repeatable Read)
con->setTransactionIsolation(sql::TRANSACTION_REPEATABLE_READ);
std::cout << "Set transaction isolation level to Repeatable Read." << std::endl;
// 开始执行事务内的SQL操作
sql::Statement *stmt = con->createStatement();
stmt->execute("YOUR SQL QUERY HERE");
// 根据实际情况提交或回滚事务
con->commit(); // 提交事务
std::cout << "Transaction committed." << std::endl;
delete stmt;
delete con;
} catch (sql::SQLException &e) {
std::cerr << "SQLException: " << e.what() << std::endl;
std::cerr << "SQLState: " << e.getSQLState() << std::endl;
}
return 0;
}
事务隔离级别枚举值
sql::TRANSACTION_READ_UNCOMMITTED
sql::TRANSACTION_READ_COMMITTED
sql::TRANSACTION_REPEATABLE_READ
sql::TRANSACTION_SERIALIZABLE
这些枚举值分别对应前面提到的四种标准事务隔离级别。选择适当的隔离级别取决于应用程序对一致性和并发性的要求。例如,在高并发环境下可能更倾向于选择 TRANSACTION_READ_COMMITTED
来减少锁定带来的性能影响;而在数据一致性要求较高的场景下,则可能选择 TRANSACTION_REPEATABLE_READ
或 TRANSACTION_SERIALIZABLE
。
【注意】更高的隔离级别通常会带来更多的锁争用,从而可能导致并发性能下降。因此,选择合适的事务隔离级别是平衡数据一致性和系统性能的重要部分。
使用保存点
Savepoint 是事务中的一个标记点。
- 它允许开发者:
- 在事务中设置一个中间点;
- 出现错误时,只回滚到该点,而不是整个事务;
- 提交事务时仍然保留保存点之前的操作。
主要 API 方法:
方法 | 描述 |
---|---|
con->setSavepoint("savepoint_name") | 设置一个命名保存点 |
con->rollback(savepoint) | 回滚到指定保存点 |
con->releaseSavepoint(savepoint) | 释放保存点 |
#include <mysql_driver.h>
#include <mysql_connection.h>
#include <cppconn/statement.h>
#include <cppconn/exception.h>
#include <iostream>
int main() {
try {
sql::mysql::MySQL_Driver *driver;
sql::Connection *con;
sql::Statement *stmt;
// 获取驱动并建立连接
driver = sql::mysql::get_mysql_driver_instance();
con = driver->connect("tcp://127.0.0.1:3306", "root", "password");
con->setSchema("testdb"); // 使用数据库 testdb
// 关闭自动提交,开启事务
con->setAutoCommit(false);
std::cout << "Auto Commit OFF" << std::endl;
stmt = con->createStatement();
// 初始数据
stmt->executeUpdate("UPDATE accounts SET balance = 1000 WHERE name = 'A'");
stmt->executeUpdate("UPDATE accounts SET balance = 500 WHERE name = 'B'");
std::cout << "Initial balances set." << std::endl;
// 设置第一个保存点
sql::Savepoint* savept1 = con->setSavepoint("savepoint_1");
// 操作1:A 转出 200 给 B
stmt->executeUpdate("UPDATE accounts SET balance = balance - 200 WHERE name = 'A'");
stmt->executeUpdate("UPDATE accounts SET balance = balance + 200 WHERE name = 'B'");
std::cout << "Transfer 200 from A to B." << std::endl;
// 设置第二个保存点
sql::Savepoint* savept2 = con->setSavepoint("savepoint_2");
// 操作2:A 再转出 300 给 B
stmt->executeUpdate("UPDATE accounts SET balance = balance - 300 WHERE name = 'A'");
stmt->executeUpdate("UPDATE accounts SET balance = balance + 300 WHERE name = 'B'");
std::cout << "Transfer 300 from A to B." << std::endl;
// 假设现在发现第二笔转账有问题,我们回滚到 savept1
std::cout << "Rolling back to savepoint_1..." << std::endl;
con->rollback(savept1);
// 释放不再需要的保存点
con->releaseSavepoint(savept2);
con->releaseSavepoint(savept1);
// 最终提交
con->commit();
std::cout << "Transaction committed after partial rollback." << std::endl;
delete stmt;
delete con;
} catch (sql::SQLException &e) {
std::cerr << "SQL Error: " << e.what() << std::endl;
std::cerr << "Error Code: " << e.getErrorCode() << std::endl;
std::cerr << "SQL State: " << e.getSQLState() << std::endl;
if (e.getErrorCode() != 0) {
try {
std::cerr << "Rolling back entire transaction..." << std::endl;
con->rollback();
} catch (...) {
std::cerr << "Rollback failed." << std::endl;
}
}
}
return 0;
}
编译命令参考(Linux / g++)
确保你链接了 Connector/C++ 库:
g++ -o transaction_example transaction.cpp -I/usr/include/cppconn -L/usr/lib -lmysqlcppconn
示例
使用MySQL Connector/C++对world数据库中的country表进行事务操作
using namespace mysqlx;
class ServerMainwindow : public QMainWindow
{
Q_OBJECT
public:
ServerMainwindow(QWidget *parent = nullptr);
~ServerMainwindow();
QString mysqlxValue2Qstring(const mysqlx::Value &value);
public slots:
void logPrint(QString strLog);
///...
void on_pbMysqlTransaction_clicked();
private:
Ui::ServerMainwindowClass ui;
QStandardItemModel* m_model;
Session* m_pCurSession = nullptr;
};
///---------cpp-----------------
void ServerMainwindow::on_pbMysqlTransaction_clicked()
{
try {
// 连接到MySQL服务器
Session sess("localhost", 33060, "root", "root", "world");
logPrint("Connected to MySQL server!");
// 开始事务
sess.startTransaction();
logPrint("Transaction started.");
// 获取country表的引用
Table country = sess.getSchema("world").getTable("country");
// 1. 插入新记录
country.insert("Code", "Name", "Continent", "Region", "Population")
.values("ZZZ", "Test Country", "Antarctica", "Test Region", 10000)
.execute();
logPrint("Inserted new country record.");
// 2. 更新记录
country.update()
.set("Population", 15000)
.where("Code = 'ZZZ'")
.execute();
logPrint("Updated country population.");
// 3. 查询记录以验证
RowResult result = country.select("Code", "Name", "Population")
.where("Code = 'ZZZ'")
.execute();
Row row = result.fetchOne();
if (!row.isNull()) {
std::cout << "Found record: "
<< row[0] << ", "
<< row[1] << ", "
<< row[2] << std::endl;
}
// 模拟错误条件 - 取消注释以下行将导致回滚
// throw runtime_error("Simulated error for rollback");
// 提交事务
sess.commit();
logPrint("Transaction committed successfully.");
}
catch (const Error &e) {
logPrint("MySQL Error: " + QString::fromStdString(e.what()));
std::cerr << "Rolling back transaction..." << std::endl;
// 不需要显式回滚,因为Session析构时会自动回滚未提交的事务
return ;
}
catch (const std::exception &e) {
logPrint("MySQL Error: " + QString::fromStdString(e.what()));
std::cerr << "Rolling back transaction..." << std::endl;
// 不需要显式回滚,因为Session析构时会自动回滚未提交的事务
return ;
}
}
总结
步骤 | 说明 |
---|---|
1. 连接数据库 | 使用 connect() 方法获取连接对象 |
2. 关闭自动提交 | con->setAutoCommit(false) |
3. 执行 SQL 操作 | 多个更新、插入等操作 |
4. 提交事务 | 成功则 con->commit() |
5. 异常处理 | 出错则 con->rollback() |
6. 清理资源 | 删除 Statement、Connection 等对象 |
事务的使用时机
事务(Transaction)在以下情况下应该使用:
-
需要保证数据一致性时:当多个操作必须全部成功或全部失败时
-
例如银行转账:一个账户扣款和另一个账户加款必须同时成功或同时失败
-
-
批量操作需要原子性时:一系列SQL语句需要作为一个不可分割的单元执行
-
例如订单系统中创建订单、减少库存、记录日志等操作
-
-
并发控制需要时:当多个用户/进程可能同时访问相同数据时
-
使用事务可以防止脏读、不可重复读和幻读问题
-
-
需要回滚能力时:当操作可能失败需要撤销已执行的部分时
单独使用SQL语句的时机
单独执行SQL语句(不使用事务)在以下情况下更合适:
-
简单查询操作:只读操作,不修改数据
-
例如简单的SELECT查询
-
-
单一操作:不需要与其他操作保持一致的独立操作
-
例如记录访问日志(即使失败也不影响主要业务)
-
-
性能要求极高时:事务会有一定开销,在极高吞吐量场景下可能考虑不用
-
自动提交模式足够时:某些数据库操作默认自动提交,能满足需求时
实践建议
-
尽量保持事务简短,减少锁定资源的时间
-
避免在事务中进行用户交互
-
合理选择隔离级别,平衡一致性和性能
-
处理异常时确保事务被正确回滚
-
考虑使用连接池管理数据库连接
事务是确保数据完整性的重要机制,正确使用事务可以避免许多数据一致性问题。