这个问题末尾的(令人遗憾的是冗长的)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()
答案 0 :(得分:1)
对临时表使用第二个单独的连接,它不会受到另一个连接上的提交的影响。