Python,SQLite3:当提交介入时,游标返回重复项

时间:2014-12-23 16:14:10

标签: python sqlite cursor

这个Python代码创建一个表,在其中插入三行并遍历行,并在光标完全耗尽之前进行干预提交。为什么它返回五行而不是三行?如果删除介入提交,则返回的行数按预期为三。或者是否期望提交(甚至不触及相关表)会使游标无效?

修改:添加了一个忘记的提交(使问题消失)和插入到不相关的表(这会使问题再次出现)。

#!/usr/bin/env python3

import sqlite3 as sq

db = sq.connect(':memory:')

db.execute('CREATE TABLE tbl (col INTEGER)')
db.execute('CREATE TABLE tbl2 (col INTEGER)')
db.executemany('INSERT INTO tbl (col) VALUES (?)', [(0,), (1,), (2,)])
db.commit()

print('count=' + str(db.execute('SELECT count(*) FROM tbl').fetchone()[0]))

# Read and print the values just inserted into tbl
for col in db.execute('SELECT col FROM tbl'):
    print(col)
    db.execute('INSERT INTO tbl2 VALUES (?)', col)
    db.commit()

print('count=' + str(db.execute('SELECT count(*) FROM tbl').fetchone()[0]))

输出结果为:

count=3
(0,)
(1,)
(0,)
(1,)
(2,)
count=3

通常,插入N行后,迭代器返回N + 2行,显然总是前两行重复。

1 个答案:

答案 0 :(得分:4)

你的后续评论让我感到不安(特别是因为很明显你是对的)。所以我花了一些时间研究python _sqlite.c库(https://svn.python.org/projects/python/trunk/Modules/_sqlite/)的源代码。

我认为问题是sqlite Connection对象如何处理游标。在内部,Connection对象维护游标和预准备语句的列表。嵌套的db.execute('INSERT ...')调用将重置与Connection对象关联的预准备语句列表。

解决方案是不依赖于快捷方式execute()方法的自动游标管理,并明确保存对正在运行的Cursor的引用。 Cursors维护自己准备好的语句列表,这些列表与Connection个对象分开。

您可以在db.execute()调用上显式创建游标或调用fetchall()。后来的例子:

import sqlite3 as sq

db = sq.connect(':memory:')

db.execute('CREATE TABLE tbl (col INTEGER)')
db.execute('CREATE TABLE tbl2 (col INTEGER)')
db.executemany('INSERT INTO tbl (col) VALUES (?)', [(0,), (1,), (2,)])
db.commit()

print('count=' + str(db.execute('SELECT count(*) FROM tbl').fetchone()[0]))

# Read and print the values just inserted into tbl
for col in db.execute('SELECT col FROM tbl').fetchall():
    print(col)
    db.execute('INSERT INTO tbl2 VALUES (?)', col)
    db.commit()

print('count=' + str(db.execute('SELECT count(*) FROM tbl').fetchone()[0]))

输出符合预期:

count=3
(0,)
(1,)
(2,)
count=3

如果fetchall()方法内存不允许,那么您可能需要依赖于两个数据库连接(https://www.sqlite.org/isolation.html)之间的隔离。例如:

db1 = sq.connect('temp.db')

db1.execute('CREATE TABLE tbl (col INTEGER)')
db1.execute('CREATE TABLE tbl2 (col INTEGER)')
db1.executemany('INSERT INTO tbl (col) VALUES (?)', [(0,), (1,), (2,)])
db1.commit()

print('count=' + str(db1.execute('SELECT count(*) FROM tbl').fetchone()[0]))

db2 = sq.connect('temp.db')

# Read and print the values just inserted into tbl
for col in db1.execute('SELECT col FROM tbl').fetchall():
    print(col)
    db2.execute('INSERT INTO tbl2 VALUES (?)', col)
    db2.commit()

print('count=' + str(db1.execute('SELECT count(*) FROM tbl').fetchone()[0]))