Python sqlite3 ve eşzamanlılık


87

"Threading" modülünü kullanan bir Python programım var. Her saniyede bir, programım web'den bazı verileri alan ve bu verileri sabit diskime depolayan yeni bir iş parçacığı başlatıyor. Bu sonuçları depolamak için sqlite3 kullanmak istiyorum, ancak çalışmasını sağlayamıyorum. Sorun şu satırla ilgili gibi görünüyor:

conn = sqlite3.connect("mydatabase.db")
  • Bu kod satırını her iş parçacığının içine koyarsam, veritabanı dosyasının kilitlendiğini söyleyen bir OperationalError alıyorum. Sanırım bu, başka bir iş parçacığının bir sqlite3 bağlantısı üzerinden mydatabase.db'ye sahip olduğu ve onu kilitlediği anlamına geliyor.
  • Bu kod satırını ana programa koyarsam ve bağlantı nesnesini (conn) her bir iş parçacığına iletirsem, bir iş parçacığında oluşturulan SQLite nesnelerinin yalnızca aynı iş parçacığında kullanılabileceğini söyleyen bir ProgrammingError hatası alırım.

Önceden tüm sonuçlarımı CSV dosyalarında saklıyordum ve bu dosya kilitleme sorunlarından hiçbiri yoktu. Umarım bu sqlite ile mümkün olur. Herhangi bir fikir?


5
Python'un daha yeni sürümlerinin, bu sorunu çözmesi gereken daha yeni sqlite3 sürümlerini içerdiğini belirtmek isterim.
Ryan Fugger

@RyanFugger, bunu destekleyen en eski sürümün ne olduğunu biliyor musunuz? 2.7
notbad.jpeg

@RyanFugger AFAIK, SQLite3'ün düzeltilmiş daha yeni bir sürümünü içeren önceden oluşturulmuş bir sürüm yok. Yine de kendiniz bir tane inşa edebilirsiniz.
shezi

Yanıtlar:


44

Tüketici-üretici modelini kullanabilirsiniz. Örneğin, iş parçacıkları arasında paylaşılan kuyruk oluşturabilirsiniz. Web'den veri alan ilk iş parçacığı, bu verileri paylaşılan kuyrukta sıraya koyar. Veritabanı bağlantısına sahip olan başka bir iş parçacığı, verileri kuyruktan çıkarır ve veritabanına iletir.


8
FWIW: sqlite'ın sonraki sürümleri, bağlantıları ve nesneleri iş parçacıkları arasında paylaşabileceğinizi iddia ediyor (imleçler hariç), ancak gerçek uygulamada bunun aksini buldum.
Richard Levasseur

İşte Evgeny Lazin'in yukarıda bahsettiklerine bir örnek.
dugres

4
Veritabanınızı paylaşılan bir kuyruğun arkasına gizlemek bu soru için gerçekten kötü bir çözümdür çünkü genel olarak SQL ve özellikle SQLite zaten yerleşik kilitleme mekanizmalarına sahiptir ve bunlar muhtemelen kendi başınıza oluşturabileceğiniz her şeyden çok daha rafine edilmiştir.
shezi

1
Soruyu okumanız gerekiyor, o anda yerleşik kilitleme mekanizmaları yoktu. Birçok çağdaş gömülü veritabanı, performans nedenlerinden dolayı bu mekanizmadan yoksundur (örneğin: LevelDB).
Evgeny Lazin

180

Genel kanının aksine, sqlite3 yeni sürümleri yapmak birden çok iş parçacığı destek erişimini.

Bu, isteğe bağlı anahtar kelime bağımsız değişkeni aracılığıyla etkinleştirilebilir check_same_thread:

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

4
Bu seçenekle öngörülemeyen istisnalarla karşılaştım ve hatta Python çöküyor (Windows 32'de Python 2.7).
kapatıldı

4
Dokümanlara göre , çok iş parçacıklı modda tek bir veritabanı bağlantısı birden çok iş parçacığında kullanılamaz. Ayrıca serileştirilmiş bir mod da var
Casebash


1
@FrEaKmAn, üzgünüm, uzun zaman önceydi, ayrıca hafıza: veritabanı değil. Bundan sonra birden fazla iş parçacığında sqlite bağlantısını paylaşmadım.
reclosedev

2
@FrEaKmAn, çoklu iş parçacığı erişiminde python işlemi çekirdek dökümüyle karşılaştım. Davranış tahmin edilemezdi ve herhangi bir istisna kaydedilmedi. Doğru hatırlıyorsam, bu hem okuma hem de yazma için geçerliydi. Şu ana kadar python çarparken gördüğüm tek şey bu: D. Bunu evre güvenli kipte derlenmiş sqlite ile denemedim, ancak o zamanlar sistemin varsayılan sqlite'ını yeniden derleme özgürlüğüm yoktu. Eric'in önerdiğine benzer bir şey yaptım ve iş parçacığı uyumluluğunu devre dışı
bıraktım

17

Aşağıdaki üzerinde bulunan mail.python.org.pipermail.1239789

Ben çözüm bulduk. Python belgelerinin neden bu seçenekle ilgili tek bir kelime olmadığını bilmiyorum. Bu yüzden bağlantı işlevine yeni bir anahtar kelime argümanı eklemeliyiz ve bundan farklı iş parçacığı içinde imleçler oluşturabileceğiz. Öyleyse kullan:

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

benim için mükemmel çalışıyor. Tabii ki bundan sonra db'ye güvenli çoklu okuma erişimi ile ilgilenmem gerekiyor. Her neyse, yardım etmeye çalıştığın için teşekkürler.


(GIL ile, zaten gördüğüm
kadarıyla db'ye

UYARI : Python dokümanlar var bu konuda söyleyecek check_same_threadseçeneği: "operasyonları yazma aynı bağlantı önlemek veri bozulmasına kullanıcı tarafından dizgeleştirilecek ile birden konuları kullanırken" Yani evet, olabilir uzun tek iplik herhangi bir zamanda veritabanına yazabilir kod aktarımı sağlar gibi birden fazla konu ile SQLite'ı kullanmak. Aksi takdirde, veritabanınızı bozabilirsiniz.
Ajedi32

14

Çoklu işlemeye geçin . Çok daha iyidir, iyi ölçeklenir, birden çok CPU kullanarak birden çok çekirdek kullanımının ötesine geçebilir ve arayüz python threading modülü kullanmakla aynıdır.

Ya da Ali'nin önerdiği gibi, sadece SQLAlchemy'nin iş parçacığı havuzlama mekanizmasını kullanın . Her şeyi sizin için otomatik olarak halleder ve sadece bazılarından alıntı yapmak için birçok ekstra özelliğe sahiptir:

  1. SQLAlchemy, SQLite, Postgres, MySQL, Oracle, MS-SQL, Firebird, MaxDB, MS Access, Sybase ve Informix için lehçeler içerir; IBM ayrıca bir DB2 sürücüsü yayınladı. Dolayısıyla, SQLite'den uzaklaşmaya karar verirseniz uygulamanızı yeniden yazmak zorunda kalmazsınız.
  2. SQLAlchemy'nin Nesne İlişkisel Eşleştiricisinin (ORM) merkezi bir parçası olan İş Birimi sistemi, bekleyen oluşturma / ekleme / güncelleme / silme işlemlerini sıralar halinde düzenler ve hepsini tek bir toplu işte temizler. Bunu başarmak için, yabancı anahtar kısıtlamalarına uymak için kuyruktaki tüm değiştirilmiş öğelerin topolojik bir "bağımlılık sıralaması" gerçekleştirir ve bazen daha da fazla toplu işlenebilecekleri gereksiz ifadeleri bir arada gruplandırır. Bu, maksimum verimlilik ve işlem güvenliği sağlar ve kilitlenme olasılığını en aza indirir.

11

Bunun için hiç iş parçacığı kullanmamalısın. Bu, bükülmüş için önemsiz bir görevdir ve muhtemelen sizi daha da ileriye götürür.

Yalnızca bir iş parçacığı kullanın ve isteğin tamamlanmasının, yazma yapmak için bir olayı tetiklemesini sağlayın.

twisted sizin için planlamayı, geri aramaları vb. halleder. Size tüm sonucu bir dizge olarak verir veya bunu bir akış işlemcisi aracılığıyla çalıştırabilirsiniz (Bir twitter API'm ve sonuçlar hala indirilirken arayanlara olayları başlatan bir twitter API'im ve bir friendfeed API'm var ).

Verilerinizle ne yaptığınıza bağlı olarak, tüm sonucu tamamlandığında sqlite'a atabilir, pişirebilir ve atabilir veya okunurken pişirebilir ve sonunda dökebilirsiniz.

Github'da istediğinize yakın bir şey yapan çok basit bir uygulamam var. Ben buna pfetch (paralel getirme) diyorum . Bir programdaki çeşitli sayfaları yakalar, sonuçları bir dosyaya aktarır ve isteğe bağlı olarak her birinin başarıyla tamamlanmasının ardından bir komut dosyası çalıştırır. Aynı zamanda koşullu GET'ler gibi bazı süslü şeyler de yapar, ancak yine de yaptığınız her şey için iyi bir temel olabilir.


7

Ya da benim gibi tembelseniz , SQLAlchemy'yi kullanabilirsiniz . Sizin için iş parçacığı oluşturacak ( yerel iş parçacığı ve bazı bağlantı havuzu kullanarak ) ve bunu yapma şekli yapılandırılabilir bile .

Ek bonus için, herhangi bir eşzamanlı uygulama için Sqlite kullanmanın bir felaket olacağını fark ettiğinizde / karar verdiğinizde, kodunuzu MySQL, Postgres veya başka bir şey kullanmak için değiştirmeniz gerekmeyecek. Sadece geçiş yapabilirsiniz.


1
Neden resmi web sitesinin hiçbir yerinde Python sürümünü belirtmiyor?
Görünen Ad

3

Bu hataya neden olan çoklu evrelerde aynı imleci kullanmamak için aynı imleci aynı evrede kullanmak için veritabanına yapılan her işlemdensession.close() sonra kullanmanız gerekir .



0

Evgeny'nin cevabını beğendim - Sıralar genellikle iş parçacıkları arası iletişimi gerçekleştirmenin en iyi yoludur. Tamlık için, işte diğer bazı seçenekler:

  • Ortaya çıkan evreler onu kullanmayı bitirdiğinde DB bağlantısını kapatın. Bu sizin OperationalErrordurumunuzu düzeltir , ancak bunun gibi bağlantıları açıp kapatmak performans ek yükü nedeniyle genellikle Hayır-Hayır olur.
  • Alt dizileri kullanmayın. Saniyede bir görev oldukça hafifse, getirip saklamaktan kurtulabilir ve ardından doğru ana kadar uyuyabilirsiniz. Getirme ve depolama işlemleri> 1 saniye sürebileceğinden bu istenmeyen bir durumdur ve çok iş parçacıklı bir yaklaşımla sahip olduğunuz çoğullamalı kaynakların avantajını kaybedersiniz.

0

Programınız için eşzamanlılığı tasarlamanız gerekir. SQLite'ın açık sınırlamaları vardır ve bunlara uymanız gerekir, SSS'ye bakın (ayrıca aşağıdaki soru).


0

Scrapy soruma potansiyel bir cevap gibi görünüyor. Ana sayfası benim tam görevimi açıklıyor. (Kodun henüz ne kadar kararlı olduğundan emin değilim.)


0

Veri kalıcılığı için y_serial Python modülüne bir göz atardım: http://yserial.sourceforge.net

Bu, tek bir SQLite veritabanını çevreleyen kilitlenme sorunlarını çözer. Eşzamanlılık talebi ağırlaşırsa, yükü stokastik zamana yaymak için birçok veritabanının Farm sınıfını kolayca kurabilirsiniz.

Umarım bu projenize yardımcı olur ... 10 dakika içinde uygulanacak kadar basit olmalıdır.


0

Yukarıdaki cevapların hiçbirinde herhangi bir kıyaslama bulamadım, bu yüzden her şeyi kıyaslamak için bir test yazdım.

3 yaklaşım denedim

  1. SQLite veritabanından sıralı olarak okuma ve yazma
  2. Okumak / yazmak için ThreadPoolExecutor kullanma
  3. Bir ProcessPoolExecutor okumak / yazmak için kullanma

Karşılaştırmadan elde edilen sonuçlar ve çıkarımlar aşağıdaki gibidir

  1. Sıralı okumalar / sıralı yazmalar en iyi sonucu verir
  2. Paralel olarak işlem yapmanız gerekiyorsa, paralel olarak okumak için ProcessPoolExecutor'u kullanın.
  3. Veritabanı kilitli hatalarla karşılaşacağınız için ThreadPoolExecutor'ı veya ProcessPoolExecutor'u kullanarak herhangi bir yazma işlemi gerçekleştirmeyin ve parçayı tekrar eklemeyi yeniden denemeniz gerekir.

Testler için kodu ve eksiksiz çözümü SO cevabımda bulabilirsiniz BURADA Umarım yardımcı olur!


-1

Kilitli veritabanlarında hata almanızın en olası nedeni, sorun

conn.commit()

bir veritabanı işlemini bitirdikten sonra. Aksi takdirde, veritabanınız yazma kilitli olacak ve bu şekilde kalacaktır. Yazmayı bekleyen diğer iş parçacıkları bir süre sonra zaman aşımına uğrayacaktır (varsayılan 5 saniyeye ayarlanmıştır , bununla ilgili ayrıntılar için bkz. Http://docs.python.org/2/library/sqlite3.html#sqlite3.connect ) .

Doğru ve eşzamanlı ekleme örneği şu şekildedir:

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'ı seviyorsanız veya SQLite veri tabanlarıyla çalışan başka araçlara sahipseniz veya CSV dosyalarını SQLite db dosyalarıyla değiştirmek istiyorsanız veya platformlar arası IPC gibi nadir bir şey yapmanız gerekiyorsa, SQLite harika bir araçtır ve amaca çok uygundur. Doğru hissettirmiyorsa, farklı bir çözüm kullanmaya zorlanmanıza izin vermeyin!

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.