Python 2.7 使用PythonSQLite3的事务
我正在尝试将一些代码移植到使用sqlite数据库的Python中,我正在尝试让事务正常工作,我真的很困惑。我真的被这搞糊涂了;我在其他语言中使用过很多sqlite,因为它很棒,但我根本不知道这里出了什么问题 下面是我的测试数据库的模式(将输入sqlite3命令行工具) 这是一个测试程序Python 2.7 使用PythonSQLite3的事务,python-2.7,sqlite,python-db-api,Python 2.7,Sqlite,Python Db Api,我正在尝试将一些代码移植到使用sqlite数据库的Python中,我正在尝试让事务正常工作,我真的很困惑。我真的被这搞糊涂了;我在其他语言中使用过很多sqlite,因为它很棒,但我根本不知道这里出了什么问题 下面是我的测试数据库的模式(将输入sqlite3命令行工具) 这是一个测试程序 import sqlite3 sql = sqlite3.connect("test.db") with sql: c = sql.cursor() c.executescript("""
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脚本在第二行失败
根据文档,sql语句应该围绕内容设置一个隐式事务,只有在块成功时才会提交。然而,当我运行它时,我得到了预期的SQL错误。。。但是i的值设置为99到1。我希望它保持在99,因为第一次更新应该回滚
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,)
下面是另一个测试程序,它显式调用commit()
和rollback()
它的行为方式完全相同——我从99变为1
现在我调用BEGIN并显式提交:
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\u level
似乎对行为没有影响。)
有人能给我解释一下这里发生了什么事吗?我需要理解这一点;如果我不能信任数据库中的事务,我就无法使我的应用程序工作
Python 2.7、Python-sqlite3 2.6.0、sqlite3 3.7.13、Debian。Python的DB API试图变得智能,并且
我建议使用不使用Python DB API的DB驱动程序,如。对于任何想使用sqlite3 lib的人,不管它有什么缺点,我发现如果你做以下两件事,你可以控制一些事务:
设置Connection.isolation\u level=None
(根据,这意味着自动提交模式)
完全避免使用executescript
,因为根据it“首先发出提交语句”——即,麻烦。事实上,我发现它会干扰任何手动设置的事务
因此,以下对您的测试进行的调整对我很有用:
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")
每
连接对象可以用作上下文管理器,自动
提交或回滚事务。如果发生异常,则
事务被回滚;否则,事务将被提交:
因此,如果在发生异常时让Python退出with语句,事务将回滚
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,)
正如预期的那样。您可以将连接用作上下文管理器。然后,它将在发生异常时自动回滚事务或以其他方式提交它们
试试看:
与con:
con.execute(“插入个人(名字)值(?),(“Joe”))
除sqlite3.IntegrityError外:
打印(“无法添加Joe两次”)
请参阅正常.execute()
的工作,使用舒适的默认自动提交模式和连接:…
上下文管理器执行自动提交或回滚-除了受保护的读-修改-写事务,这将在本答案的末尾解释
sqlite3模块的非标准conn\u或\u cursor.executescript()
不参与(默认)自动提交模式(因此不能正常使用带有conn:…
上下文管理器的),而是将脚本原始地转发。因此,它只是在开始时提交一个潜在的挂起的自动提交事务,然后才开始“原始”
这也意味着,如果脚本executescript()
中没有“开始”,则在没有事务的情况下工作,因此在出现错误或其他情况时没有回滚选项
因此,对于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,)]
>>>
(注意此处通过脚本回滚,因为没有.execute()
接管提交控制)
这里有一个关于自动提交模式的注释,它与一个更困难的问题相结合,即受保护的读-修改-写事务——这使得@Jeremie说:“在用sqlite/python编写的关于事务的很多事情中,这是唯一让我做我想做的事情(在数据库上有一个独占的读锁)”在对一个示例的评论中,该示例包含一个c.execute(“begin”)
。虽然sqlite3通常不会在实际写回的持续时间之外进行长阻塞独占读锁,但更聪明的5阶段锁可以实现足够的保护,防止重叠更改
带有conn:
的自动提交上下文尚未放置或触发足够强的锁,以在中进行受保护的读-修改-写操作。只有在发出第一个数据修改命令时,才会隐式地进行这种锁定,因此太迟了。
只有显式的开始(延迟)(事务)
触发所需的行为:
对数据库的操作将创建共享锁和
第一次写入操作创建一个保留锁
因此,一般使用编程语言(而不是特殊的原子SQL更新子句)的受保护读-修改-写事务如下所示:
>>> 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,)]
>>>
with conn:
conn.execute('BEGIN TRANSACTION') # crucial !
v = conn.execute('SELECT * FROM test').fetchone()[0]
v = v + 1
time.sleep(3) # no read lock in effect, but only one concurrent modify succeeds
conn.execute('UPDATE test SET i=?', (v,))
一旦失败,这种读-修改-写事务可以重试几次。以下是我根据我的读操作所认为的情况
>>> 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,)]
>>>
with conn:
conn.execute('BEGIN TRANSACTION') # crucial !
v = conn.execute('SELECT * FROM test').fetchone()[0]
v = v + 1
time.sleep(3) # no read lock in effect, but only one concurrent modify succeeds
conn.execute('UPDATE test SET i=?', (v,))
with connection:
db.execute("BEGIN")
# do other things, but do NOT use 'executescript'