如何在Python中使用sqlite3连接
我创建了一个叫 WebApp.test()
的东西,用来测试同时访问数据库的情况,目的是模拟一个浏览器在执行一些查询的同时,后台任务也在更新数据库。
但是,当我执行一个新的查询并且使用同一个连接时,后台任务就崩溃了。我到底哪里做错了呢?
@cherrypy.expose
def test(self, x=''):
if x == 'connect':
WebApp.con = sqlite3.connect('db', check_same_thread=False)
return 'connected'
if x == 'insert':
with WebApp.con:
cur = WebApp.con.cursor()
cur.execute('drop table if exists x')
cur.execute('create table x(x)')
for i in range(10000):
cur.execute('insert into x values (%s)' % i)
WebApp.con.commit()
return 'Inserted %s rows' % i
if x == 'query':
with WebApp.con:
cur = WebApp.con.cursor()
cur.execute('select * from x where x < 20')
data = cur.fetchall()
return 'result: %s' % data
我依次访问了以下三个页面,每个页面之间只等了几秒钟:
test?x=connect
test?x=insert
test?x=query
第一个页面是用来建立连接的。
第二个页面开始了一个非常长的循环,模拟一个将在服务器上运行几分钟的后台任务。
第三个页面则是执行一个简单的查询。
当我访问第三个页面时,查询的结果如预期返回了,但仍在运行的循环却被中断,并出现了以下错误:
WebApp.con.commit()
sqlite3.OperationalError: cannot commit - no transaction is active
2 个回答
一个问题是你在每个情况下都把连接对象当作上下文管理器来用。文档提到,连接在结束时会被提交或者回滚。你其实是明确地在提交,这样就几乎没什么好处了,因为你本来是想用上下文管理器的。
所以你的第三个查询提交了连接,这就导致了你的insert
脚本出现问题,因为连接现在已经被提交了。(...或者可能是你在10000次循环中做的那些明确提交造成的)
Sqlite还提到,你不能在多个线程之间共享连接和游标。我觉得你每次都应该使用一个新的连接。
关于connect
方法的文档中提到:"当多个连接访问数据库时,如果其中一个进程修改了数据库,SQLite数据库会被锁定,直到那个事务被提交。timeout参数指定连接应该等待多长时间,直到锁被释放,否则就会抛出异常。timeout参数的默认值是5.0(五秒)。"
经过一些研究,我得出了以下结论:
在不同的线程中,不能同时使用同一个连接。因为这样的话,这两个线程会共享同一个事务、同一个游标和其他资源。
不过,可以在不同的线程中使用同一个连接,只要不是同时使用。例如,SqlAlchemy 就使用了连接池。
对于简单的网页应用(像我这个),每次调用时创建一个连接是可以的,这样的开销非常小。