Python sqlite3 dan konkurensi

87

Saya memiliki program Python yang menggunakan modul "threading". Setiap detik sekali, program saya memulai utas baru yang mengambil beberapa data dari web, dan menyimpan data ini ke hard drive saya. Saya ingin menggunakan sqlite3 untuk menyimpan hasil ini, tetapi saya tidak dapat membuatnya berfungsi. Masalahnya tampaknya tentang baris berikut:

conn = sqlite3.connect("mydatabase.db")
  • Jika saya meletakkan baris kode ini di dalam setiap utas, saya mendapatkan OperationalError yang memberi tahu saya bahwa file database terkunci. Saya kira ini berarti thread lain telah membuka mydatabase.db melalui koneksi sqlite3 dan telah menguncinya.
  • Jika saya meletakkan baris kode ini di program utama dan meneruskan objek koneksi (conn) ke setiap utas, saya mendapatkan ProgrammingError, yang mengatakan bahwa objek SQLite yang dibuat di utas hanya dapat digunakan di utas yang sama.

Sebelumnya saya menyimpan semua hasil saya dalam file CSV, dan tidak mengalami masalah penguncian file ini. Mudah-mudahan ini bisa dilakukan dengan sqlite. Ada ide?

RexE
sumber
5
Saya ingin mencatat bahwa versi Python yang lebih baru menyertakan versi sqlite3 yang lebih baru yang seharusnya memperbaiki masalah ini.
Ryan Fugger
@RyanFugger, tahukah Anda versi paling awal apa yang mendukung ini? Saya menggunakan 2.7
notbad.jpeg
@RyanFugger AFAIK tidak ada versi pra-bangun yang berisi versi SQLite3 yang lebih baru yang telah diperbaiki. Anda bisa membuatnya sendiri.
shezi

Jawaban:

44

Anda bisa menggunakan pola konsumen-produsen. Misalnya Anda dapat membuat antrian yang dibagikan antar utas. Untaian pertama yang mengambil data dari web mengantrekan data ini dalam antrean bersama. Utas lain yang memiliki koneksi database menghapus data dari antrian dan meneruskannya ke database.

Evgeny Lazin
sumber
8
FWIW: Versi sqlite selanjutnya mengklaim Anda dapat berbagi koneksi dan objek di seluruh utas (kecuali kursor), tetapi saya telah menemukan sebaliknya dalam praktik aktual.
Richard Levasseur
Berikut adalah contoh dari apa yang Evgeny Lazin sebutkan di atas.
dugres
4
Menyembunyikan database Anda di balik antrian bersama adalah solusi yang sangat buruk untuk pertanyaan ini karena SQL secara umum dan SQLite secara khusus sudah memiliki mekanisme penguncian bawaan, yang mungkin jauh lebih halus daripada apa pun yang dapat Anda buat sendiri secara ad-hoc.
shezi
1
Anda perlu membaca pertanyaannya, pada saat itu tidak ada mekanisme penguncian bawaan. Banyak basis data tertanam kontemporer tidak memiliki mekanisme ini karena alasan kinerja (misalnya: LevelDB).
Evgeny Lazin
180

Berlawanan dengan kepercayaan populer, versi yang lebih baru dari sqlite3 melakukan akses dukungan dari beberapa benang.

Ini dapat diaktifkan melalui argumen kata kunci opsional check_same_thread:

sqlite.connect(":memory:", check_same_thread=False)
Jeremiah Rose
sumber
4
Saya telah menemukan pengecualian yang tidak dapat diprediksi dan bahkan Python macet dengan opsi ini (Python 2.7 pada Windows 32).
reclosedev
4
Menurut dokumen , dalam mode multi-utas, tidak ada koneksi database tunggal yang dapat digunakan di banyak utas. Ada juga mode serial
Casebash
1
Tidak apa-apa, baru saja menemukannya: http://sqlite.org/compile.html#threadsafe
Medeiros
1
@FrEaKmAn, maaf, sudah lama sekali, juga bukan: memory: database. Setelah itu saya tidak membagikan koneksi sqlite di banyak utas.
reclosedev
2
@FrEaKmAn, saya mengalami ini, dengan proses python core-dumping pada akses multi-utas. Perilakunya tidak dapat diprediksi, dan tidak ada pengecualian yang dicatat. Jika saya ingat dengan benar, ini berlaku untuk membaca dan menulis. Ini adalah satu hal yang saya lihat sebenarnya crash python sejauh ini: D. Saya belum mencoba ini dengan sqlite yang dikompilasi dalam mode threadsafe, tetapi pada saat itu, saya tidak memiliki kebebasan untuk mengkompilasi ulang sqlite default sistem. Saya akhirnya melakukan sesuatu yang mirip dengan saran Eric dan menonaktifkan kompatibilitas utas
verboze
17

Berikut ini ditemukan di mail.python.org.pipermail.1239789

Saya telah menemukan solusinya. Saya tidak tahu mengapa dokumentasi python tidak memiliki satu kata pun tentang opsi ini. Jadi kita harus menambahkan argumen kata kunci baru ke fungsi koneksi dan kita akan dapat membuat kursor darinya di utas yang berbeda. Jadi gunakan:

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

bekerja dengan sempurna untuk saya. Tentu saja mulai sekarang saya perlu menjaga akses multithreading yang aman ke db. Pokoknya terima kasih semua untuk mencoba membantu.

Robert Krolik
sumber
(Dengan GIL, sebenarnya tidak banyak akses multithread ke db yang sebenarnya saya lihat)
Erik Aronesty
PERINGATAN : The Python docs telah ini untuk mengatakan tentang check_same_threadopsi: "Ketika menggunakan beberapa thread dengan koneksi yang sama menulis operasi harus serial oleh pengguna korupsi menghindari data" Jadi ya, Anda dapat menggunakan SQLite dengan beberapa utas selama kode Anda memastikan bahwa hanya satu utas yang dapat menulis ke database pada waktu tertentu. Jika tidak, Anda mungkin merusak database Anda.
Ajedi32
14

Beralih ke multiprosesing . Jauh lebih baik, menskalakan dengan baik, dapat melampaui penggunaan beberapa inti dengan menggunakan banyak CPU, dan antarmukanya sama dengan menggunakan modul penguliran python.

Atau, seperti yang disarankan Ali, cukup gunakan mekanisme pengumpulan utas SQLAlchemy . Ini akan menangani semuanya untuk Anda secara otomatis dan memiliki banyak fitur tambahan, hanya untuk mengutip beberapa di antaranya:

  1. SQLAlchemy mencakup dialek untuk SQLite, Postgres, MySQL, Oracle, MS-SQL, Firebird, MaxDB, MS Access, Sybase dan Informix; IBM juga telah merilis driver DB2. Jadi, Anda tidak perlu menulis ulang aplikasi jika memutuskan untuk keluar dari SQLite.
  2. Sistem Unit Kerja, bagian utama dari Object Relational Mapper (ORM) SQLAlchemy, mengatur operasi buat / sisipkan / perbarui / hapus yang tertunda ke dalam antrean dan membuang semuanya dalam satu batch. Untuk mencapai ini, ia melakukan "semacam ketergantungan" topologi dari semua item yang dimodifikasi dalam antrian untuk menghormati batasan kunci asing, dan mengelompokkan pernyataan yang berlebihan bersama-sama di mana mereka kadang-kadang dapat dikumpulkan lebih jauh. Ini menghasilkan efisiensi maksimal dan keamanan transaksi, dan meminimalkan kemungkinan kebuntuan.
nosklo.dll
sumber
11

Anda tidak boleh menggunakan utas sama sekali untuk ini. Ini adalah tugas yang sepele untuk twisted dan itu kemungkinan akan membawa Anda lebih jauh secara signifikan.

Gunakan hanya satu utas, dan penyelesaian permintaan memicu peristiwa untuk melakukan penulisan.

twisted akan menangani penjadwalan, panggilan balik, dll ... untuk Anda. Ini akan memberi Anda seluruh hasil sebagai string, atau Anda dapat menjalankannya melalui stream-processor (Saya memiliki Twitter API dan friendfeed API yang keduanya menjalankan peristiwa ke pemanggil karena hasilnya masih diunduh).

Bergantung pada apa yang Anda lakukan dengan data Anda, Anda bisa membuang hasil lengkapnya ke sqlite setelah selesai, memasaknya dan membuangnya, atau memasaknya saat sedang dibaca dan membuangnya di akhir.

Saya memiliki aplikasi yang sangat sederhana yang melakukan sesuatu yang mendekati apa yang Anda inginkan di github. Saya menyebutnya pfetch (pengambilan paralel). Itu mengambil berbagai halaman pada jadwal, mengalirkan hasil ke file, dan secara opsional menjalankan skrip setelah berhasil menyelesaikan masing-masing. Itu juga melakukan beberapa hal mewah seperti GET bersyarat, tetapi masih bisa menjadi dasar yang baik untuk apa pun yang Anda lakukan.

Dustin
sumber
7

Atau jika Anda malas, seperti saya, Anda bisa menggunakan SQLAlchemy . Ini akan menangani threading untuk Anda, ( menggunakan utas lokal, dan beberapa penggabungan koneksi ) dan cara melakukannya bahkan dapat dikonfigurasi .

Untuk bonus tambahan, jika / ketika Anda menyadari / memutuskan bahwa menggunakan Sqlite untuk aplikasi bersamaan akan menjadi bencana, Anda tidak perlu mengubah kode Anda untuk menggunakan MySQL, atau Postgres, atau apa pun. Anda bisa beralih saja.

Ali Afshar
sumber
1
Mengapa itu tidak menentukan versi Python di mana pun di situs web resmi?
Nama Tampilan
3

Anda perlu menggunakan session.close()setelah setiap transaksi ke database untuk menggunakan kursor yang sama di utas yang sama, tidak menggunakan kursor yang sama di multi-utas yang menyebabkan kesalahan ini.

Hazem Khaled
sumber
1

Gunakan threading.Lock ()

Alexandr
sumber
1
Harap berikan fungsi kode berikut dan di mana seharusnya digunakan.
Ali Akhtari
0

Saya suka jawaban Evgeny - Antrian umumnya merupakan cara terbaik untuk mengimplementasikan komunikasi antar-utas. Untuk kelengkapannya, berikut beberapa opsi lainnya:

  • Tutup koneksi DB ketika utas yang muncul telah selesai menggunakannya. Ini akan memperbaiki Anda OperationalError, tetapi membuka dan menutup koneksi seperti ini umumnya No-No, karena overhead kinerja.
  • Jangan gunakan utas anak. Jika tugas sekali per detik cukup ringan, Anda dapat melakukan pengambilan dan penyimpanan, lalu tidur sampai saat yang tepat. Hal ini tidak diinginkan karena operasi pengambilan dan penyimpanan dapat memakan waktu> 1 detik, dan Anda kehilangan manfaat dari resource multipleks yang Anda miliki dengan pendekatan multi-threaded.
James Brady
sumber
0

Anda perlu merancang konkurensi untuk program Anda. SQLite memiliki batasan yang jelas dan Anda harus mematuhinya, lihat FAQ (juga pertanyaan berikut).

iny
sumber
0

Scrapy sepertinya merupakan jawaban potensial untuk pertanyaan saya. Halaman beranda menjelaskan tugas saya dengan tepat. (Meskipun saya belum yakin seberapa stabil kodenya.)

RexE
sumber
0

Saya akan melihat modul y_serial Python untuk data persistensi: http://yserial.sourceforge.net

yang menangani masalah kebuntuan di sekitar database SQLite tunggal. Jika permintaan pada konkurensi menjadi berat, seseorang dapat dengan mudah mengatur kelas Farm dari banyak database untuk menyebarkan beban selama waktu stokastik.

Semoga ini bisa membantu proyek Anda ... seharusnya cukup sederhana untuk diterapkan dalam 10 menit.

kode43
sumber
0

Saya tidak dapat menemukan tolok ukur apa pun dalam jawaban di atas, jadi saya menulis tes untuk membandingkan semuanya.

Saya mencoba 3 pendekatan

  1. Membaca dan menulis secara berurutan dari database SQLite
  2. Menggunakan ThreadPoolExecutor untuk membaca / menulis
  3. Menggunakan ProcessPoolExecutor untuk membaca / menulis

Hasil dan kesimpulan dari benchmark adalah sebagai berikut

  1. Pembacaan / penulisan sekuensial bekerja paling baik
  2. Jika Anda harus memproses secara paralel, gunakan ProcessPoolExecutor untuk membaca secara paralel
  3. Jangan melakukan penulisan apa pun baik menggunakan ThreadPoolExecutor atau menggunakan ProcessPoolExecutor karena Anda akan mengalami kesalahan penguncian database dan Anda harus mencoba memasukkan kembali potongan tersebut

Anda dapat menemukan kode dan solusi lengkap untuk tolok ukur di jawaban saya JADI DI SINI Semoga membantu!

PirateApp
sumber
-1

Alasan paling mungkin Anda mendapatkan kesalahan dengan database terkunci adalah Anda harus mengeluarkannya

conn.commit()

setelah menyelesaikan operasi database. Jika tidak, database Anda akan dikunci untuk menulis dan tetap seperti itu. Utas lain yang menunggu untuk ditulis akan habis waktunya (default disetel ke 5 detik, lihat http://docs.python.org/2/library/sqlite3.html#sqlite3.connect untuk detailnya) .

Contoh penyisipan yang benar dan bersamaan adalah ini:

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

Jika Anda menyukai SQLite, atau memiliki fitur lain yang bekerja dengan database SQLite, atau ingin mengganti file CSV dengan file db SQLite, atau harus melakukan sesuatu yang langka seperti IPC antar-platform, maka SQLite adalah fitur yang hebat dan sangat sesuai untuk tujuan tersebut. Jangan biarkan diri Anda ditekan untuk menggunakan solusi yang berbeda jika rasanya tidak tepat!

shezi
sumber