Dav*_*ven 32 sqlite python-2.7 python-db-api
我正在尝试将一些代码移植到使用sqlite数据库的Python,而我正试图让事务工作,我真的很困惑.我真的很困惑; 我在其他语言中经常使用sqlite,因为它很棒,但我根本无法弄清楚这里有什么问题.
这是我的测试数据库的模式(将被输入sqlite3命令行工具).
BEGIN TRANSACTION;
CREATE TABLE test (i integer);
INSERT INTO "test" VALUES(99);
COMMIT;
Run Code Online (Sandbox Code Playgroud)
这是一个测试程序.
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;
""")
Run Code Online (Sandbox Code Playgroud)
你可能会注意到故意的错误.执行更新后,这会导致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()
Run Code Online (Sandbox Code Playgroud)
这种行为完全相同 - 我从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")
Run Code Online (Sandbox Code Playgroud)
这也失败了,但是以不同的方式.我明白了:
sqlite3.OperationalError: cannot rollback - no transaction is active
Run Code Online (Sandbox Code Playgroud)
但是,如果我将呼叫替换c.execute()为c.executescript(),那么它可以工作(我仍然是99)!
(我还要补充一点,如果我把begin和commit内部通话内部,executescript然后将它正确地在所有情况下的表现,但遗憾的是我不能用我的应用程序的方法.此外,改变sql.isolation_level似乎使该行为没有什么区别. )
有人可以向我解释这里发生了什么吗?我需要理解这一点; 如果我不能信任数据库中的事务,我无法使我的应用程序工作......
Python 2.7,python-sqlite3 2.6.0,sqlite3 3.7.13,Debian.
yun*_*hin 29
对于任何想要使用sqlite3 lib而不管其缺点的人,我发现如果你做这两件事,你可以控制交易:
Connection.isolation_level = None(根据文档,这意味着自动提交模式)executescript,因为根据文档,它"先发出一个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")
Run Code Online (Sandbox Code Playgroud)
unu*_*tbu 17
根据文档,
连接对象可以用作自动提交或回滚事务的上下文管理器.如果发生异常,则回滚事务; 否则,交易承诺:
因此,如果您在发生异常时让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,)
Run Code Online (Sandbox Code Playgroud)
产量
(99,)
Run Code Online (Sandbox Code Playgroud)
正如所料.
CL.*_*CL. 12
Python的DB API试图变得聪明,并自动开始并提交事务.
我建议使用不使用Python DB API 的数据库驱动程序,如apsw.
根据我对Python的sqlite3绑定以及官方Sqlite3文档的阅读,这是我认为正在发生的事情。简短的答案是,如果您想进行适当的交易,则应遵循以下习惯用法:
with connection:
db.execute("BEGIN")
# do other things, but do NOT use 'executescript'
Run Code Online (Sandbox Code Playgroud)
相反,我的直觉,with connection也没有调用BEGIN在进入的范围。实际上,它根本不做任何事情__enter__。仅当您选择__exit__范围时,或者根据范围是正常退出还是有异常退出时,它才有效。COMMITROLLBACK
因此,正确的做法是始终使用明确标记交易的开始BEGIN。这使事务内isolation_level 无关紧要,因为幸运的是,它仅在启用自动提交模式时才起作用,并且始终在事务块内禁止自动提交模式。
另一个怪癖是executescript,它总是COMMIT在运行脚本之前发出a。这很容易使交易混乱,因此您可以选择
executescript在交易中仅使用一个,而不使用其他任何东西,或者executescript完全避免;您可以execute根据一个execute限制的任意次数拨打任意电话。| 归档时间: |
|
| 查看次数: |
35691 次 |
| 最近记录: |