(python-)sqlite3:阻止COMMIT重置不相关的临时表上的读取游标

时间:2014-02-24 18:05:51

标签: python sql sqlite transactions

这个问题末尾的(令人遗憾的是冗长的)MWE从实际应用中删除。 假设这样工作:有两个表。一个包括已处理和尚未处理的数据,另一个包含处理数据的结果。在启动时,我们创建一个临时表,列出尚未处理的所有数据。然后我们在该表上打开一个读取光标并从头到尾扫描它;对于每个数据,我们进行一些处理(在MWE中省略),然后使用单独的游标将结果插入到处理数据表中。

这在自动提交模式下可以正常工作。但是,如果写操作包含在事务中 - 并且在实际应用程序中,它必须是,因为写实际上接触了几个表(除了其中一个表已从MWE中省略) - 然后是COMMIT操作具有重置临时表上的读取游标的副作用,导致已经处理的行被重新处理,这不仅阻止了向前进展,而且在尝试插入时导致程序崩溃IntegrityError一个重复的行进入data_out。如果您运行MWE,您应该看到此输出:

0
1
2
3
4
5
6
7
8
9
10
0
---
127 rows remaining
Traceback (most recent call last):
  File "sqlite-test.py", line 85, in <module>
    test_main()
  File "sqlite-test.py", line 83, in test_main
    test_run(db)
  File "sqlite-test.py", line 71, in test_run
    (row[0], b"output"))
sqlite3.IntegrityError: UNIQUE constraint failed: data_out.value

如何通过COMMIT触摸不相关的表来阻止读取光标被重置?

注意:架构中的所有INTEGER都是ID号;在实际应用程序中,有几个辅助表可以为每个ID保存更多信息,而写入事务除了data_out之外还会触及其中的两个或三个,具体取决于计算结果。在实际应用程序中,临时“data_todo”表可能非常大 - 数百万行;我开始走这条路正是因为Python列表太大而无法适应内存。 MWE的shebang用于python3但在python2下它的行为完全相同(前提是解释器足够新以理解b"..."字符串)。设置PRAGMA locking_mode = EXCLUSIVE;和/或PRAGMA journal_mode = WAL;对此现象没有影响。我正在使用SQLite 3.8.2。

#! /usr/bin/python3

import contextlib
import sqlite3
import sys
import tempfile
import textwrap

def init_db(db):
    db.executescript(textwrap.dedent("""\
        CREATE TABLE data_in (
            origin    INTEGER,
            origin_id INTEGER,
            value     INTEGER,
            UNIQUE(origin, origin_id)
        );
        CREATE TABLE data_out (
            value     INTEGER PRIMARY KEY,
            processed BLOB
        );
        """))

    db.executemany("INSERT INTO data_in VALUES(?, ?, ?);",
                   [ (1, x, x) for x in range(100) ])
    db.executemany("INSERT INTO data_in VALUES(?, ?, ?);",
                   [ (2, x, 200 - x*2) for x in range(100) ])

    db.executemany("INSERT INTO data_out VALUES(?, ?);",
                   [ (x, b"already done") for x in range(50, 130, 5) ])

    db.execute(textwrap.dedent("""\
        CREATE TEMPORARY TABLE data_todo AS
            SELECT DISTINCT value FROM data_in
            WHERE value NOT IN (SELECT value FROM data_out)
            ORDER BY value;
        """))

def test_run(db):
    init_db(db)

    read_cur  = db.cursor()
    write_cur = db.cursor()

    read_cur.arraysize = 10
    read_cur.execute("SELECT * FROM data_todo;")

    try:
        while True:
            block = read_cur.fetchmany()
            if not block: break
            for row in block:
                # (in real life, data actually crunched here)
                sys.stdout.write("{}\n".format(row[0]))
                write_cur.execute("BEGIN TRANSACTION;")
                # (in real life, several more inserts here)
                write_cur.execute("INSERT INTO data_out VALUES(?, ?);",
                                  (row[0], b"output"))
                db.commit()

    finally:
        read_cur.execute("SELECT COUNT(DISTINCT value) FROM data_in "
                         "WHERE value NOT IN (SELECT value FROM data_out)")
        result = read_cur.fetchone()
        sys.stderr.write("---\n{} rows remaining\n".format(result[0]))

def test_main():
    with tempfile.NamedTemporaryFile(suffix=".db") as tmp:
        with contextlib.closing(sqlite3.connect(tmp.name)) as db:
            test_run(db)

test_main()

1 个答案:

答案 0 :(得分:1)

对临时表使用第二个单独的连接,它不会受到另一个连接上的提交的影响。