首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >如何在多线程Python应用程序中共享单个SQLite连接

如何在多线程Python应用程序中共享单个SQLite连接
EN

Stack Overflow用户
提问于 2014-03-30 10:31:58
回答 3查看 33.4K关注 0票数 13

我正在尝试编写一个多线程Python应用程序,在该应用程序中,线程之间共享单个SQlite连接。我无法让它工作。真正的应用程序是一个cherrypy web服务器,但下面的简单代码演示了我的问题。

为了成功运行下面的示例代码,我需要进行哪些更改或更改?

当我在THREAD_COUNT设置为1的情况下运行这个程序时,它工作得很好,并且我的数据库按照我的预期进行了更新(即,字母"X“被添加到SectorGroup列的文本值中)。

当我在THREAD_COUNT设置为大于1的任何值的情况下运行它时,除了1之外的所有线程都会提前终止,并出现与SQLite相关的异常。不同的线程抛出不同的异常(没有可识别的模式),包括:

代码语言:javascript
复制
OperationalError: cannot start a transaction within a transaction 

(出现在UPDATE语句中)

代码语言:javascript
复制
OperationalError: cannot commit - no transaction is active 

(在.commit()调用时发生)

代码语言:javascript
复制
InterfaceError: Error binding parameter 0 - probably unsupported type. 

(出现在UPDATESELECT语句中)

代码语言:javascript
复制
IndexError: tuple index out of range

(这个问题让我非常困惑,它出现在语句group = rows[0][0] or ''上,但只有在多个线程运行时才会出现)

代码如下:

代码语言:javascript
复制
CONNECTION = sqlite3.connect('./database/mydb', detect_types=sqlite3.PARSE_DECLTYPES, check_same_thread = False)
CONNECTION.row_factory = sqlite3.Row

def commands(start_id):

    # loop over 100 records, read the SectorGroup column, and write it back with "X" appended.
    for inv_id in range(start_id, start_id + 100):

        rows = CONNECTION.execute('SELECT SectorGroup FROM Investment WHERE InvestmentID = ?;', [inv_id]).fetchall()
        if rows:
            group = rows[0][0] or ''
            msg = '{} inv {} = {}'.format(current_thread().name, inv_id, group)
            print msg
            CONNECTION.execute('UPDATE Investment SET SectorGroup = ? WHERE InvestmentID = ?;', [group + 'X', inv_id])

        CONNECTION.commit()

if __name__ == '__main__':

    THREAD_COUNT = 10

    for i in range(THREAD_COUNT):
        t = Thread(target=commands, args=(i*100,))
        t.start()
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2014-03-30 11:29:33

在线程之间共享连接是不安全的;至少您需要使用锁来序列化访问。也要阅读http://docs.python.org/2/library/sqlite3.html#multithreading,因为旧的SQLite版本仍然有更多的问题。

在这方面,check_same_thread选项似乎故意文档不足,请参阅http://bugs.python.org/issue16509

您可以改用每个线程一个连接,或者在SQLAlchemy中寻找一个连接池(以及一个非常有效的工作说明书和队列系统来引导)。

票数 17
EN

Stack Overflow用户

发布于 2014-04-24 18:07:42

为了好玩和学习,我在编写一个简单的SqLite服务器时遇到了WSGI线程问题。在Apache下运行时,WSGI本质上是多线程的。下面的代码看起来很适合我:

代码语言:javascript
复制
import sqlite3
import threading

class LockableCursor:
    def __init__ (self, cursor):
        self.cursor = cursor
        self.lock = threading.Lock ()

    def execute (self, arg0, arg1 = None):
        self.lock.acquire ()

        try:
            self.cursor.execute (arg1 if arg1 else arg0)

            if arg1:
                if arg0 == 'all':
                    result = self.cursor.fetchall ()
                elif arg0 == 'one':
                    result = self.cursor.fetchone ()
        except Exception as exception:
            raise exception

        finally:
            self.lock.release ()
            if arg1:
                return result

def dictFactory (cursor, row):
    aDict = {}
    for iField, field in enumerate (cursor.description):
        aDict [field [0]] = row [iField]
    return aDict

class Db:
    def __init__ (self, app):
        self.app = app

    def connect (self):
        self.connection = sqlite3.connect (self.app.dbFileName, check_same_thread = False, isolation_level = None)  # Will create db if nonexistent
        self.connection.row_factory = dictFactory
        self.cs = LockableCursor (self.connection.cursor ())

使用示例:

代码语言:javascript
复制
if not ok and self.user:    # Not logged out
    # Get role data for any later use
    userIdsRoleIds = self.cs.execute ('all', 'SELECT role_id FROM users_roles WHERE user_id == {}'.format (self.user ['id']))

    for userIdRoleId in userIdsRoleIds:
        self.userRoles.append (self.cs.execute ('one', 'SELECT name FROM roles WHERE id == {}'.format (userIdRoleId ['role_id'])))

另一个例子:

代码语言:javascript
复制
self.cs.execute ('CREATE TABLE users (id INTEGER PRIMARY KEY, email_address, password, token)')         
self.cs.execute ('INSERT INTO users (email_address, password) VALUES ("{}", "{}")'.format (self.app.defaultUserEmailAddress, self.app.defaultUserPassword))

# Create roles table and insert default role
self.cs.execute ('CREATE TABLE roles (id INTEGER PRIMARY KEY, name)')
self.cs.execute ('INSERT INTO roles (name) VALUES ("{}")'.format (self.app.defaultRoleName))

# Create users_roles table and assign default role to default user
self.cs.execute ('CREATE TABLE users_roles (id INTEGER PRIMARY KEY, user_id, role_id)') 

defaultUserId = self.cs.execute ('one', 'SELECT id FROM users WHERE email_address = "{}"'.format (self.app.defaultUserEmailAddress)) ['id']         
defaultRoleId = self.cs.execute ('one', 'SELECT id FROM roles WHERE name = "{}"'.format (self.app.defaultRoleName)) ['id']

self.cs.execute ('INSERT INTO users_roles (user_id, role_id) VALUES ({}, {})'.format (defaultUserId, defaultRoleId))

使用此结构的完整程序可从以下位置下载:http://www.josmith.org/

注:上面的代码是实验性的,当使用它处理(许多)并发请求(例如,作为WSGI服务器的一部分)时,可能会有(基本)问题。性能对我的应用程序来说并不重要。最简单的事情可能是只使用MySql,但我喜欢尝试一下,关于SqLite的零安装的事情吸引了我。如果有人认为上面的代码有根本性的缺陷,请作出反应,因为我的目的是学习。如果没有,我希望这对其他人有用。

票数 6
EN

Stack Overflow用户

发布于 2018-06-02 03:48:23

我在这里猜测,但看起来您这样做的原因是性能问题。

对于这种用例,Python线程没有任何有意义的性能。取而代之的是使用sqlite事务,它非常快。

如果你在一个事务中完成所有的更新,你会发现速度提高了一个数量级。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/22739590

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档