使用Python sqlite3进行交易

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)!

(我还要补充一点,如果我把begincommit内部通话内部,executescript然后将它正确地在所有情况下的表现,但遗憾的是我不能用我的应用程序的方法.此外,改变sql.isolation_level似乎使该行为没有什么区别. )

有人可以向我解释这里发生了什么吗?我需要理解这一点; 如果我不能信任数据库中的事务,我无法使我的应用程序工作......

Python 2.7,python-sqlite3 2.6.0,sqlite3 3.7.13,Debian.

yun*_*hin 29

对于任何想要使用sqlite3 lib而不管其缺点的人,我发现如果你做这两件事,你可以控制交易:

  1. 设置Connection.isolation_level = None(根据文档,这意味着自动提交模式)
  2. 完全避免使用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)

  • "自动提交"模式的含义是什么?根据它的名字,我猜任何类型的MDL语句都将自动提交.但根据你的解释,似乎我的猜测是错误的.谁能帮帮我?我在谷歌搜索它,也无法得到任何有用的答案. (5认同)
  • ↑我可以回答这个问题的自动提交部分。`isolation_level = None`为您禁用了Python包装器对发出`BEGIN`等的自动处理。剩下的是底层的C库,默认情况下它会“自动提交”。但是,当您执行`BEGIN`(b / c您正在使用该语句发出交易信号)时,该自动提交被禁用,这就是上面的工作方式起作用的原因。[关于此的SQLite文档](https://www.sqlite.org/c3ref/get_autocommit.html) (3认同)
  • 我要补充的是,您也可以设置交易级别。例如 `c.execute("开始独占事务")`。 (2认同)

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)

正如所料.

  • -1 为每笔交易打开一个新连接的效率非常低,而且完全没有必要。您也没有说明事务何时开始(例如,您是否知道删除并创建表的第一个块实际上具有三个单独的事务?) (2认同)

CL.*_*CL. 12

Python的DB API试图变得聪明,并自动开始并提交事务.

我建议使用使用Python DB API 的数据库驱动程序,如apsw.

  • 谢谢,apsw 正是我要找的。不过,我仍然很困惑;如果 python-sqlite3 的事务处理被破坏了,为什么没有人注意到并修复它,因为它似乎是 Python 的默认 Sqlite 绑定?事务肯定是任何 SQL 库的核心能力吗? (4认同)
  • 这是其他一些数据库如何使用他们的Python驱动程序.Python DBAPI规范尝试使它看起来像所有数据库的功能相同,具有相同的语义,这就是pysqlite以这种方式工作的原因.当然,SQLite与*非常不同,这就是我首先编写APSW的原因.请参阅http://apidoc.apsw.googlecode.com/hg/pysqlite.html (4认同)
  • 从python3.6开始,文档读取:>在版本3.6中更改:sqlite3用于在DDL语句之前隐式提交打开的事务.这已不再是这种情况. (2认同)

Ruf*_*ind 5

根据我对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限制的任意次数拨打任意电话。