我有一整套数据要插入到表格中。我试图让它插入/更新所有内容或回滚。我打算在事务中执行此操作,但我不确定 sql_exec() 命令是否执行相同的操作。
我的目标是遍历列表。 根据主键从每次迭代中选择。
If result was found:
append update to string;
else
append insert to string;
然后在遍历循环之后,我会得到一个巨大的字符串并说:
sql_exec(字符串); sql_close(db);
我应该这样做吗?我打算在循环的每次迭代中都这样做,但我不认为如果出现错误就进行全局回滚。
最佳答案
不,您不应该将所有内容附加到一个巨大的字符串中。如果这样做,您将需要分配一大堆内存,并且很难为每个单独的语句创建良好的错误消息,因为整个字符串只会出现一个错误。当 SQLite 必须再次将其解析回其单独的语句时,为什么要花费所有这些努力来构建一个大字符串?
相反,正如@Chad 建议的那样,您应该只在 BEGIN
语句上使用 sqlite3_exec()
,这将开始一个事务。然后 sqlite3_exec()
依次执行每个语句,最后 sqlite3_exec()
COMMIT
或 ROLLBACK
取决于一切如何去。 BEGIN
语句将启动一个事务,之后执行的所有语句都将在该事务内,因此一起提交或回滚。这就是 ACID 中的“A”代表;原子性,因为事务中的所有语句都将被提交或回滚,就好像它们是单个原子操作一样。
此外,如果某些数据在每个语句中变化,例如从文件中读取,您可能不应该使用 sqlite3_exec()
。如果你这样做,一个错误很容易给你留下一个SQL injection。漏洞。例如,如果您通过附加字符串构造查询,并且要插入像 char *str = "it's a string"
这样的字符串,如果您没有正确引用它,您的语句可能会出现像 INSERT INTO table VALUES ('it's a string');
,这将是一个错误。或者,如果有人恶意将数据写入此文件,那么他们可能会导致您执行他们想要的任何 SQL 语句(imagine 如果字符串是 "'); DROP TABLE my_important_table; --"
) .您可能认为没有恶意的人会提供输入,但如果有人将混淆 SQL 解析器的字符放入字符串中,您仍然会遇到意外问题。
相反,您应该使用 sqlite3_prepare_v2()
和 sqlite3_bind_...()
(其中 ...
是类型,如 int
或 double
或 text
)。为此,您可以使用 char *query = "INSERT INTO table VALUES (?)"
之类的语句,将 ?
替换为您想要参数的位置开始,使用 sqlite3_prepare_v2(db, query, -1, &stmt, NULL)
准备它,使用 sqlite3_bind_text(stmt, 1, str, -1, SQLITE_STATIC)
绑定(bind)参数>,然后执行带有sqlite3_step(stmt)
的语句.如果该语句返回任何数据,您将得到 SQLITE_ROW
,并且可以使用各种 sqlite3_columne_...()
访问数据职能。请务必仔细阅读文档;我给出的一些示例参数可能需要根据您的使用方式进行更改。
是的,这比调用 sqlite3_exec()
要麻烦一些,但是如果您的查询有任何从外部源(文件、用户输入)加载的数据,这是唯一的方法正确地做。 sqlite3_exec()
如果查询的整个文本都包含在您的源代码中,例如 BEGIN
和 COMMIT
或 ROLLBACK
语句,或没有来自程序外部的任何部分的预写查询,如果有任何意外字符串可能进入的机会,您只需要准备/绑定(bind)。
最后,您不需要查询数据库中是否已经存在某些内容,然后再插入或更新它。您可以执行 INSERT OR REPLACE
查询,这将插入一条记录,或者用匹配的主键替换一条记录,这相当于选择然后执行 INSERT
或 UPDATE
,但更快更简单。查看INSERT
和 "on conflict"文档以获取更多详细信息。
关于c++ - sqlite3 事务和 exec 调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13278679/