SmartESQL事务管理
所有 SmartEDB 数据库访问均在事务内执行,以通过强制实施 ACID 原则来确保数据库的完整性。
有关事务处理的详细说明,请参阅用户指南。
在使用 SmartESQL API 时,事务处理是隐式的,即运行时会自动启动和提交事务,而无需显式调用函数。
- 例如,当执行 SQL SELECT 语句时,McoSqlEngine 方法
executeStatment()
会启动并关闭一个只读事务(除非指定了 FOR UPDATE 子句),而在执行 INSERT、UPDATE 或 DELETE 语句时会启动一个读写事务。同样,QueryResult 构造函数会启动一个只读事务并返回结果集游标;其析构函数会关闭事务。
不过,某些应用程序需要执行更新操作,这些更新操作需要跨多个 SmartESQL 函数调用进行事务阻塞。
- 例如,进入“货币兑换”环节,此时需要同时记录贷方和借方的更新。如果在更新之间由于某些系统故障中断了应用程序,数据库数据就会处于不一致的状态。事务阻塞可确保要么两个更新都成功,要么都不应用到数据库中。
若要通过 SmartEDB 来掌控事务处理,应用程序会使用 McoSqlEngine 方法 beginTransaction()
来创建一个事务对象,然后调用事务方法 commit()
来完成数据库操作的代码块。
多线程应用程序将使用继承 McoSqlEngine 方法的 McoSqlSession 对象。
可以在事务仍处于打开状态时使用事务方法 checkpoint()
来更新索引并使对象对查询可见,或者使用 rollback()
来撤销此事务中执行的所有操作。
示例
// 定义全局 McoSQL 引擎
using namespace McoSql;
McoMultithreadedSqlEngine engine;
// 定义与数据库记录“Person”相对应的结构。
struct _Member
{
int id;
char * name;
int balance;
};
int main()
{
_Member m;
// 打开 SmartEDB 数据库和 SQL 引擎
engine.open( db_name, exchangedb_get_dictionary(), DATABASE_SIZE, MEMORY_PAGE_SIZE);
// 创建一个 McoSession 来管理事务
McoSqlSession session(&engine);
// 插入两条"Member"记录,初始余额均为 100 。
m.id = 201000001;
m.name = "John Smith";
m.balance = 100;
session.executeStatement("insert into Member %r", &m);
m.id = 201000002;
m.name = "Peter Brown";
m.balance = 100;
session.executeStatement("insert into Member %r", &m);
// 列出“Member”表的内容
printf ("\n\tInitial Member records:\n");
listMembers( &session );
// 从Smith到Brown的50个单位的交换并显示结果
doExchange( &session, 201000001, 201000002, 50 );
printf ("\n\tAfter an exchange of 50 units from 'Smith' to 'Brown':\n");
listMembers( &session );
// 关闭 SQL 会话、引擎和数据库
engine.close();
return 0;
}
void doExchange( McoSqlSession * session, int idFrom, int idTo, int amount)
{
int fromBalance = getBalance( session, idFrom ) - amount;
int toBalance = getBalance( session, idTo ) + amount;
// 执行两个更新操作,这两个操作都必须在一个数据库事务中完成
Transaction* trans = session->database()->beginTransaction(Transaction::ReadWrite);
session->executeStatement(trans, "update Member set balance=%i where id=%i",
fromBalance, idFrom);
session->executeStatement(trans, "update Member set balance=%i where id=%i",
toBalance, idTo);
trans->commit();
trans->release();
}
int getBalance( McoSqlSession * session, int id )
{
QueryResult result(session->executeQuery( "select * from Member where id=%i", id ) );
Cursor* cursor = result->records();
Record* rec = cursor->next();
_Member m;
result->extract(rec, &m, sizeof(m));
return m.balance;
}
void listMembers( McoSqlSession * session )
{
QueryResult result(session->executeQuery("select * from Member order by id"));
Cursor* cursor = result->records();
while (cursor->hasNext())
{
_Member m;
Record* rec = cursor->next();
result->extract(rec, &m, sizeof(m));
printf("\t\t%s: Balance=%d\n", m.name, m.balance );
}
}
在上述代码示例中需要注意的一些编程要点:
- 调用 McoSqlSession 构造函数来实例化一个会话对象,然后所有后续的数据库访问都通过此会话进行。
- McoMultithreadedSqlEngine 对象仅用于打开和关闭运行时和数据库。
- 通过调用会话对象的数据库成员上的
beginTransaction()
方法来实例化事务对象。 - 完成数据库更新后,通过调用事务对象的
commit()
和release()
方法来关闭事务对象并释放其内存。
检查点和自动检查点
在复杂的事务中,可能需要执行检查点操作,以便在事务提交之前更新索引。可以按如下方式调用 C++ API 函数来触发检查点:
{
int fromBalance = getBalance( session, idFrom ) - amount;
int toBalance = getBalance( session, idTo ) + amount;
// Perform two updates that must both complete within a single database transaction
Transaction* trans = session->database()->beginTransaction(Transaction::ReadWrite);
session->executeStatement(trans, "update Member set balance=%i where id=%i",
fromBalance, idFrom);
trans->checkpoint();
QueryResult result(session->executeQuery("select * from Member order by id"));
...
trans->commit();
trans->release();
}
或者可以使用以下 SQL 语句来触发检查点:
executeStatement("CHECKPOINT TRANSACTION");
要启用自动检查点功能,请设置 `SqlEngine.autoCheckpoint
using namespace McoSql;
McoSqlEngine engine;
int main(int argc, char* argv[])
{
engine.autoCheckpoint = true;
...
}
或者在数据库参数 mco_db_params_t.mode_mask
中设置 MCO_DB_SQL_AUTOCHECKPOINT
标志。例如:
int main(int argc, char* argv[])
{
...
mco_db_params_t db_params;
...
mco_db_params_init( &db_params ); /* 用默认值初始化参数 */
...
db_params.mode_mask = MCO_DB_SQL_AUTOCHECKPOINT;
...
}