Как совместно использовать одно соединение SQLite в многопоточном приложении Python
Я пытаюсь написать многопоточное приложение Python, в котором одно соединение SQlite совместно используется между потоками. Я не могу заставить это работать. Реальное приложение является веб-сервером cherrypy, но следующий простой код демонстрирует мою проблему.
какие изменения или изменения мне нужно сделать, чтобы успешно запустить пример кода ниже?
когда я запускаю эту программу с THREAD_COUNT, установленным в 1, он отлично работает, и моя база данных обновляется, как я ожидаю (что is, буква "X"добавляется к текстовому значению в столбце SectorGroup).
когда я запускаю его с THREAD_COUNT, установленным на что-либо выше 1, все потоки, кроме 1, заканчиваются преждевременно с исключениями, связанными с SQLite. Различные потоки бросают разные исключения (без различимого шаблона), включая:
OperationalError: cannot start a transaction within a transaction
(возникает на UPDATE
заявление)
OperationalError: cannot commit - no transaction is active
(возникает на .commit () call)
InterfaceError: Error binding parameter 0 - probably unsupported type.
(возникает на UPDATE
и SELECT
заявления)
IndexError: tuple index out of range
(это меня совершенно озадачило, это происходит по заявлению group = rows[0][0] or ''
, но только при запуске нескольких потоков)
вот код:
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()
3 ответов
небезопасно делиться соединением между потоками; по крайней мере, вам нужно использовать блокировку для сериализации доступа. Читайте такжеhttp://docs.python.org/2/library/sqlite3.html#multithreading поскольку более старые версии SQLite имеют больше проблем.
на check_same_thread
опция появляется намеренно недокументированной в этом отношении, см. http://bugs.python.org/issue16509.
вместо этого вы можете использовать соединение для каждого потока или посмотреть SQLAlchemy для пула соединений (и очень эффективной системы постановки работы и очереди для загрузки).
я столкнулся с проблемой потоковой передачи SqLite при написании простого сервера WSGI для удовольствия и обучения. WSGI многопоточен по своей природе при работе под Apache. Кажется, для меня работает следующий код:
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 ())
пример использования:
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'])))
еще пример:
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/
N. B. код выше экспериментальный, может быть (фундаментальные) проблемы при использовании этого с (многими) параллельными запросами (например, как часть сервера WSGI). Производительность не критична для моего приложения. Самое простое, вероятно, было бы просто использовать MySql, но мне нравится немного экспериментировать, и нулевая установка SQLite понравилась мне. Если кто-то думает, что приведенный выше код имеет фундаментальные недостатки, пожалуйста, отреагируйте, так как моя цель-учиться. Если нет, я надеюсь, что это полезно для других.
Я предполагаю здесь, но похоже, что причина, по которой вы это делаете, - это проблема производительности.
потоки Python не являются значимыми для этого случая использования. Вместо этого используйте транзакции sqlite, которые очень быстры.
Если вы делаете все ваши обновления в транзакции, вы найдете порядок величины ускорения.