Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/sqlite/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 2.7 使用PythonSQLite3的事务_Python 2.7_Sqlite_Python Db Api - Fatal编程技术网

Python 2.7 使用PythonSQLite3的事务

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("""

我正在尝试将一些代码移植到使用sqlite数据库的Python中,我正在尝试让事务正常工作,我真的很困惑。我真的被这搞糊涂了;我在其他语言中使用过很多sqlite,因为它很棒,但我根本不知道这里出了什么问题

下面是我的测试数据库的模式(将输入sqlite3命令行工具)

这是一个测试程序

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'