Python sqlite3 및 동시성


87

"스레딩"모듈을 사용하는 Python 프로그램이 있습니다. 매초마다 내 프로그램은 웹에서 일부 데이터를 가져 와서이 데이터를 내 하드 드라이브에 저장하는 새 스레드를 시작합니다. 이 결과를 저장하기 위해 sqlite3를 사용하고 싶지만 작동 할 수 없습니다. 문제는 다음 줄에 관한 것 같습니다.

conn = sqlite3.connect("mydatabase.db")
  • 이 코드 줄을 각 스레드에 넣으면 데이터베이스 파일이 잠겨 있음을 알려주는 OperationalError가 발생합니다. 이것은 다른 스레드가 sqlite3 연결을 통해 mydatabase.db를 열고 잠겨 있음을 의미합니다.
  • 이 코드 줄을 주 프로그램에 넣고 연결 개체 (conn)를 각 스레드에 전달하면 스레드에서 생성 된 SQLite 개체는 동일한 스레드에서만 사용할 수 있다는 ProgrammingError가 발생합니다.

이전에는 모든 결과를 CSV 파일에 저장했지만 이러한 파일 잠금 문제가 없었습니다. 바라건대 이것은 sqlite로 가능할 것입니다. 어떤 아이디어?


5
최신 버전의 Python에는이 문제를 해결해야하는 최신 버전의 sqlite3가 포함되어 있습니다.
Ryan Fugger

@RyanFugger 이것을 지원하는 가장 초기 버전이 무엇인지 알고 있습니까? 2.7을 사용하고 있습니다
notbad.jpeg 2013-06-25

@RyanFugger AFAIK에는 수정 된 최신 버전의 SQLite3가 포함 된 사전 빌드 된 버전이 없습니다. 하지만 직접 만들 수 있습니다.
shezi

답변:


44

소비자-생산자 패턴을 사용할 수 있습니다. 예를 들어 스레드간에 공유되는 대기열을 만들 수 있습니다. 웹에서 데이터를 가져 오는 첫 번째 스레드는이 데이터를 공유 큐에 넣습니다. 데이터베이스 연결을 소유 한 다른 스레드는 큐에서 데이터를 빼고 데이터베이스로 전달합니다.


8
FWIW : 이후 버전의 sqlite에서는 스레드 (커서 제외)를 통해 연결과 개체를 공유 할 수 있다고 주장하지만 실제로는 그렇지 않습니다.
Richard Levasseur

다음 은 위에서 언급 한 Evgeny Lazin의 예입니다.
dugres

4
공유 대기열 뒤에 데이터베이스를 숨기는 것은 일반적으로 SQL과 SQLite가 이미 내장 된 잠금 메커니즘을 가지고 있기 때문에이 질문에 대한 정말 나쁜 해결책입니다.이 메커니즘은 사용자가 직접 임시로 구축 할 수있는 것보다 훨씬 더 정교 할 것입니다.
shezi

1
질문을 읽어야합니다. 그 순간에는 잠금 장치가 내장되어 있지 않았습니다. 많은 최신 임베디드 데이터베이스에는 성능상의 이유로이 메커니즘이 없습니다 (예 : LevelDB).
Evgeny Lazin 2013

180

대중적인 믿음과는 달리, 최신 버전의 sqlite3 다중 스레드에서의 액세스를 지원합니다.

선택적 키워드 인수를 통해 활성화 할 수 있습니다 check_same_thread.

sqlite.connect(":memory:", check_same_thread=False)

4
예측할 수없는 예외가 발생했으며 Python도이 옵션 (Windows 32의 Python 2.7)과 충돌합니다.
reclosedev

4
문서 에 따르면 다중 스레드 모드에서는 단일 데이터베이스 연결을 다중 스레드에서 사용할 수 없습니다. 직렬화 된 모드도 있습니다
Casebash 2013 년


1
@FrEaKmAn, 죄송합니다. 오래 전 일 이었지만 : memory : database도 아닙니다. 그 후 여러 스레드에서 sqlite 연결을 공유하지 않았습니다.
reclosedev 2014 년

2
@FrEaKmAn, 다중 스레드 액세스에서 파이썬 프로세스 코어 덤프와 함께 이것을 만났습니다. 동작을 예측할 수 없었으며 예외가 기록되지 않았습니다. 내가 올바르게 기억한다면 이것은 읽기와 쓰기 모두에 해당됩니다. 이것은 지금까지 실제로 파이썬이 충돌하는 것을 본 것입니다 : D. 나는 threadsafe 모드에서 컴파일 된 sqlite로 이것을 시도하지 않았지만, 당시 나는 시스템의 기본 sqlite를 재 컴파일 할 자유가 없었다. 나는 Eric이 제안한 것과 비슷한 일을했고 쓰레드 호환성을 비활성화했습니다
verboze

17

다음은 mail.python.org.pipermail.1239789

에서 발견되었습니다. 해결책을 찾았습니다. 파이썬 문서에이 옵션에 대해 한 마디도없는 이유를 모르겠습니다. 따라서 연결 함수에 새 키워드 인수를 추가해야하며 다른 스레드에서 커서를 만들 수 있습니다. 따라서 다음을 사용하십시오.

sqlite.connect(":memory:", check_same_thread = False)

나를 위해 완벽하게 작동합니다. 물론 지금부터 db에 대한 안전한 멀티 스레딩 액세스를 처리해야합니다. 어쨌든 도움을 주신 모든 분들께 감사드립니다.


(GIL을 사용하면 실제로 db에 대한 진정한 다중 스레드 액세스 방법이 거의 없습니다.)
Erik Aronesty

경고 : 파이썬 문서가 한 에 대해 할 말이 check_same_thread옵션 : ". 작업을 작성 동일한 연결을 피하기 데이터 손상에 사용자가 serialize해야하는지와 다중 스레드를 사용하는 경우" 예, 코드가 주어진 시간에 하나의 스레드 만 데이터베이스에 쓸 수 있도록 보장하는 한 여러 스레드에서 SQLite를 사용할 있습니다. 그렇지 않으면 데이터베이스가 손상 될 수 있습니다.
Ajedi32

14

다중 처리로 전환하십시오 . 훨씬 더 좋고 확장 성이 뛰어나며 여러 CPU를 사용하여 여러 코어를 사용할 수 있으며 인터페이스는 파이썬 스레딩 모듈을 사용하는 것과 동일합니다.

또는 Ali가 제안했듯이 SQLAlchemy의 스레드 풀링 메커니즘을 사용하십시오 . 자동으로 모든 것을 처리하고 몇 가지 추가 기능을 제공합니다.

  1. SQLAlchemy는 SQLite, Postgres, MySQL, Oracle, MS-SQL, Firebird, MaxDB, MS Access, Sybase 및 Informix 용 방언을 포함합니다. IBM은 DB2 드라이버도 출시했습니다. 따라서 SQLite에서 벗어나기로 결정한 경우 애플리케이션을 다시 작성할 필요가 없습니다.
  2. SQLAlchemy의 ORM (Object Relational Mapper)의 중심 부분 인 작업 단위 시스템은 보류중인 생성 / 삽입 / 업데이트 / 삭제 작업을 대기열로 구성하고 모든 작업을 한 번에 플러시합니다. 이를 수행하기 위해 큐에있는 모든 수정 된 항목의 토폴로지 "종속성 정렬"을 수행하여 외래 키 제약 조건을 준수하고 중복 문을 그룹화하여 때때로 추가로 일괄 처리 할 수 ​​있습니다. 이는 최대의 효율성과 트랜잭션 안전성을 제공하고 교착 상태의 가능성을 최소화합니다.

11

이를 위해 스레드를 전혀 사용해서는 안됩니다. 이것은 꼬인 사람 에게는 사소한 작업이며 어쨌든 훨씬 더 나아갈 것입니다.

하나의 스레드 만 사용하고 요청이 완료되면 쓰기를 수행하는 이벤트를 트리거합니다.

twisted는 예약, 콜백 등을 처리합니다. 전체 결과를 문자열로 전달하거나 스트림 프로세서를 통해 실행할 수 있습니다 ( 결과가 아직 다운로드되는 동안 호출자에게 이벤트를 발생 시키는 Twitter APIfriendfeed API 가 있습니다).

데이터로 수행하는 작업에 따라 전체 결과를 sqlite에 완료 한 후 덤프하거나, 쿠킹하고 덤프하거나, 읽는 동안 쿠킹하고 마지막에 덤프 할 수 있습니다.

github에서 원하는 것과 비슷한 작업을 수행하는 매우 간단한 응용 프로그램이 있습니다. 나는 그것을 pfetch (병렬 가져 오기) 라고 부른다 . 일정에 따라 다양한 페이지를 가져오고 결과를 파일로 스트리밍하며 각 페이지가 성공적으로 완료되면 선택적으로 스크립트를 실행합니다. 또한 조건부 GET과 같은 멋진 작업을 수행하지만 여전히 수행중인 작업에 대한 좋은 기반이 될 수 있습니다.


7

또는 저처럼 게으르다면 SQLAlchemy 를 사용할 수 있습니다 . 스레드 로컬 및 일부 연결 풀링을 사용하여 스레딩을 처리하고 구성 방법도 가능 합니다 .

추가 보너스로, 동시 애플리케이션에 Sqlite를 사용하는 것이 재앙이 될 것이라는 사실을 깨닫거나 결정할 때 MySQL, Postgres 또는 다른 것을 사용하기 위해 코드를 변경할 필요가 없습니다. 그냥 전환 할 수 있습니다.


1
공식 웹 사이트 어디에도 파이썬 버전을 지정하지 않는 이유는 무엇입니까?
표시 이름

3

당신은 사용할 필요가 session.close()이후 모든 트랜잭션 이 오류가 발생할 다중 스레드에서 같은 커서를 사용하지 않는 동일한 스레드에서 같은 커서를 사용하기 위해 데이터베이스에.



0

나는 Evgeny의 대답을 좋아합니다. 대기열은 일반적으로 스레드 간 통신을 구현하는 가장 좋은 방법입니다. 완전성을 위해 다음과 같은 몇 가지 다른 옵션이 있습니다.

  • 생성 된 스레드가 사용을 완료하면 DB 연결을 닫습니다. 이렇게하면 문제가 해결 OperationalError되지만 이와 같은 연결 열기 및 닫기는 성능 오버 헤드로 인해 일반적으로 아니요입니다.
  • 자식 스레드를 사용하지 마십시오. 초당 1 회 작업이 상당히 가벼우면 가져 오기 및 저장을하지 않고 적절한 순간까지 잠을 잘 수 있습니다. 가져 오기 및 저장 작업이 1 초를 초과 할 수 있고 다중 스레드 접근 방식을 사용하는 다중 리소스의 이점을 잃을 수 있으므로 이는 바람직하지 않습니다.

0

프로그램의 동시성을 디자인해야합니다. SQLite에는 명확한 제한이 있으며이를 준수해야합니다. FAQ (다음 질문도 참조)를 참조하십시오 .


0

Scrapy 는 내 질문에 대한 잠재적 인 대답 인 것 같습니다. 홈 페이지는 내 정확한 작업을 설명합니다. (아직 코드가 얼마나 안정적인지 잘 모르겠습니다.)


0

데이터 지속성을 위해 y_serial Python 모듈을 살펴 보겠습니다. http://yserial.sourceforge.net

단일 SQLite 데이터베이스를 둘러싼 교착 상태 문제를 처리합니다. 동시성에 대한 요구가 무거워지면 많은 데이터베이스의 Farm 클래스를 쉽게 설정하여 확률 적 시간 동안 부하를 분산시킬 수 있습니다.

이것이 여러분의 프로젝트에 도움이되기를 바랍니다. 10 분 안에 구현할 수있을만큼 간단해야합니다.


0

위의 답변에서 벤치 마크를 찾을 수 없어서 모든 것을 벤치마킹하기위한 테스트를 작성했습니다.

나는 3 가지 접근법을 시도했다

  1. SQLite 데이터베이스에서 순차적으로 읽고 쓰기
  2. ThreadPoolExecutor를 사용하여 읽기 / 쓰기
  3. ProcessPoolExecutor를 사용하여 읽기 / 쓰기

벤치 마크의 결과 및 요약은 다음과 같습니다.

  1. 순차 읽기 / 순차 쓰기가 가장 좋습니다.
  2. 병렬로 처리해야하는 경우 ProcessPoolExecutor를 사용하여 병렬로 읽습니다.
  3. ThreadPoolExecutor를 사용하거나 ProcessPoolExecutor를 사용하여 쓰기를 수행하지 마십시오. 데이터베이스 잠금 오류가 발생하고 청크 삽입을 다시 시도해야합니다.

내 SO 답변 HERE 에서 벤치 마크에 대한 코드와 완전한 솔루션을 찾을 수 있습니다 .


-1

잠긴 데이터베이스에서 오류가 발생하는 가장 가능성있는 이유는 다음을 실행해야하기 때문입니다.

conn.commit()

데이터베이스 작업을 마친 후. 그렇지 않으면 데이터베이스가 쓰기 잠금 상태가되어 그대로 유지됩니다. 쓰기를 기다리는 다른 스레드는 시간이 지나면 시간 초과됩니다 (기본값은 5 초로 설정 됨, 자세한 내용은 http://docs.python.org/2/library/sqlite3.html#sqlite3.connect 참조 ). .

올바른 동시 삽입의 예는 다음과 같습니다.

import threading, sqlite3
class InsertionThread(threading.Thread):

    def __init__(self, number):
        super(InsertionThread, self).__init__()
        self.number = number

    def run(self):
        conn = sqlite3.connect('yourdb.db', timeout=5)
        conn.execute('CREATE TABLE IF NOT EXISTS threadcount (threadnum, count);')
        conn.commit()

        for i in range(1000):
            conn.execute("INSERT INTO threadcount VALUES (?, ?);", (self.number, i))
            conn.commit()

# create as many of these as you wish
# but be careful to set the timeout value appropriately: thread switching in
# python takes some time
for i in range(2):
    t = InsertionThread(i)
    t.start()

SQLite를 좋아하거나 SQLite 데이터베이스와 함께 작동하는 다른 도구가 있거나 CSV 파일을 SQLite db 파일로 대체하거나 플랫폼 간 IPC와 같은 드문 작업을 수행해야하는 경우 SQLite는 훌륭한 도구이며 목적에 매우 적합합니다. 옳지 않다고 느끼더라도 다른 솔루션을 사용하도록 압력을 가하지 마십시오!

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.