2013-01-24 4 views
7

मैं वर्तमान में टर्ननाडो में एक वेब सर्वर पर काम कर रहा हूं, लेकिन एक बार में डेटाबेस तक पहुंचने की कोशिश कर रहे कोड के विभिन्न बिट्स के साथ समस्याएं हैं।प्रत्येक क्वेरी के बाद sqlite डेटाबेस को फिर से खोलने की क्षमता

def query(command, arguments = []): 
    db = sqlite3.open("models/data.db") 
    cursor = db.cursor() 
    cursor.execute(command, arguments) 
    result = cursor.findall() 
    db.close() 
    return result 

मैं बस सोच रहा हूँ कैसे कुशल यह हर क्वेरी के बाद डेटाबेस को फिर से खोलने के लिए है (मैं:

मैं बस एक प्रश्न समारोह जो मूल रूप से इस (लेकिन थोड़ा और अधिक उन्नत) करता होने से इस सरल है अनुमान लगाया जाएगा कि यह एक बहुत बड़ा निरंतर समय ऑपरेशन है, या यह चीज़ों या कुछ कैश करेगा?), और क्या ऐसा करने का एक बेहतर तरीका है।

+1

कृपया 'ओपन' और 'findall' के बजाय 'कनेक्ट' और' fetchall' जैसे सही कार्यों के साथ वास्तविक कोड पोस्ट करें। – abarnert

+0

आपके पास "कोड के विभिन्न बिट्स के साथ समस्याएं" क्या हैं? यदि आप एकाधिक समवर्ती लिखने की कोशिश कर रहे हैं, तो असली आरडीबीएमएस का उपयोग करें http://www.sqlite.org/faq.html#q5 – msw

उत्तर

12

मैं अपना खुद का जवाब जोड़ रहा हूं क्योंकि मैं वर्तमान में स्वीकार किए गए एक से असहमत हूं। यह कहता है कि ऑपरेशन थ्रेड-सुरक्षित नहीं है, लेकिन यह सादा गलत है - SQLite uses file locking यह सुनिश्चित करने के लिए कि वर्तमान में ACID का पालन करें, वर्तमान प्लेटफ़ॉर्म के लिए उपयुक्त है।

यूनिक्स सिस्टम पर यह fcntl() या flock() लॉकिंग होगा, जो प्रति-फ़ाइल हैंडल लॉक है। नतीजतन, कोड पोस्ट किया गया है जो हर बार एक नया कनेक्शन बनाता है हमेशा एक नया फाइलहेडल आवंटित करेगा और इसलिए SQLite का अपना लॉकिंग डेटाबेस भ्रष्टाचार को रोक देगा। इसका एक परिणाम यह है कि एनएफएस शेयर या इसी तरह SQLite का उपयोग करना आम तौर पर एक बुरा विचार है, क्योंकि अक्सर यह विशेष रूप से विश्वसनीय लॉकिंग प्रदान नहीं करता है (हालांकि यह आपके एनएफएस कार्यान्वयन पर निर्भर करता है)।

जैसा कि @बर्नर्ट ने पहले ही टिप्पणियों में SQLite has had issues with threads में बताया है, लेकिन यह धागे के बीच एक कनेक्शन साझा करने से संबंधित था। जैसा कि वह भी उल्लेख करता है, इसका मतलब है कि यदि आप एक एप्लिकेशन-व्यापी पूल का उपयोग करते हैं तो आपको रनटाइम त्रुटियां मिलेंगी यदि दूसरा थ्रेड पूल से रीसाइक्लिंग कनेक्शन खींचता है। ये भी परेशान करने वाली बग्स की तरह हैं जिन्हें आप परीक्षण में नहीं देख सकते हैं (हल्का भार, शायद उपयोग में केवल एक धागा), लेकिन जो आसानी से बाद में सिरदर्द का कारण बन सकता है। मार्टिजन पीटर्स के बाद में थ्रेड-स्थानीय पूल के सुझाव को ठीक काम करना चाहिए।

संस्करण के रूप में SQLite FAQ में दिखाया गया है 3.3.1 यह रूप में लंबे समय धागे के बीच कनेक्शन पारित करने के लिए वास्तव में सुरक्षित है क्योंकि वे किसी भी ताले पकड़ नहीं है - यह एक रियायत है कि SQLite के लेखक के महत्वपूर्ण होने के बावजूद जोड़ा था सामान्य रूप से धागे का उपयोग।कोई समझदार कनेक्शन पूलिंग कार्यान्वयन हमेशा यह सुनिश्चित करेगा कि पूल में कनेक्शन को बदलने से पहले सब कुछ या तो किया गया है या फिर लुढ़का गया है, इसलिए वास्तव में एक एप्लिकेशन-ग्लोबल पूल संभवतः सुरक्षित होगा अगर यह साझा करने के खिलाफ पायथन चेक के लिए नहीं था , जो मुझे लगता है कि SQLite का एक नवीनतम संस्करण उपयोग किया जाता है, भले ही मेरा मानना ​​है। निश्चित रूप से मेरे पायथन 2.7.3 सिस्टम में sqlite3 मॉड्यूल sqlite_version_info रिपोर्टिंग 3.7.9 है, फिर भी यह RuntimeError फेंकता है यदि आप इसे एकाधिक थ्रेड से एक्सेस करते हैं।

किसी भी मामले में, चेक मौजूद होने पर कनेक्शन को प्रभावी रूप से साझा नहीं किया जा सकता है भले ही अंतर्निहित SQLite लाइब्रेरी इसका समर्थन करे।

आपके मूल प्रश्न के रूप में, निश्चित रूप से कनेक्शन के पूल को रखने से हर बार एक नया कनेक्शन बनाना कम कुशल होता है, लेकिन पहले ही उल्लेख किया जा चुका है कि इसे थ्रेड-स्थानीय पूल होना आवश्यक है, जो लागू करने के लिए थोड़ा सा दर्द है । डेटाबेस में नया कनेक्शन बनाने का ओवरहेड अनिवार्य रूप से फ़ाइल खोल रहा है और यह सुनिश्चित करने के लिए हेडर पढ़ रहा है कि यह वैध SQLite फ़ाइल है। वास्तव में एक कथन निष्पादित करने का ओवरहेड अधिक होता है क्योंकि इसे देखने के लिए और फ़ाइल I/O का प्रदर्शन करने की आवश्यकता होती है, इसलिए काम का बड़ा हिस्सा वास्तव में कथन निष्पादन और/या प्रतिबद्ध होने तक स्थगित कर दिया जाता है।

दिलचस्प बात यह है कि, कम से कम लिनक्स सिस्टम पर मैंने कथन निष्पादित करने के लिए कोड को फ़ाइल हेडर पढ़ने के चरणों को दोहराया है - नतीजतन, एक नया कनेक्शन खोलना इतना बुरा नहीं होगा चूंकि कनेक्शन खोलते समय प्रारंभिक पढ़ने से हेडर को सिस्टम के फाइल सिस्टम कैश में खींच लिया जाएगा। तो यह एक एकल फ़ाइल खोलने के ऊपरी हिस्से में उबाल जाता है।

मुझे यह भी जोड़ना चाहिए कि यदि आप अपने कोड को उच्च समेकन तक स्केल करने की उम्मीद कर रहे हैं तो SQLite खराब विकल्प हो सकता है। their own website points out के रूप में यह उच्च समेकन के लिए वास्तव में उपयुक्त नहीं है क्योंकि एक वैश्विक लॉक के माध्यम से सभी पहुंच को निचोड़ने के प्रदर्शन हिट को समेकित धागे की संख्या बढ़ने लगती है। यह ठीक है अगर आप सुविधा के लिए धागे का उपयोग कर रहे हैं, लेकिन यदि आप वास्तव में उच्च स्तर की समेकन की उम्मीद कर रहे हैं तो मैं SQLite से बचूंगा।

संक्षेप में, मुझे नहीं लगता कि हर बार खोलने का आपका दृष्टिकोण वास्तव में इतना बुरा है। क्या थ्रेड-स्थानीय पूल प्रदर्शन में सुधार कर सकता है? शायद हाँ। क्या यह प्रदर्शन लाभ ध्यान देने योग्य होगा? मेरी राय में, जब तक कि आप बहुत अधिक कनेक्शन दरों को नहीं देख रहे हैं, और उस समय आपके पास बहुत सारे धागे होंगे ताकि आप शायद SQLite से दूर जाना चाहें क्योंकि यह समेकन को अच्छी तरह से संभाल नहीं लेता है। यदि आप एक का उपयोग करने का निर्णय लेते हैं, तो सुनिश्चित करें कि यह पूल पर लौटने से पहले कनेक्शन को साफ़ कर देता है - SQLAlchemy में कुछ connection pooling कार्यक्षमता है जो आपको उपयोगी मिल सकती है भले ही आप सभी ओआरएम परतों को शीर्ष पर नहीं चाहते हैं।

संपादित

के रूप में काफी हद बताया मैं वास्तविक समय देते हैं चाहिए। ये काफी कम संचालित वीपीएस से हैं:

>>> timeit.timeit("cur = conn.cursor(); cur.execute('UPDATE foo SET name=\"x\" 
    WHERE id=3'); conn.commit()", setup="import sqlite3; 
    conn = sqlite3.connect('./testdb')", number=100000) 
5.733098030090332 
>>> timeit.timeit("conn = sqlite3.connect('./testdb'); cur = conn.cursor(); 
    cur.execute('UPDATE foo SET name=\"x\" WHERE id=3'); conn.commit()", 
    setup="import sqlite3", number=100000) 
16.518677949905396 

आप लगभग 3x अंतर का एक कारक देख सकते हैं, जो महत्वहीन नहीं है। हालांकि, पूर्ण समय अभी भी उप-मिलीसेकंड है, इसलिए जब तक आप प्रति अनुरोध के बहुत से प्रश्न नहीं करते हैं, तो शायद पहले अनुकूलित करने के लिए अन्य स्थान हैं। यदि आप बहुत सारे प्रश्न पूछते हैं, तो एक उचित समझौता प्रति अनुरोध एक नया कनेक्शन हो सकता है (लेकिन पूल की जटिलता के बिना, बस हर बार फिर से कनेक्ट करें)।

पढ़ने के लिए (यानी चयन करें) तो प्रत्येक बार कनेक्ट करने के सापेक्ष ओवरहेड अधिक होंगे, लेकिन दीवार घड़ी के समय में पूर्ण ओवरहेड सुसंगत होना चाहिए।

जैसा कि इस प्रश्न पर कहीं और चर्चा की जा चुकी है, आपको वास्तविक प्रश्नों के साथ परीक्षण करना चाहिए, मैं बस दस्तावेज बनाना चाहता था जो मैंने अपने निष्कर्षों पर करने के लिए किया था।

+0

मैं इनमें से अधिकांश से सहमत हूं, लेकिन ... प्रदर्शन के बारे में अनुमान लगाने के बजाय, आप वास्तव में इसका परीक्षण कर सकते हैं। और, जैसा कि आप मेरे उत्तर से देख सकते हैं, कनेक्शन खोलने के लिए मेरे लिए एक साधारण क्वेरी के रूप में 4-8x और ओपी के लिए 10x से अधिक समय लगता है, जिसका अर्थ है कि आपका अनुमान है कि "एक नया कनेक्शन खोलना नहीं जा रहा है वह सब बुरा होना "गलत है। यह आसानी से मामला हो सकता है कि ओपी के वास्तविक प्रश्न मेरे सरल प्रश्नों से बहुत धीमे होते हैं कि इससे कोई फर्क नहीं पड़ता है, लेकिन आप इसे प्राथमिकता नहीं मान सकते हैं। – abarnert

+0

मुझे लगता है कि अगर आप 'check_same_thread = गलत' पास करते हैं तो यह आपको कनेक्शन क्रॉस थ्रेड साझा करने की अनुमति देता है।मेरा सवाल यह है कि यदि आप ऐसा करते हैं तो आपको अभी भी अपना सिंक्रनाइज़ेशन प्रदान करने की आवश्यकता है या आपके लिए एक कनेक्शन क्रॉस थ्रेड का उपयोग करके हैंडलैट होगा? –

+0

SQLite3 के हाल के संस्करण (3.3.1 और बाद में) कनेक्शन को एकाधिक थ्रेड में उपयोग करने की अनुमति देते हैं और 'check_same_thread' पैरामीटर इस तरह से समर्थन करने के लिए मौजूद है जो पुराने व्यवहार पर निर्भर किसी भी विरासत कोड को तोड़ नहीं देगा (या है एक मंच पर जहां SQLite का पुराना संस्करण लिंक किया गया है)। हालांकि, आपको यह सुनिश्चित करने की आवश्यकता है कि कोई भी उत्कृष्ट SQLite फ़ाइल ताले नहीं हैं - यानी सभी कथन को अंतिम रूप दिया गया है। विवरण के लिए [यहां] (http://www.sqlite.org/faq.html#q6) देखें। यह एक SQLite सीमा है, पायथन मॉड्यूल नहीं। – Cartroo

0

यह बहुत अक्षम है, और बूट करने के लिए थ्रेड-सुरक्षित नहीं है।

इसके बजाए एक सभ्य कनेक्शन पूल लाइब्रेरी का उपयोग करें। sqlalchemy पूलिंग और बहुत कुछ प्रदान करता है, या खुद को स्क्लाइट के लिए हल्का वजन वाला पूल ढूंढता है।

+0

थ्रेड-सुरक्षित द्वारा आपका क्या मतलब है? क्या इसका मतलब यह नहीं है कि एक ही समय में केवल एक ही चीज़ डेटाबेस तक पहुंच रही है? – matts

+0

@matts: आह, जैसा कि यह पता चला है, टर्ननाडो धागे का उपयोग नहीं करता है (एक धारणा स्ट्राइक आउट)। यदि आप बहु-थ्रेडेड एप्लिकेशन का उपयोग कर रहे थे, तो एकाधिक थ्रेड से SQLite डेटाबेस खोलना जैसे काम नहीं करेगा। –

+0

दाएं। तो थ्रेड-सुरक्षित द्वारा आप का मतलब है कि चक्स त्रुटियों जैसे "डेटाबेस लॉक है"? – matts

1

यदि आप जानना चाहते हैं कि कुछ अक्षम कैसे है, तो एक परीक्षा लिखें और स्वयं को देखें।

एक बार मैंने अपनी उदाहरण को पहले स्थान पर बनाने के लिए बग तय कर दिए, और इसे चलाने के लिए एक टेस्ट केस बनाने के लिए कोड लिखा, यह पता लगाने के लिए कि timeit के साथ समय के साथ यह कितना छोटा था।

http://pastebin.com/rd39vkVa

तो, क्या होता है जब आप इसे चलाते हैं देखते हैं?

$ python2.7 sqlite_test.py 10000 
reopen: 2.02089715004 
reuse: 0.278793811798 
$ python3.3 sqlite_test.py 10000 
reopen: 1.8329595914110541 
reuse: 0.2124928394332528 
$ pypy sqlite_test.py 10000 
reopen: 3.87628388405 
reuse: 0.760829925537 

तो, डेटाबेस खोलने के बारे में 4 से 8 बार करने के लिए एक लगभग खाली टेबल कि कुछ भी नहीं देता है के खिलाफ एक मृत-सरल क्वेरी चलाने के रूप में रूप में लंबे समय लेता है। आपका सबसे बुरा मामला है।

+0

'$ python3.2 test.py 10000' ' फिर से खोलें: 0.8462200164794922' 'पुन: उपयोग: 0.075 9 4895362854004' ऐसा लगता है कि, मेरे अधिकांश प्रश्न उस से बहुत बड़े होंगे, इससे कोई उल्लेखनीय अंतर नहीं आएगा , और दिया कि मैं सबकुछ खुद को लागू कर रहा हूं, यह बहुत कम लाभ के लिए दर्द होगा। इसके अलावा, क्षमा करें, लेकिन मुझे नहीं पता कि टिप्पणियों में न्यूलाइन कैसे डालें – matts

+0

@matts: अफसोस की बात है, जहां तक ​​मुझे पता है, आप टिप्पणियों में न्यूलाइन नहीं डाल सकते हैं। (आप उन्हें पेस्ट कर सकते हैं, लेकिन वे वैसे भी रिक्त स्थान में बदल जाते हैं।) वैसे भी, मैं सुझाव दूंगा कि आप निर्णय लेने से पहले अपने उपयोग के मामले के लिए यथार्थवादी प्रश्नों के नमूने के साथ परीक्षण का प्रयास करें। यदि यह न्यूनतम क्वेरी के लिए 11x है, तो यह अभी भी पर्याप्त क्वेरी के लिए 2x हो सकता है, जो निश्चित रूप से अभी भी ध्यान देने योग्य है ... या यह 1.001x हो सकता है, इस स्थिति में आप इसे अनदेखा कर सकते हैं। लेकिन अग्रिम में अनुमान लगाना बहुत मुश्किल है। – abarnert

0

क्यों न केवल हर एन सेकेंड को फिर से कनेक्ट करें। मेरी ajax अग्रदर्शी/डेटाबेस सेवाओं जिसका बोतल का 30-40 लाइनें हैं मैं हर घंटे फिर से कनेक्ट अपडेट प्राप्त करने के लिए, वहाँ बेहतर डेटाबेस यदि आप लाइव डेटा पर काम करने की जरूरत है अनुकूल हैं:

t0 = time.time() 
con = None 
connect_interval_in_sec = 3600 

def myconnect(dbfile=<path to dbfile>): 
    try: 
     mycon = sqlite3.connect(dbfile) 
     cur = mycon.cursor() 
     cur.execute('SELECT SQLITE_VERSION()') 
     data = cur.fetchone() 
    except sqlite3.Error as e: 
     print("Error:{}".format(e.args[0])) 
     sys.exit(1) 
    return mycon 

और मुख्य पाश में :

if con is None or time.time()-t0 > connect_interval_in_sec: 
    con = myconnect() 
    t0 = time.time() 
<do your query stuff on con> 
संबंधित मुद्दे