SQLAlchemy ORM के साथ थोक सम्मिलित करें


130

क्या SQLAlchemy प्राप्त करने का कोई तरीका नहीं है कि वह प्रत्येक व्यक्तिगत ऑब्जेक्ट डालने के बजाय एक बल्क इंसर्ट करे। अर्थात,

करते हुए:

INSERT INTO `foo` (`bar`) VALUES (1), (2), (3)

बजाय:

INSERT INTO `foo` (`bar`) VALUES (1)
INSERT INTO `foo` (`bar`) VALUES (2)
INSERT INTO `foo` (`bar`) VALUES (3)

मैंने कच्चे sql के बजाय sqlalchemy का उपयोग करने के लिए कुछ कोड को परिवर्तित किया है और हालाँकि अब इसके साथ काम करने के लिए बहुत अच्छा है यह अब धीमा हो रहा है (10 के कारक तक), मुझे आश्चर्य है कि क्या यह कारण है।

हो सकता है कि मैं सत्रों का अधिक कुशलता से उपयोग करके स्थिति में सुधार कर सकूं। फिलहाल मेरे पास है autoCommit=Falseऔर कुछ करने के session.commit()बाद मैंने कुछ सामान जोड़ा है। यद्यपि यह डेटा को बासी होने का कारण लगता है अगर DB को कहीं और बदल दिया जाता है, जैसे कि अगर मैं एक नई क्वेरी करता हूं तो भी मुझे कुछ परिणाम वापस मिलेंगे?

आपकी सहायताके लिए धन्यवाद!


1
यह मदद कर सकता है: stackoverflow.com/questions/270879/…
सीन विएरा

1
निक, मैं समझता हूं कि यह बहुत पुरानी पोस्ट है। क्या शीर्षक को "SQLAlchemy ORM के साथ एकाधिक रिकॉर्ड सम्मिलित करना" सही करना संभव है । आपके द्वारा प्रदान किए गए जैसे बहु-रिकॉर्ड सम्मिलित विवरण डेटाबेस स्तर पर बल्क-लोडिंग ऑपरेशन से काफी अलग हैं। थोक आवेषण 1k + डेटा अपलोड के लिए अभिप्रेत हैं, आमतौर पर बड़े डेटासेट से और अनुप्रयोग प्रबंधकों द्वारा किया जाता है, न कि REST संचालन या अनुप्रयोग स्तर कोड .... चलो हमारे नामकरण का ठीक से उपयोग करें।
W4t3randWind

जो लोग sqlalchemy Core (ORM नहीं) में बल्क ऑपरेशंस के बारे में जानकारी की तलाश में इस सवाल पर डटे हुए हैं, उनके लिए एक और सवाल का जवाब देखें ।
निकोलय

जवाबों:


173

SQLAlchemy ने उस संस्करण में पेश किया 1.0.0:

थोक संचालन - SQLAlchemy डॉक्स

इन ऑपरेशनों के साथ, अब आप बल्क इंसर्ट या अपडेट कर सकते हैं!

उदाहरण के लिए, आप कर सकते हैं:

s = Session()
objects = [
    User(name="u1"),
    User(name="u2"),
    User(name="u3")
]
s.bulk_save_objects(objects)
s.commit()

यहां, एक बल्क इंसर्ट बनाया जाएगा।


30
आपको वास्तव में रिकॉर्ड्स को बचाने के लिए s.commit () की आवश्यकता है (यह मुझे पता लगाने में थोड़ा सा समय लगा)।
horcle_buzz

3
मैंने sqlachemy 1.0.11 के साथ यह कोशिश की और यह अभी भी 3 आवेषण बयान करता है। लेकिन यह सामान्य ऑरम संचालन की तुलना में बहुत तेज है।
zidarsk8

3
जबकि ओपीएस प्रश्न के अनुरूप नहीं है, यह ध्यान देने योग्य है कि यह ओआरएम की कुछ विशेषताओं को तोड़ता है। docs.sqlalchemy.org/en/rel_1_0/orm/…
4:00

@dangel हाँ इस पोस्ट करने के लिए धन्यवाद। हालाँकि, ओपी के शीर्षक में "बल्क लोडिंग" की चिंता है, मल्टी-रिकॉर्ड इन्सर्ट स्टेटमेंट के बारे में उनके सवाल का sqlalchemy के बल्क लोडिंग फीचर से कोई लेना-देना नहीं है।
W4t3randWind

CSV से \copypsql (उसी क्लाइंट से उसी सर्वर पर) के साथ एक ही डेटा सम्मिलित करने की तुलना में , मुझे सर्वर साइड पर प्रदर्शन में भारी अंतर दिखाई देता है जिसके परिणामस्वरूप लगभग 10x अधिक इन्सर्ट / s होते हैं। जाहिरा तौर पर क्लाइंट-से-सर्वर से संचार में एक पैकिंग का उपयोग करके \copy(या COPYसर्वर पर) बल्क-लोडिंग का उपयोग SQLAlchemy के माध्यम से SQL का उपयोग करने से बेहतर है। और जानकारी: डालने प्रदर्शन अंतर PostgreSQL बनाम ... बड़े थोक
gertvdijk

42

Sqlalchemy डॉक्स में विभिन्न तकनीकों के प्रदर्शन पर एक राइटअप है जो कि थोक आवेषण के लिए उपयोग किया जा सकता है:

ORMs मूल रूप से उच्च-प्रदर्शन थोक आवेषण के लिए अभिप्रेत नहीं हैं - यह संपूर्ण कारण है SQLAlchemy ORM के अलावा कोर को प्रथम श्रेणी के घटक के रूप में प्रस्तुत करता है।

तेजी से थोक आवेषण के उपयोग के मामले में, SQL पीढ़ी और निष्पादन प्रणाली है कि ORM शीर्ष पर बनाता है कोर का हिस्सा है। सीधे इस प्रणाली का उपयोग करके, हम एक INSERT का उत्पादन कर सकते हैं जो सीधे कच्चे डेटाबेस एपीआई का उपयोग करने के साथ प्रतिस्पर्धी है।

वैकल्पिक रूप से, SQLAlchemy ORM तरीकों का बल्क ऑपरेशंस सूट प्रदान करता है, जो कोर-लेवल INSERT और UPDATE निर्माण को ORM- आधारित स्वचालन की एक छोटी सी डिग्री के साथ काम करने की प्रक्रिया के उपखंड में हुक प्रदान करता है।

नीचे दी गई उदाहरण पंक्तियों को सम्मिलित करने के कई अलग-अलग तरीकों के लिए समय-आधारित परीक्षणों को दिखाता है, सबसे स्वचालित से कम से कम। 2.7 cPython के साथ, रनटाइम्स देखे गए:

classics-MacBook-Pro:sqlalchemy classic$ python test.py
SQLAlchemy ORM: Total time for 100000 records 12.0471920967 secs
SQLAlchemy ORM pk given: Total time for 100000 records 7.06283402443 secs
SQLAlchemy ORM bulk_save_objects(): Total time for 100000 records 0.856323003769 secs
SQLAlchemy Core: Total time for 100000 records 0.485800027847 secs
sqlite3: Total time for 100000 records 0.487842082977 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())
engine = None


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 xrange(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 xrange(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_orm_bulk_insert(n=100000):
    init_sqlalchemy()
    t0 = time.time()
    n1 = n
    while n1 > 0:
        n1 = n1 - 10000
        DBSession.bulk_insert_mappings(
            Customer,
            [
                dict(name="NAME " + str(i))
                for i in xrange(min(10000, n1))
            ]
        )
    DBSession.commit()
    print(
        "SQLAlchemy ORM bulk_save_objects(): 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 xrange(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 xrange(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_orm_bulk_insert(100000)
    test_sqlalchemy_core(100000)
    test_sqlite3(100000)

1
धन्यवाद। वास्तव में सहायक और पूरी तरह से।
स्टीव बी।

मैंने एक और उदाहरण देखा bindparams का उपयोग करते हुए। वाक्यविन्यास रसीला दिखता है, क्या यह कोई अच्छा है?
जय

35

जहाँ तक मुझे पता है, थोक आवेषण जारी करने के लिए ओआरएम प्राप्त करने का कोई तरीका नहीं है। मेरा मानना ​​है कि अंतर्निहित कारण यह है कि SQLAlchemy को प्रत्येक ऑब्जेक्ट की पहचान (यानी, नई प्राथमिक कुंजी) पर नज़र रखने की आवश्यकता होती है, और थोक आवेषण इसमें हस्तक्षेप करते हैं। उदाहरण के लिए, मान लें कि आपकी fooतालिका में एक idकॉलम है और उसे Fooकक्षा में मैप किया गया है :

x = Foo(bar=1)
print x.id
# None
session.add(x)
session.flush()
# BEGIN
# INSERT INTO foo (bar) VALUES(1)
# COMMIT
print x.id
# 1

चूंकि SQLAlchemy ने x.idकिसी अन्य क्वेरी को जारी किए बिना मान लिया , इसलिए हम अनुमान लगा सकते हैं कि इसे INSERTकथन से सीधे मान मिला । यदि आपको एक ही उदाहरण के माध्यम से निर्मित वस्तुओं तक बाद की पहुँच की आवश्यकता नहीं है , तो आप अपनी प्रविष्टि के लिए ORM परत को छोड़ सकते हैं:

Foo.__table__.insert().execute([{'bar': 1}, {'bar': 2}, {'bar': 3}])
# INSERT INTO foo (bar) VALUES ((1,), (2,), (3,))

SQLAlchemy किसी भी मौजूदा ऑब्जेक्ट के साथ इन नई पंक्तियों से मेल नहीं खा सकता है, इसलिए आपको बाद के किसी भी संचालन के लिए उन्हें नए सिरे से क्वेरी करना होगा।

जहां तक ​​बासी डेटा का सवाल है, यह याद रखना मददगार है कि सत्र सत्र के बाहर डेटाबेस बदलने के दौरान यह जानने का कोई अंतर्निहित तरीका नहीं है। मौजूदा उदाहरणों के माध्यम से बाह्य रूप से संशोधित डेटा तक पहुंचने के लिए, उदाहरणों को समय-सीमा के रूप में चिह्नित किया जाना चाहिए । यह डिफ़ॉल्ट रूप से होता है session.commit(), लेकिन कॉल करके session.expire_all()या मैन्युअल रूप से किया जा सकता है session.expire(instance)। एक उदाहरण (SQL छोड़ा गया):

x = Foo(bar=1)
session.add(x)
session.commit()
print x.bar
# 1
foo.update().execute(bar=42)
print x.bar
# 1
session.expire(x)
print x.bar
# 42

session.commit()समय सीमा समाप्त हो जाती है x, इसलिए पहले प्रिंट स्टेटमेंट में एक नया लेन-देन और फिर से क्वेरीज़ xके गुण खुलते हैं । यदि आप पहले प्रिंट स्टेटमेंट पर टिप्पणी करते हैं, तो आप देखेंगे कि दूसरा अब सही मूल्य चुनता है, क्योंकि नई क्वेरी अपडेट के बाद तक उत्सर्जित नहीं होती है।

यह लेन-देन अलगाव के दृष्टिकोण से समझ में आता है - आपको लेनदेन के बीच केवल बाहरी संशोधनों को चुनना चाहिए। यदि यह आपको परेशान कर रहा है, तो मैं तुरंत आपके आवेदन की लेनदेन की सीमाओं को स्पष्ट करने या फिर से सोचने की सलाह दूंगा session.expire_all()


आपके उत्तर के लिए धन्यवाद, मैं यह बताने जा रहा हूं। एक्सपायरिंग की समस्या का सामना करें, मैंने जो देखा वह बिलकुल नहीं था। मैं अशांत सत्रों का उपयोग कर रहा हूं। एक getSession () क्वेरी (Foo) .filter .... सभी () अनुरोध के आधार पर अलग-अलग चीजें लौटा दीं, जब तक मैंने इसे पुनरारंभ नहीं किया, तब तक अद्यतन रिकॉर्ड्स भी db में नहीं थे। मैंने एक ऑटोकॉमिट = सही और कुछ में जोड़कर इस मुद्दे को तय किया है। अनुरोध पूरा होने के बाद .remove () डी सत्र (मैं इकट्ठा करता हूं कि आप वैसे भी करने के लिए हैं)।
निक होल्डन

मुझे लगता है कि यह अनुरोध के आधार पर अलग-अलग चीजें लौटाता है क्योंकि इसमें पूल में प्रति थ्रेड सत्र था और सत्र अलग-अलग राज्यों में थे? यह थोड़ा अजीब लग रहा था कि सा को नए अनुरोध के बाद नया डेटा नहीं मिलेगा। मुझे उम्मीद है कि मैं यह गलत समझ रहा हूं कि ऑटोकेमिट = गलत क्या कर रहा है
निक होल्डन

के साथ autocommit=False, मेरा मानना ​​है कि आपको session.commit()अनुरोध पूरा होने पर कॉल किया जाना चाहिए (मैं टर्बोगियर्स से परिचित नहीं हूं, इसलिए इसे अनदेखा करें यदि यह आपके लिए फ्रेमवर्क स्तर पर है)। यह सुनिश्चित करने के अलावा कि आपके परिवर्तनों ने इसे डेटाबेस में कर दिया है, इससे सत्र में सब कुछ समाप्त हो जाएगा। अगला लेनदेन उस सत्र के अगले उपयोग तक शुरू नहीं होगा, इसलिए उसी धागे पर भविष्य के अनुरोध बासी डेटा नहीं देखेंगे।
ढेफी

10
वैकल्पिक शैली:session.execute(Foo.__table__.insert(), values)
Joril

6
ध्यान दें कि sqlalchemy के नए संस्करणों में थोक आवेषण
वेन वर्नर

18

मैं आमतौर पर इसका उपयोग करता हूं add_all

from app import session
from models import User

objects = [User(name="u1"), User(name="u2"), User(name="u3")]
session.add_all(objects)
session.commit()

2
क्या आपको यकीन है कि यह काम करता है? यह सिर्फ .addसत्र एक के लिए उन्हें आईएनजी के बराबर नहीं करता है ?
एलेक

यह विधि नाम दिया गया काउंटर सहज होगा, डॉक्स विस्तार में नहीं जाते हैं: Add the given collection of instances to this Session.क्या आपके पास यह विश्वास करने का कोई कारण है कि यह एक थोक आवेषण नहीं करता है?
9

3
मुझे नहीं लगता कि यह बहुत अधिक स्पष्ट है - यह वास्तव में उन सभी चीजों को जोड़ता है जो आप इसे पूछते हैं। सत्र के लिए सभी चीजों को जोड़ने के बारे में कुछ भी नहीं लगता है जैसे कि यह अंतर्निहित एसक्यूएल बयान जारी करेगा। स्रोत को देखते हुए: github.com/zzzeek/sqlalchemy/blob/… यह वास्तव में .addप्रत्येक आइटम को व्यक्तिगत रूप से प्रतीत होता है ।
एलेक

यह bulk_save_objects()एक के साथ तुलना में अच्छी तरह से काम करता है flush(), हम ऑब्जेक्ट की आईडी प्राप्त कर सकते हैं, लेकिन bulk_save_objects()( flush()बुलाया के साथ घटना ) नहीं कर सकते ।
कुन्नूर

14

संस्करण 0.8 के रूप में SQLAlchemy में प्रत्यक्ष समर्थन जोड़ा गया था

डॉक्स के अनुसार , connection.execute(table.insert().values(data))ट्रिक करना चाहिए। (ध्यान दें कि यह वही नहीं है connection.execute(table.insert(), data)जिसके परिणामस्वरूप कॉल के माध्यम से कई व्यक्तिगत पंक्ति आवेषण होते हैं executemany)। कुछ भी पर एक स्थानीय कनेक्शन प्रदर्शन में अंतर बहुत बड़ा हो सकता है।


10

SQLAlchemy ने उस संस्करण में पेश किया 1.0.0:

थोक संचालन - SQLAlchemy डॉक्स

इन ऑपरेशनों के साथ, अब आप बल्क इंसर्ट या अपडेट कर सकते हैं!

उदाहरण के लिए (यदि आप सरल तालिका INSERTs के लिए सबसे कम ओवरहेड चाहते हैं), आप उपयोग कर सकते हैं Session.bulk_insert_mappings():

loadme = [(1, 'a'),
          (2, 'b'),
          (3, 'c')]
dicts = [dict(bar=t[0], fly=t[1]) for t in loadme]

s = Session()
s.bulk_insert_mappings(Foo, dicts)
s.commit()

या, यदि आप चाहें, तो loadmeटुपल्स को छोड़ें और सीधे शब्दकोश लिखें dicts(लेकिन मुझे डेटा से सभी शब्दावलियों को छोड़ना और लूप में शब्दकोशों की सूची को लोड करना आसान लगता है)।


7

पियर का जवाब सही है लेकिन एक मुद्दा यह है कि bulk_save_objectsडिफ़ॉल्ट रूप से वस्तुओं की प्राथमिक कुंजी वापस नहीं आती है, अगर यह आपके लिए चिंता का विषय है। इस व्यवहार को प्राप्त return_defaultsकरने के Trueलिए सेट करें ।

प्रलेखन यहाँ है

foos = [Foo(bar='a',), Foo(bar='b'), Foo(bar='c')]
session.bulk_save_objects(foos, return_defaults=True)
for foo in foos:
    assert foo.id is not None
session.commit()

2
झंडे के साथ एक सावधानी बरतनी चाहिए। यह एक समय में एक वस्तु को क्रमिक रूप से सम्मिलित करेगा और महत्वपूर्ण प्रदर्शन लाभ [1] नहीं हो सकता है। मेरे मामले में, प्रदर्शन में गिरावट आई जिसके कारण मुझे ओवरहेड पर संदेह हुआ। [१]: docs.sqlalchemy.org/en/13/orm/…
dhfromkorea

6

सभी सड़कें रोम की ओर जाती हैं , लेकिन उनमें से कुछ पहाड़ों को पार करती हैं, घाटों की आवश्यकता होती है, लेकिन यदि आप जल्दी से बस मोटरवे लेना चाहते हैं।


इस मामले में मोटर मार्ग का उपयोग करना है execute_batch () की सुविधा psycopg2 । प्रलेखन यह सबसे अच्छा कहता है:

executemany()विशेष रूप से प्रदर्शन नहीं कर रहा है (एक अत्यंत धर्मार्थ समझ का उपयोग कर) के वर्तमान कार्यान्वयन है। इन कार्यों का उपयोग मापदंडों के एक सेट के खिलाफ एक बयान के दोहराया निष्पादन को तेज करने के लिए किया जा सकता है। सर्वर राउंडट्रिप्स की संख्या को कम करके प्रदर्शन का उपयोग करने से बेहतर परिमाण के आदेश हो सकते हैं executemany()

अपने स्वयं के परीक्षण execute_batch()में लगभग दो बार के रूप में उपवास है executemany(), और आगे tweaking के लिए page_size कॉन्फ़िगर करने का विकल्प देता है (यदि आप ड्राइवर से अंतिम 2-3% प्रदर्शन को निचोड़ना चाहते हैं)।

यदि आप use_batch_mode=Trueइंजन के साथ इंस्ट्रूमेंट करते हैं तो पैरामीटर के रूप में सेट करके यदि आप SQLAlchemy का उपयोग कर रहे हैं तो वही सुविधा आसानी से सक्षम की जा सकती हैcreate_engine()


नोट: psycopg2's execute_values, थोक आवेषण करते समय psycopg2 की तुलना में तेज़ है execute_batch!
भयंकर

5

यह एक तरीका है:

values = [1, 2, 3]
Foo.__table__.insert().execute([{'bar': x} for x in values])

यह इस तरह सम्मिलित होगा:

INSERT INTO `foo` (`bar`) VALUES (1), (2), (3)

संदर्भ: SQLAlchemy FAQ में विभिन्न प्रतिबद्ध विधियों के लिए मानक शामिल हैं।


3

अब तक मुझे मिला सबसे अच्छा जवाब sqlalchemy प्रलेखन में था:

http://docs.sqlalchemy.org/en/latest/faq/performance.html#im-inserting-400-000-rows-with-the-orm-and-it-s-really-slow

संभावित समाधानों के मानदंड का एक पूरा उदाहरण है।

जैसा कि प्रलेखन में दिखाया गया है:

बल्क_सैव_बॉज सबसे अच्छा समाधान नहीं है लेकिन यह प्रदर्शन सही है।

दूसरा सबसे अच्छा कार्यान्वयन मुझे लगता है कि SQLAlchemy कोर के साथ पठनीयता के संदर्भ में था:

def test_sqlalchemy_core(n=100000):
    init_sqlalchemy()
    t0 = time.time()
    engine.execute(
        Customer.__table__.insert(),
            [{"name": 'NAME ' + str(i)} for i in xrange(n)]
    )

इस फ़ंक्शन का संदर्भ प्रलेखन लेख में दिया गया है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.