SQLAlchemy को सीधे sqlite3 के उपयोग की तुलना में sqlite 25 गुना धीमी गति से क्यों डाला जाता है?


81

यह सरल परीक्षण केस SQLAlchemy से 100,000 पंक्तियों को 25 बार धीमा करने की तुलना में क्यों है क्योंकि यह सीधे sqlite3 ड्राइवर का उपयोग कर रहा है? मैंने वास्तविक दुनिया के अनुप्रयोगों में इसी तरह की मंदी देखी है। क्या मुझसे कुछ गलत हो रही है?

#!/usr/bin/env python
# Why is SQLAlchemy with SQLite so slow?
# Output from this program:
# SqlAlchemy: Total time for 100000 records 10.74 secs
# sqlite3:    Total time for 100000 records  0.40 secs


import time
import sqlite3

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String,  create_engine 
from sqlalchemy.orm import scoped_session, sessionmaker

Base = declarative_base()
DBSession = scoped_session(sessionmaker())

class Customer(Base):
    __tablename__ = "customer"
    id = Column(Integer, primary_key=True)
    name = Column(String(255))

def init_sqlalchemy(dbname = 'sqlite:///sqlalchemy.db'):
    engine  = create_engine(dbname, echo=False)
    DBSession.configure(bind=engine, autoflush=False, expire_on_commit=False)
    Base.metadata.drop_all(engine)
    Base.metadata.create_all(engine)

def test_sqlalchemy(n=100000):
    init_sqlalchemy()
    t0 = time.time()
    for i in range(n):
        customer = Customer()
        customer.name = 'NAME ' + str(i)
        DBSession.add(customer)
    DBSession.commit()
    print "SqlAlchemy: Total time for " + str(n) + " records " + str(time.time() - t0) + " secs"

def init_sqlite3(dbname):
    conn = sqlite3.connect(dbname)
    c = conn.cursor()
    c.execute("DROP TABLE IF EXISTS customer")
    c.execute("CREATE TABLE customer (id INTEGER NOT NULL, name VARCHAR(255), PRIMARY KEY(id))")
    conn.commit()
    return conn

def test_sqlite3(n=100000, dbname = 'sqlite3.db'):
    conn = init_sqlite3(dbname)
    c = conn.cursor()
    t0 = time.time()
    for i in range(n):
        row = ('NAME ' + str(i),)
        c.execute("INSERT INTO customer (name) VALUES (?)", row)
    conn.commit()
    print "sqlite3: Total time for " + str(n) + " records " + str(time.time() - t0) + " sec"

if __name__ == '__main__':
    test_sqlalchemy(100000)
    test_sqlite3(100000)

मैंने कई बदलावों की कोशिश की है (देखें http://pastebin.com/zCmzDraU )

जवाबों:


189

SQLAlchemy ORM डेटाबेस में परिवर्तन को सिंक्रनाइज़ करते समय कार्य पैटर्न की इकाई का उपयोग करता है । यह पैटर्न डेटा के सरल "आवेषण" से बहुत आगे निकल जाता है। इसमें वे विशेषताएँ शामिल हैं जो ऑब्जेक्ट पर असाइन किए गए हैं, एक विशेषता इंस्ट्रूमेंटेशन सिस्टम का उपयोग करके प्राप्त की जाती हैं जो ऑब्जेक्ट पर किए गए परिवर्तनों को ट्रैक करती हैं जैसे कि वे बनाई जाती हैं, इसमें शामिल हैं कि सभी पंक्तियों को एक पहचान मानचित्र में ट्रैक किया जाता हैजिसका प्रभाव है कि प्रत्येक पंक्ति के लिए SQLAlchemy को अपनी "अंतिम सम्मिलित आईडी" पुनः प्राप्त करनी चाहिए यदि पहले से नहीं दी गई है, और इसमें यह भी शामिल है कि सम्मिलित की जाने वाली पंक्तियों को स्कैन किया जाता है और आवश्यकतानुसार निर्भरता के लिए क्रमबद्ध किया जाता है। इस सब को चालू रखने के लिए ऑब्जेक्ट भी बहीखाते की एक उचित डिग्री के अधीन हैं, जो एक ही बार में बहुत बड़ी संख्या में पंक्तियों के लिए बड़ी डेटा संरचनाओं के साथ बिताए गए समय की एक अयोग्य राशि बना सकता है, इसलिए इन पर मंथन करना सबसे अच्छा है।

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

इसलिए ओआरएम मूल रूप से उच्च-प्रदर्शन थोक आवेषण के लिए अभिप्रेत नहीं हैं। यह संपूर्ण कारण है कि SQLAlchemy के पास दो अलग-अलग लाइब्रेरी हैं, जिन्हें आप ध्यान से देखेंगे तो यदि आप http://docs.sqlalchemy.org/en/latest/index.html पर देखें तो आपको इंडेक्स पेज पर दो अलग-अलग पड़ाव दिखाई देंगे। ओआरएम के लिए एक और कोर के लिए एक। आप दोनों को समझे बिना प्रभावी ढंग से SQLAlchemy का उपयोग नहीं कर सकते।

तेजी से थोक आवेषण के उपयोग के मामले में, SQLAlchemy कोर प्रदान करता है , जो SQL पीढ़ी और निष्पादन प्रणाली है जो ORM शीर्ष पर बनाता है। इस प्रणाली का प्रभावी ढंग से उपयोग करके हम एक INSERT का उत्पादन कर सकते हैं जो कच्चे SQLite संस्करण के साथ प्रतिस्पर्धी है। नीचे दी गई स्क्रिप्ट यह दर्शाती है, साथ ही एक ओआरएम संस्करण भी है जो प्राथमिक कुंजी पहचानकर्ताओं को पूर्व-असाइन करता है ताकि ओआरएम पंक्तियों को सम्मिलित करने के लिए निष्पादक () का उपयोग कर सकें। दोनों ओआरएम संस्करण एक समय में 1000 रिकॉर्ड पर फ्लश को चैंकते हैं और साथ ही एक महत्वपूर्ण प्रदर्शन प्रभाव पड़ता है।

यहाँ देखे गए रूंटिम्स हैं:

SqlAlchemy ORM: Total time for 100000 records 16.4133379459 secs
SqlAlchemy ORM pk given: Total time for 100000 records 9.77570986748 secs
SqlAlchemy Core: Total time for 100000 records 0.568737983704 secs
sqlite3: Total time for 100000 records 0.595796823502 sec

स्क्रिप्ट:

import time
import sqlite3

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String,  create_engine
from sqlalchemy.orm import scoped_session, sessionmaker

Base = declarative_base()
DBSession = scoped_session(sessionmaker())

class Customer(Base):
    __tablename__ = "customer"
    id = Column(Integer, primary_key=True)
    name = Column(String(255))

def init_sqlalchemy(dbname = 'sqlite:///sqlalchemy.db'):
    global engine
    engine = create_engine(dbname, echo=False)
    DBSession.remove()
    DBSession.configure(bind=engine, autoflush=False, expire_on_commit=False)
    Base.metadata.drop_all(engine)
    Base.metadata.create_all(engine)

def test_sqlalchemy_orm(n=100000):
    init_sqlalchemy()
    t0 = time.time()
    for i in range(n):
        customer = Customer()
        customer.name = 'NAME ' + str(i)
        DBSession.add(customer)
        if i % 1000 == 0:
            DBSession.flush()
    DBSession.commit()
    print "SqlAlchemy ORM: Total time for " + str(n) + " records " + str(time.time() - t0) + " secs"

def test_sqlalchemy_orm_pk_given(n=100000):
    init_sqlalchemy()
    t0 = time.time()
    for i in range(n):
        customer = Customer(id=i+1, name="NAME " + str(i))
        DBSession.add(customer)
        if i % 1000 == 0:
            DBSession.flush()
    DBSession.commit()
    print "SqlAlchemy ORM pk given: Total time for " + str(n) + " records " + str(time.time() - t0) + " secs"

def test_sqlalchemy_core(n=100000):
    init_sqlalchemy()
    t0 = time.time()
    engine.execute(
        Customer.__table__.insert(),
        [{"name":'NAME ' + str(i)} for i in range(n)]
    )
    print "SqlAlchemy Core: Total time for " + str(n) + " records " + str(time.time() - t0) + " secs"

def init_sqlite3(dbname):
    conn = sqlite3.connect(dbname)
    c = conn.cursor()
    c.execute("DROP TABLE IF EXISTS customer")
    c.execute("CREATE TABLE customer (id INTEGER NOT NULL, name VARCHAR(255), PRIMARY KEY(id))")
    conn.commit()
    return conn

def test_sqlite3(n=100000, dbname = 'sqlite3.db'):
    conn = init_sqlite3(dbname)
    c = conn.cursor()
    t0 = time.time()
    for i in range(n):
        row = ('NAME ' + str(i),)
        c.execute("INSERT INTO customer (name) VALUES (?)", row)
    conn.commit()
    print "sqlite3: Total time for " + str(n) + " records " + str(time.time() - t0) + " sec"

if __name__ == '__main__':
    test_sqlalchemy_orm(100000)
    test_sqlalchemy_orm_pk_given(100000)
    test_sqlalchemy_core(100000)
    test_sqlite3(100000)

इसे भी देखें: http://docs.sqlalchemy.org/en/latest/faq/performance.html


स्पष्टीकरण के लिए धन्यवाद। क्या इंजन। एक्सक्यूट () DBSession.execute () से काफी अलग है? मैंने DBSession.execute () का उपयोग करके एक सम्मिलित अभिव्यक्ति की कोशिश की थी, लेकिन यह पूर्ण ORM संस्करण की तुलना में बहुत तेज नहीं था।
ब्रैडॉक

4
engine.execute () और DBSession.execute () DBSession.execute () को छोड़कर ज्यादातर एक ही हैं, पाठ में दिए गए सादे SQL स्ट्रिंग () को लपेटेंगे। यदि आप निष्पादन / निष्कासन वाक्यविन्यास का उपयोग कर रहे हैं तो इससे बहुत फर्क पड़ता है। pysqlite पूरी तरह से C में लिखा गया है और इसकी लगभग कोई विलंबता नहीं है, इसलिए किसी भी पायथन ओवरहेड को इसके निष्पादन में जोड़ा गया () कॉल प्रोफाइलिंग में स्पष्ट रूप से दिखाई देगा। यहां तक ​​कि एक भी शुद्ध-पायथन फ़ंक्शन कॉल एक शुद्ध सी-फ़ंक्शन कॉल की तुलना में काफी धीमी है, जैसे कि पाइसक्लाइट का निष्पादन ()। आपको यह भी विचार करने की आवश्यकता है कि SQLAlchemy अभिव्यक्ति का निर्माण एक निष्पादन चरण प्रति निष्पादन () कॉल के माध्यम से होता है।
zzzeek

3
कोर पहले बनाया गया था, हालांकि पहले कुछ हफ्तों के बाद एक बार अवधारणा के मूल प्रमाण ने काम किया (और यह भयानक था ) ओआरएम और कोर उस बिंदु से समानांतर में विकसित किए गए थे।
zzzeek

2
मैं वास्तव में नहीं जानता कि कोई भी ORM मॉडल का चयन क्यों करेगा। डेटाबेस का उपयोग करने वाली अधिकांश परियोजनाओं में +10,000 पंक्तियाँ होंगी। 2 अद्यतन विधियों को बनाए रखना (एक पंक्ति के लिए और एक थोक के लिए) बस स्मार्ट नहीं लगता है।
पीटर मूर

5
होगा .... 10000 पंक्तियाँ उन्हें एक बार में सभी समय थोक में डालने की आवश्यकता है? विशेष रूप से नहीं। उदाहरण के लिए अधिकांश वेब अनुप्रयोग शायद प्रति अनुरोध आधा दर्जन पंक्तियों का आदान-प्रदान करते हैं। ORM कुछ बहुत प्रसिद्ध और उच्च यातायात वेबसाइटों के साथ बहुत लोकप्रिय है।
zzzeek

21

@Zzzeek से उत्कृष्ट जवाब। उन प्रश्नों के लिए एक ही आँकड़े के बारे में सोच रहे लोगों के लिए मैंने @zzzeek कोड को थोड़ा संशोधित किया है, उन्हें डालने के बाद ठीक उसी रिकॉर्ड को क्वेरी करने के लिए, फिर उन रिकॉर्डों को सूची की सूची में परिवर्तित करें।

यहां देखें नतीजे

SqlAlchemy ORM: Total time for 100000 records 11.9210000038 secs
SqlAlchemy ORM query: Total time for 100000 records 2.94099998474 secs
SqlAlchemy ORM pk given: Total time for 100000 records 7.51800012589 secs
SqlAlchemy ORM pk given query: Total time for 100000 records 3.07699990273 secs
SqlAlchemy Core: Total time for 100000 records 0.431999921799 secs
SqlAlchemy Core query: Total time for 100000 records 0.389000177383 secs
sqlite3: Total time for 100000 records 0.459000110626 sec
sqlite3 query: Total time for 100000 records 0.103999853134 secs

यह ध्यान रखना दिलचस्प है कि SQLAlchemy Core का उपयोग करने की तुलना में नंगे sqlite3 का उपयोग करना अभी भी लगभग 3 गुना तेज है। मुझे लगता है कि आपके द्वारा रिजल्टप्रॉक्सी के लिए भुगतान की गई कीमत नंगे sqlite3 पंक्ति के बजाय वापस आ गई है।

SQLAlchemy Core ORM का उपयोग करने से लगभग 8 गुना तेज है। तो ORM का उपयोग करना बहुत मायने रखता है।

यहाँ मैं उपयोग किया गया कोड है:

import time
import sqlite3

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String,  create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.sql import select

Base = declarative_base()
DBSession = scoped_session(sessionmaker())

class Customer(Base):
    __tablename__ = "customer"
    id = Column(Integer, primary_key=True)
    name = Column(String(255))

def init_sqlalchemy(dbname = 'sqlite:///sqlalchemy.db'):
    global engine
    engine = create_engine(dbname, echo=False)
    DBSession.remove()
    DBSession.configure(bind=engine, autoflush=False, expire_on_commit=False)
    Base.metadata.drop_all(engine)
    Base.metadata.create_all(engine)

def test_sqlalchemy_orm(n=100000):
    init_sqlalchemy()
    t0 = time.time()
    for i in range(n):
        customer = Customer()
        customer.name = 'NAME ' + str(i)
        DBSession.add(customer)
        if i % 1000 == 0:
            DBSession.flush()
    DBSession.commit()
    print "SqlAlchemy ORM: Total time for " + str(n) + " records " + str(time.time() - t0) + " secs"
    t0 = time.time()
    q = DBSession.query(Customer)
    dict = [{'id':r.id, 'name':r.name} for r in q]
    print "SqlAlchemy ORM query: Total time for " + str(len(dict)) + " records " + str(time.time() - t0) + " secs"


def test_sqlalchemy_orm_pk_given(n=100000):
    init_sqlalchemy()
    t0 = time.time()
    for i in range(n):
        customer = Customer(id=i+1, name="NAME " + str(i))
        DBSession.add(customer)
        if i % 1000 == 0:
            DBSession.flush()
    DBSession.commit()
    print "SqlAlchemy ORM pk given: Total time for " + str(n) + " records " + str(time.time() - t0) + " secs"
    t0 = time.time()
    q = DBSession.query(Customer)
    dict = [{'id':r.id, 'name':r.name} for r in q]
    print "SqlAlchemy ORM pk given query: Total time for " + str(len(dict)) + " records " + str(time.time() - t0) + " secs"

def test_sqlalchemy_core(n=100000):
    init_sqlalchemy()
    t0 = time.time()
    engine.execute(
        Customer.__table__.insert(),
        [{"name":'NAME ' + str(i)} for i in range(n)]
    )
    print "SqlAlchemy Core: Total time for " + str(n) + " records " + str(time.time() - t0) + " secs"
    conn = engine.connect()
    t0 = time.time()
    sql = select([Customer.__table__])
    q = conn.execute(sql)
    dict = [{'id':r[0], 'name':r[0]} for r in q]
    print "SqlAlchemy Core query: Total time for " + str(len(dict)) + " records " + str(time.time() - t0) + " secs"

def init_sqlite3(dbname):
    conn = sqlite3.connect(dbname)
    c = conn.cursor()
    c.execute("DROP TABLE IF EXISTS customer")
    c.execute("CREATE TABLE customer (id INTEGER NOT NULL, name VARCHAR(255), PRIMARY KEY(id))")
    conn.commit()
    return conn

def test_sqlite3(n=100000, dbname = 'sqlite3.db'):
    conn = init_sqlite3(dbname)
    c = conn.cursor()
    t0 = time.time()
    for i in range(n):
        row = ('NAME ' + str(i),)
        c.execute("INSERT INTO customer (name) VALUES (?)", row)
    conn.commit()
    print "sqlite3: Total time for " + str(n) + " records " + str(time.time() - t0) + " sec"
    t0 = time.time()
    q = conn.execute("SELECT * FROM customer").fetchall()
    dict = [{'id':r[0], 'name':r[0]} for r in q]
    print "sqlite3 query: Total time for " + str(len(dict)) + " records " + str(time.time() - t0) + " secs"


if __name__ == '__main__':
    test_sqlalchemy_orm(100000)
    test_sqlalchemy_orm_pk_given(100000)
    test_sqlalchemy_core(100000)
    test_sqlite3(100000)

मैंने क्वेरी परिणाम को dicts में कनवर्ट किए बिना भी परीक्षण किया और आँकड़े समान हैं:

SqlAlchemy ORM: Total time for 100000 records 11.9189999104 secs
SqlAlchemy ORM query: Total time for 100000 records 2.78500008583 secs
SqlAlchemy ORM pk given: Total time for 100000 records 7.67199993134 secs
SqlAlchemy ORM pk given query: Total time for 100000 records 2.94000005722 secs
SqlAlchemy Core: Total time for 100000 records 0.43700003624 secs
SqlAlchemy Core query: Total time for 100000 records 0.131000041962 secs
sqlite3: Total time for 100000 records 0.500999927521 sec
sqlite3 query: Total time for 100000 records 0.0859999656677 secs

ORM की तुलना में SQLAlchemy Core के साथ क्वेरी करना लगभग 20 गुना तेज है।

यह ध्यान रखना महत्वपूर्ण है कि वे परीक्षण बहुत सतही हैं और उन्हें बहुत गंभीरता से नहीं लिया जाना चाहिए। मुझे कुछ स्पष्ट ट्रिक्स याद आ रही हैं जो आँकड़ों को पूरी तरह से बदल सकती हैं।

प्रदर्शन सुधार को मापने का सबसे अच्छा तरीका सीधे आपके खुद के आवेदन में है। मेरे आँकड़े मत लो।


बस आपको बताना चाहता हूं कि 2019 में हर चीज के नवीनतम संस्करणों के साथ, मैं आपके समय से महत्वपूर्ण सापेक्ष विचलन का निरीक्षण नहीं करता हूं। फिर भी, मैं उत्सुक हूँ अगर कुछ "चाल" याद आती है।
PascalVKooten

0

मैं एक्सप्रेशन एक्सप्रेशन टेस्ट और फिर बेंचमार्क की कोशिश करूँगा ।

यह संभवतया ओआर मैपर ओवरहेड के कारण अभी भी धीमा होगा, लेकिन मुझे उम्मीद है कि इतना धीमा नहीं होगा।

क्या आप परिणाम की कोशिश करने और पोस्ट करने का मन करेंगे। यह बहुत ही रोचक सामग्री है।


1
एक सम्मिलित अभिव्यक्ति का उपयोग करके केवल 10% तेज । काश मैं जानता था कि क्यों: SqlAlchemy सम्मिलित करें: 100000 रिकॉर्ड 9.47 सेकंड के लिए कुल समय
ब्रैडॉक

इसके साथ आपको बग करने के लिए नहीं, लेकिन अगर आप रुचि रखते हैं तो आवेषण के बाद db सत्र से संबंधित कोड और टाइमिट का उपयोग कर सकते हैं। docs.python.org/library/timeit.html
Edmon

मुझे डालने की अभिव्यक्ति के साथ एक ही समस्या है, यह धीमी गति से मृत है, देखें stackoverflow.com/questions/11887895/…
dorvak
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.