如何在Python中处理多个线程访问SQLite数据库?

3

看起来自 SQLite 3.7.13 起就支持多线程访问,而 Python 的 sqlite3 模块从 3.4 版本开始支持。为了使用它,你可以编写如下代码:

import sqlite3
dburi = "file:TESTING_MEMORY_DB?mode=memory&cache=shared"
connection = sqlite3.connect(dburi, uri=True, check_same_thread=False)
with connection:
    cursor = conneciton.cursor()
    cursor.execute("SQL")

这个方法是可行的,但你需要锁定对数据库的访问,否则另一个线程可能会破坏你的数据。实现方式如下所示:
import threading
import sqlite3
dburi = "file:TESTING_MEMORY_DB?mode=memory&cache=shared"
connection = sqlite3.connect(dburi, uri=True, check_same_thread=False)
# NOTE: You'll need to share this same lock object with every other thread using the database
lock = threading.Lock()
with lock:
    with connection:
        cursor = connection.cursor()
        cursor.execute("SQL")
        connection.commit()

现在,如果一个线程获取了锁,另一个线程就不能获取它,直到第一个线程关闭它。只要所有线程都使用相同的“lock”对象,并在使用“connection:”之前记得使用“with lock:”,您的数据就不会被损坏。
然而,现在我需要一种方法来将锁与连接一起传递。您可以使用单独的参数来实现这一点,或者也可以使用自定义类:
import threading
import sqlite3

class LockableSqliteConnection(object):
    def __init__(self, dburi):
        self.lock = threading.Lock()
        self.connection = sqlite3.connect(dburi, uri=True, check_same_thread=False)

dburi = "file:TESTING_MEMORY_DB?mode=memory&cache=shared"
lsc = LockableSqliteConnection(dburi)
with lsc.lock:
    with lsc.connection:
        cursor = lsc.connection.cursor()
        cursor.execute("SQL")
        lsc.connection.commit()

这很不错,因为类的名称让我想起了我拥有的内容,所以我不太可能忘记锁定并破坏我的数据。但是有没有办法摆脱这两个with语句呢?理想情况下,我想将它们合并成单个with,因为我不应该在没有锁定的情况下使用连接。

1个回答

5

在这篇文章的帮助下,我能够编写符合自身需求的代码:http://effbot.org/zone/python-with-statement.htm

我的代码看起来像这样:

import threading
import sqlite3

class LockableSqliteConnection(object):
    def __init__(self, dburi):
        self.lock = threading.Lock()
        self.connection = sqlite3.connect(dburi, uri=True, check_same_thread=False)
        self.cursor = None
    def __enter__(self):
        self.lock.acquire()
        self.cursor = self.connection.cursor()
        return self
    def __exit__(self, type, value, traceback):
        self.lock.release()
        self.connection.commit()
        if self.cursor is not None:
            self.cursor.close()
            self.cursor = None

这个类的对象现在可以直接与with语句一起使用:

dburi = "file:TESTING_MEMORY_DB?mode=memory&cache=shared"
lsc = LockableSqliteConnection(dburi)
with lsc:
    lsc.cursor.execute("SQL")

它还为我方便地打开了一个游标,并自动提交我对数据库所做的更改,这意味着调用我的新类的代码更短。这很好,但自动提交数据库更改实际上可能不是最好的主意... 在更复杂的情况下,您可能想要开始一个事务,然后在部分回滚它,这种功能可能会引起麻烦。然而,我的SQL需求非常简单,而且我从不回滚,所以我现在将其保留在项目中。

4
尝试过代码,好像可以工作。我没有完全测试它,但发现了一个问题。self.lock.release() 应该是 "_ exit _" 函数中的最后一行。否则,在建立新连接时,锁可能会被释放,导致新连接的游标被设置为 None。 - K Thorngren

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接