我正在尝试将一些代码移植到使用sqlite数据库的Python,而我正试图让事务工作,我真的很困惑。我真的很困惑;我在其他语言中使用了很多sqlite,因为它很棒,但我根本无法弄清楚这里有什么问题。
这是我的测试数据库的架构(将被送入sqlite3命令行工具)。
BEGIN TRANSACTION;
CREATE TABLE test (i integer);
INSERT INTO "test" VALUES(99);
COMMIT;
这是一个测试程序。
import sqlite3
sql = sqlite3.connect("test.db")
with sql:
c = sql.cursor()
c.executescript("""
update test set i = 1;
fnord;
update test set i = 0;
""")
你可能会注意到其中的故意错误。执行更新后,这会导致SQL脚本在第二行失败。
根据文档,with sql
语句应该围绕内容设置隐式事务,只有在块成功时才会提交。但是,当我运行它时,我得到了预期的SQL错误...但是i的值从99设置为1.我希望它保持在99,因为第一次更新应该回滚。
这是另一个测试程序,它明确调用commit()
和rollback()
。
import sqlite3
sql = sqlite3.connect("test.db")
try:
c = sql.cursor()
c.executescript("""
update test set i = 1;
fnord;
update test set i = 0;
""")
sql.commit()
except sql.Error:
print("failed!")
sql.rollback()
这种行为完全相同 - 我从99变为1.
现在我明确地调用BEGIN和COMMIT:
import sqlite3
sql = sqlite3.connect("test.db")
try:
c = sql.cursor()
c.execute("begin")
c.executescript("""
update test set i = 1;
fnord;
update test set i = 0;
""")
c.execute("commit")
except sql.Error:
print("failed!")
c.execute("rollback")
这也失败了,但是以不同的方式。我明白了:
sqlite3.OperationalError: cannot rollback - no transaction is active
但是,如果我将c.execute()
的来电替换为c.executescript()
,那么的工作(我仍然是99)!
(我还应该补充一点,如果我将begin
和commit
置于executescript
的内部调用中,那么它在所有情况下都会正常运行,但不幸的是我不能使用它我的应用程序中的方法。此外,更改sql.isolation_level
似乎对行为没有任何影响。)
有人可以向我解释这里发生了什么吗?我需要理解这一点;如果我不能信任数据库中的事务,我就无法使我的应用程序工作......
Python 2.7,python-sqlite3 2.6.0,sqlite3 3.7.13,Debian。
答案 0 :(得分:29)
对于任何想要使用sqlite3 lib而不管其缺点的人,我发现如果你做这两件事,你可以控制交易:
Connection.isolation_level = None
(根据docs,这意味着自动提交模式)executescript
,因为根据docs,它“首先发出一个COMMIT语句” - 即麻烦。事实上,我发现它会干扰任何手动设置的交易那么,以下适合您的测试适用于我:
import sqlite3
sql = sqlite3.connect("/tmp/test.db")
sql.isolation_level = None
c = sql.cursor()
c.execute("begin")
try:
c.execute("update test set i = 1")
c.execute("fnord")
c.execute("update test set i = 0")
c.execute("commit")
except sql.Error:
print("failed!")
c.execute("rollback")
答案 1 :(得分:17)
每the docs,
连接对象可以自动用作上下文管理器 提交或回滚事务。如果发生例外,则 交易回滚;否则,交易已经提交:
因此,如果您在发生异常时让Python退出with-statement,则将回滚该事务。
import sqlite3
filename = '/tmp/test.db'
with sqlite3.connect(filename) as conn:
cursor = conn.cursor()
sqls = [
'DROP TABLE IF EXISTS test',
'CREATE TABLE test (i integer)',
'INSERT INTO "test" VALUES(99)',]
for sql in sqls:
cursor.execute(sql)
try:
with sqlite3.connect(filename) as conn:
cursor = conn.cursor()
sqls = [
'update test set i = 1',
'fnord', # <-- trigger error
'update test set i = 0',]
for sql in sqls:
cursor.execute(sql)
except sqlite3.OperationalError as err:
print(err)
# near "fnord": syntax error
with sqlite3.connect(filename) as conn:
cursor = conn.cursor()
cursor.execute('SELECT * FROM test')
for row in cursor:
print(row)
# (99,)
产量
(99,)
正如所料。
答案 2 :(得分:13)
Python的DB API试图变得聪明,begins and commits transactions automatically。
我建议使用不使用Python DB API的数据库驱动程序,例如apsw。
答案 3 :(得分:4)
根据我对Python的sqlite3绑定以及官方Sqlite3文档的阅读,我认为这是我发生的事情。简短的回答是,如果你想要一个正确的交易,你应该坚持这个成语:
with connection:
db.execute("BEGIN")
# do other things, but do NOT use 'executescript'
与我的直觉相反,with connection
在进入范围时 调用BEGIN
。实际上它是doesn't do anything at all in __enter__
。只有__exit__
范围choosing either COMMIT
or ROLLBACK
depending on whether the scope is exiting normally or with an exception时,它才会生效。
因此,正确的做法是始终使用BEGIN
明确标记交易的开头。这会在交易中呈现isolation_level
无关,因为谢天谢地它只在autocommit mode is enabled和autocommit mode is always suppressed within transaction blocks时生效。
另一个怪癖是executescript
,always issues a COMMIT
before running your script。这很容易弄乱交易,所以你可以选择
executescript
而不是其他任何内容,或executescript
;您可以根据需要多次拨打execute
,但需遵守每个execute
一个语句的限制。答案 4 :(得分:2)
您可以将连接用作上下文管理器。然后,它会在发生异常时自动回滚事务或以其他方式提交事务。
try:
with con:
con.execute("insert into person(firstname) values (?)", ("Joe",))
except sqlite3.IntegrityError:
print("couldn't add Joe twice")
请参阅https://docs.python.org/3/library/sqlite3.html#using-the-connection-as-a-context-manager
答案 5 :(得分:2)
正常.execute()
正常工作,具有舒适的默认自动提交模式,with conn: ...
上下文管理器执行自动提交 OR 回滚 - 除外受保护的读 - 修改 - 写交易,这在本答复的最后解释。
sqlite3模块的非标准conn_or_cursor.executescript()
不参与(默认)自动提交模式(因此不能与with conn: ...
上下文管理器一起正常工作)但转发脚本而不是生的。因此,它只是在“开始生效”之前提交潜在的待处理自动提交事务开始。
这也意味着如果脚本executescript()
内部没有“BEGIN”,则无需事务,因此在出错或其他情况下无回滚选项。
因此,对于executescript()
,我们最好使用显式BEGIN(正如您的初始模式创建脚本为“原始”模式sqlite命令行工具所做的那样)。这种互动逐步显示了什么:
>>> list(conn.execute('SELECT * FROM test'))
[(99,)]
>>> conn.executescript("BEGIN; UPDATE TEST SET i = 1; FNORD; COMMIT""")
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
OperationalError: near "FNORD": syntax error
>>> list(conn.execute('SELECT * FROM test'))
[(1,)]
>>> conn.rollback()
>>> list(conn.execute('SELECT * FROM test'))
[(99,)]
>>>
脚本未到达“COMMIT”。因此我们可以查看当前的中间状态并决定回滚(或提交)
因此,通过excecutescript()
进行的try-except-rollback工作如下所示:
>>> list(conn.execute('SELECT * FROM test'))
[(99,)]
>>> try: conn.executescript("BEGIN; UPDATE TEST SET i = 1; FNORD; COMMIT""")
... except Exception as ev:
... print("Error in executescript (%s). Rolling back" % ev)
... conn.executescript('ROLLBACK')
...
Error in executescript (near "FNORD": syntax error). Rolling back
<sqlite3.Cursor object at 0x011F56E0>
>>> list(conn.execute('SELECT * FROM test'))
[(99,)]
>>>
(注意这里通过脚本回滚,因为没有.execute()
接管提交控制)
这里有一个关于自动提交模式的说明,结合了受保护的读 - 修改 - 写事务这个更难的问题 - 这使@Jeremie说“ Out of all关于sqlite / python中的事务写的很多很多东西,这是让我做我想做的唯一事情(对数据库有一个独占的读锁)。“在一个包含一个{的示例的注释中{1}}。虽然sqlite3通常不会进行长阻塞独占读锁定,但实际写回的持续时间除外,但更聪明的5阶段锁定可以实现足够的保护,防止重叠更改。
c.execute("begin")
自动提交上下文尚未在5-stage locking scheme of sqlite3中放置或触发足够强的锁以进行受保护的读 - 修改 - 写。只有在发出第一个数据修改命令时才会隐含地进行这种锁定 - 因此为时已晚。
只有明确的with conn:
才会触发想要的行为:
针对数据库的The first read操作会创建一个SHARED锁 第一个写操作创建一个RESERVED锁。
因此,一般使用编程语言(而不是特殊的原子SQL UPDATE子句)的受保护读 - 修改 - 写事务如下所示:
BEGIN (DEFERRED) (TRANSACTION)
失败时,这样的读 - 修改 - 写事务可能会重试几次。
答案 6 :(得分:0)
这是一个有点老的线程,但如果它有帮助我发现在连接对象上做回滚就可以了。