Python sqlite3 và đồng thời


87

Tôi có một chương trình Python sử dụng mô-đun "phân luồng". Cứ sau mỗi giây, chương trình của tôi bắt đầu một chuỗi mới tìm nạp một số dữ liệu từ web và lưu trữ dữ liệu này vào ổ cứng của tôi. Tôi muốn sử dụng sqlite3 để lưu trữ các kết quả này, nhưng tôi không thể làm cho nó hoạt động. Vấn đề dường như là về dòng sau:

conn = sqlite3.connect("mydatabase.db")
  • Nếu tôi đặt dòng mã này bên trong mỗi luồng, tôi nhận được Lỗi OperationalError cho tôi biết rằng tệp cơ sở dữ liệu đã bị khóa. Tôi đoán điều này có nghĩa là một luồng khác có mydatabase.db mở thông qua kết nối sqlite3 và đã khóa nó.
  • Nếu tôi đặt dòng mã này trong chương trình chính và chuyển đối tượng kết nối (conn) cho mỗi luồng, tôi nhận được Lỗi Lập trình, nói rằng các đối tượng SQLite được tạo trong một luồng chỉ có thể được sử dụng trong cùng một luồng đó.

Trước đây, tôi đã lưu trữ tất cả các kết quả của mình trong các tệp CSV và không gặp bất kỳ sự cố khóa tệp nào trong số này. Hy vọng rằng điều này sẽ khả thi với sqlite. Có ý kiến ​​gì không?


5
Tôi muốn lưu ý rằng các phiên bản Python gần đây hơn bao gồm các phiên bản mới hơn của sqlite3 sẽ khắc phục sự cố này.
Ryan Fugger

@RyanFugger bạn có biết phiên bản sớm nhất hỗ trợ điều này là gì không? Tôi đang sử dụng 2,7
notbad.jpeg

@RyanFugger AFAIK không có phiên bản tạo sẵn nào chứa phiên bản SQLite3 mới hơn đã sửa lỗi đó. Tuy nhiên, bạn có thể tự xây dựng một cái.
shezi

Câu trả lời:


44

Bạn có thể sử dụng mô hình người tiêu dùng-nhà sản xuất. Ví dụ, bạn có thể tạo hàng đợi được chia sẻ giữa các luồng. Chuỗi đầu tiên tìm nạp dữ liệu từ web xếp hàng dữ liệu này trong hàng đợi được chia sẻ. Một luồng khác sở hữu kết nối cơ sở dữ liệu sắp xếp lại dữ liệu từ hàng đợi và chuyển nó đến cơ sở dữ liệu.


8
FWIW: Các phiên bản sau của sqlite tuyên bố bạn có thể chia sẻ các kết nối và đối tượng trên các luồng (ngoại trừ con trỏ), nhưng trong thực tế thì tôi thấy khác.
Richard Levasseur 14/02/09

Đây là một ví dụ về những gì Evgeny Lazin đã đề cập ở trên.
dugres

4
Ẩn cơ sở dữ liệu của bạn đằng sau một hàng đợi được chia sẻ là một giải pháp thực sự tồi cho câu hỏi này vì SQL nói chung và SQLite nói riêng đã có các cơ chế khóa tích hợp sẵn, có thể được tinh chỉnh hơn nhiều so với bất kỳ thứ gì bạn có thể tự xây dựng.
shezi

1
Bạn cần đọc câu hỏi, tại thời điểm đó không có cơ chế khóa nào được tích hợp sẵn. Nhiều cơ sở dữ liệu nhúng hiện đại thiếu cơ chế này vì lý do hiệu suất (ví dụ: LevelDB).
Evgeny Lazin

180

Trái ngược với niềm tin phổ biến, các phiên bản mới hơn của sqlite3 làm truy cập hỗ trợ từ nhiều chủ đề.

Điều này có thể được kích hoạt thông qua đối số từ khóa tùy chọn check_same_thread:

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

4
Tôi đã gặp phải các trường hợp ngoại lệ không thể đoán trước và thậm chí Python gặp sự cố với tùy chọn này (Python 2.7 trên Windows 32).
reclosedev

4
Theo tài liệu , trong chế độ đa luồng, không có kết nối cơ sở dữ liệu đơn lẻ nào có thể được sử dụng trong nhiều luồng. Ngoài ra còn có một chế độ tuần tự
Casebash

1
Đừng bận tâm, chỉ cần tìm thấy nó: http://sqlite.org/compile.html#threadsafe
Medeiros

1
@FrEaKmAn, xin lỗi, cũng lâu rồi, cũng không có: memory: database. Sau đó, tôi đã không chia sẻ kết nối sqlite trong nhiều chủ đề.
reclosedev

2
@FrEaKmAn, tôi đã gặp phải điều này, với quá trình python đổ lõi cho quyền truy cập đa luồng. Hành vi không thể đoán trước và không có ngoại lệ nào được ghi lại. Nếu tôi nhớ không lầm, điều này đúng cho cả đọc và viết. Đây là một điều mà tôi đã thấy thực sự sụp đổ python cho đến nay: D. Tôi chưa thử điều này với sqlite được biên dịch ở chế độ threadsafe, nhưng tại thời điểm đó, tôi không có quyền biên dịch lại sqlite mặc định của hệ thống. Tôi đã kết thúc việc làm tương tự như những gì Eric đã đề xuất và vô hiệu hóa khả năng tương thích của chuỗi
tiết

17

Phần sau được tìm thấy trên mail.python.org.pipermail.1239789

Tôi đã tìm thấy giải pháp. Tôi không biết tại sao tài liệu python không có một từ nào về tùy chọn này. Vì vậy, chúng ta phải thêm một đối số từ khóa mới vào hàm kết nối và chúng ta sẽ có thể tạo các con trỏ từ nó trong một chuỗi khác. Vì vậy, hãy sử dụng:

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

hoạt động hoàn hảo cho tôi. Tất nhiên từ bây giờ tôi cần quan tâm đến việc truy cập đa luồng an toàn vào db. Dù sao thx tất cả vì đã cố gắng giúp đỡ.


(Với GIL, thực sự không có nhiều cách thức truy cập đa luồng thực sự vào db mà tôi đã thấy)
Erik Aronesty

CẢNH BÁO : Các tài liệu Python có điều này để nói về check_same_threadtùy chọn: "Khi sử dụng nhiều luồng với các hoạt động ghi kết nối giống nhau, người dùng nên tuần tự hóa để tránh hỏng dữ liệu." Vì vậy, có, bạn có thể sử dụng SQLite với nhiều luồng miễn là mã của bạn đảm bảo rằng chỉ một luồng có thể ghi vào cơ sở dữ liệu tại bất kỳ thời điểm nào. Nếu không, bạn có thể làm hỏng cơ sở dữ liệu của mình.
Ajedi32

14

Chuyển sang đa xử lý . Nó tốt hơn nhiều, mở rộng quy mô tốt, có thể vượt ra ngoài việc sử dụng nhiều lõi bằng cách sử dụng nhiều CPU và giao diện giống như sử dụng mô-đun phân luồng python.

Hoặc, như Ali đã đề xuất, chỉ cần sử dụng cơ chế gộp luồng của SQLAlchemy . Nó sẽ tự động xử lý mọi thứ cho bạn và có nhiều tính năng bổ sung, chỉ để trích dẫn một số trong số chúng:

  1. SQLAlchemy bao gồm các phương ngữ cho SQLite, Postgres, MySQL, Oracle, MS-SQL, Firebird, MaxDB, MS Access, Sybase và Informix; IBM cũng đã phát hành một trình điều khiển DB2. Vì vậy, bạn không cần phải viết lại ứng dụng của mình nếu bạn quyết định rời khỏi SQLite.
  2. Hệ thống Unit Of Work, một phần trung tâm của Trình lập bản đồ quan hệ đối tượng (ORM) của SQLAlchemy, tổ chức các hoạt động tạo / chèn / cập nhật / xóa đang chờ xử lý vào hàng đợi và chuyển tất cả chúng trong một đợt. Để thực hiện điều này, nó thực hiện một "sắp xếp phụ thuộc" cấu trúc liên kết của tất cả các mục đã sửa đổi trong hàng đợi để tôn trọng các ràng buộc khóa ngoại và nhóm các câu lệnh thừa lại với nhau, nơi chúng đôi khi có thể được bổ sung thêm. Điều này tạo ra hiệu quả tối đa và an toàn giao dịch, đồng thời giảm thiểu nguy cơ bế tắc.

12

Bạn không nên sử dụng chủ đề cho việc này. Đây là một nhiệm vụ nhỏ đối với xoắn và điều đó có thể sẽ đưa bạn đi xa hơn đáng kể.

Chỉ sử dụng một luồng và việc hoàn thành yêu cầu sẽ kích hoạt một sự kiện để thực hiện việc ghi.

twist sẽ lo việc lên lịch, gọi lại, v.v ... cho bạn. Nó sẽ cung cấp cho bạn toàn bộ kết quả dưới dạng một chuỗi hoặc bạn có thể chạy nó thông qua bộ xử lý luồng (tôi có API twitterAPI nguồn cấp dữ liệu bạn bè , cả hai đều kích hoạt các sự kiện cho người gọi khi kết quả vẫn đang được tải xuống).

Tùy thuộc vào những gì bạn đang làm với dữ liệu của mình, bạn có thể chỉ cần đổ toàn bộ kết quả vào sqlite khi nó hoàn thành, nấu nó và đổ nó hoặc nấu nó trong khi nó đang được đọc và kết xuất nó ở cuối.

Tôi có một ứng dụng rất đơn giản thực hiện một số thứ gần giống với những gì bạn muốn trên github. Tôi gọi nó là pfetch (tìm nạp song song). Nó lấy các trang khác nhau theo lịch trình, truyền kết quả vào một tệp và tùy chọn chạy một tập lệnh sau khi hoàn thành thành công từng trang. Nó cũng thực hiện một số công cụ ưa thích như GET có điều kiện, nhưng vẫn có thể là một cơ sở tốt cho bất cứ điều gì bạn đang làm.


7

Hoặc nếu bạn lười như tôi, bạn có thể sử dụng SQLAlchemy . Nó sẽ xử lý luồng cho bạn, ( sử dụng cục bộ luồng và một số kết nối tổng hợp ) và cách nó thực hiện thậm chí có thể định cấu hình .

Để có thêm phần thưởng, nếu / khi bạn nhận ra / quyết định rằng việc sử dụng Sqlite cho bất kỳ ứng dụng đồng thời nào sẽ là một thảm họa, bạn sẽ không phải thay đổi mã của mình để sử dụng MySQL hoặc Postgres hoặc bất kỳ thứ gì khác. Bạn chỉ có thể chuyển qua.


1
Tại sao nó không chỉ định phiên bản Python ở bất kỳ đâu trên trang web chính thức?
Tên hiển thị

3

Bạn cần phải sử dụng session.close()sau mỗi giao dịch với cơ sở dữ liệu để sử dụng cùng một con trỏ trong cùng một luồng, không sử dụng cùng một con trỏ trong nhiều luồng gây ra lỗi này.



0

Tôi thích câu trả lời của Evgeny - Hàng đợi nói chung là cách tốt nhất để triển khai giao tiếp giữa các luồng. Để hoàn thiện, đây là một số tùy chọn khác:

  • Đóng kết nối DB khi các luồng sinh sản đã hoàn tất việc sử dụng nó. Điều này sẽ khắc phục sự cố của bạn OperationalError, nhưng việc mở và đóng các kết nối như thế này thường là Không, do chi phí hoạt động.
  • Không sử dụng chủ đề con. Nếu tác vụ một lần mỗi giây nhẹ hợp lý, bạn có thể thực hiện việc tìm nạp và lưu trữ, sau đó ngủ cho đến đúng thời điểm. Điều này là không mong muốn vì các hoạt động tìm nạp và lưu trữ có thể mất> 1 giây và bạn mất lợi ích của các tài nguyên được ghép kênh mà bạn có với cách tiếp cận đa luồng.

0

Bạn cần thiết kế đồng thời cho chương trình của mình. SQLite có những hạn chế rõ ràng và bạn cần tuân theo chúng, hãy xem Câu hỏi thường gặp (cũng là câu hỏi sau).


0

Liệu pháp có vẻ như là một câu trả lời tiềm năng cho câu hỏi của tôi. Trang chủ của nó mô tả nhiệm vụ chính xác của tôi. (Mặc dù tôi không chắc mã ổn định như thế nào.)


0

Tôi sẽ xem xét mô-đun Python y_serial về tính ổn định của dữ liệu: http://yserial.sourceforge.net

xử lý các vấn đề bế tắc xung quanh một cơ sở dữ liệu SQLite duy nhất. Nếu nhu cầu về đồng thời tăng cao, người ta có thể dễ dàng thiết lập Trang trại lớp của nhiều cơ sở dữ liệu để phân tán tải theo thời gian ngẫu nhiên.

Hy vọng điều này sẽ giúp dự án của bạn ... nó phải đủ đơn giản để thực hiện trong 10 phút.


0

Tôi không thể tìm thấy bất kỳ điểm chuẩn nào trong bất kỳ câu trả lời nào ở trên vì vậy tôi đã viết một bài kiểm tra để đánh giá mọi thứ.

Tôi đã thử 3 cách tiếp cận

  1. Đọc và ghi tuần tự từ cơ sở dữ liệu SQLite
  2. Sử dụng ThreadPoolExecutor để đọc / ghi
  3. Sử dụng ProcessPoolExecutor để đọc / ghi

Các kết quả và rút ra từ điểm chuẩn như sau

  1. Đọc tuần tự / ghi tuần tự hoạt động tốt nhất
  2. Nếu bạn phải xử lý song song, hãy sử dụng ProcessPoolExecutor để đọc song song
  3. Không thực hiện bất kỳ ghi nào bằng ThreadPoolExecutor hoặc sử dụng ProcessPoolExecutor vì bạn sẽ gặp phải lỗi bị khóa cơ sở dữ liệu và bạn sẽ phải thử chèn lại đoạn mã này một lần nữa

Bạn có thể tìm thấy mã và giải pháp hoàn chỉnh cho các điểm chuẩn trong câu trả lời SO của tôi TẠI ĐÂY Hy vọng rằng sẽ hữu ích!


-1

Lý do có thể nhất khiến bạn gặp lỗi với cơ sở dữ liệu bị khóa là bạn phải phát hành

conn.commit()

sau khi kết thúc hoạt động cơ sở dữ liệu. Nếu bạn không làm như vậy, cơ sở dữ liệu của bạn sẽ bị khóa ghi và giữ nguyên như vậy. Các chuỗi khác đang chờ viết sẽ hết thời gian chờ sau một khoảng thời gian (mặc định được đặt thành 5 giây, hãy xem http://docs.python.org/2/library/sqlite3.html#sqlite3.connect để biết chi tiết về điều đó) .

Ví dụ về cách chèn đúng và đồng thời sẽ là:

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()

Nếu bạn thích SQLite, hoặc có các công cụ khác hoạt động với cơ sở dữ liệu SQLite, hoặc muốn thay thế tệp CSV bằng tệp db SQLite, hoặc phải làm điều gì đó hiếm gặp như IPC liên nền tảng, thì SQLite là một công cụ tuyệt vời và rất phù hợp cho mục đích này. Đừng để bản thân bị áp lực phải sử dụng một giải pháp khác nếu cảm thấy không phù hợp!

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.