Masinis įdėklas su SQLAlchemy ORM

Ar yra būdas priversti SQLAlchemiją daryti masinį įdėjimą, o ne įterpti kiekvieną atskirą objektą. tai yra,

daro:

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

ir ne:

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

Aš tiesiog konvertuojau keletą kodų, kad galėčiau naudoti sqlalchemy, o ne žaliavinį SQL, ir nors dabar yra daug patogiau dirbti su juo, atrodo, kad jis yra lėčiau (iki 10 kartų), man įdomu, priežastis.

Ar galiu efektyviau pagerinti situaciją naudojant sesijas. Šiuo metu turiu autoCommit=False ir autoCommit=False session.commit() pridėjus kai kuriuos dalykus. Nors atrodo, kad duomenys yra pasenę, jei DB yra pakeistas kitur, pavyzdžiui, net jei darau naują užklausą, ar vis dar gaunu senų rezultatų?

Dėkojame už pagalbą!

86
07 сент. nustatė Nick Holden , rugsėjo 7 d 2010-09-07 16:42 '10, 16:42, 2010-09-07 16:42
@ 10 atsakymų

SQLAlchemy pristatė tai 1.0.0 versijoje:

Birios operacijos - SQLAlchemy dokumentai

Naudodamiesi šiomis operacijomis, dabar galite atlikti didelius lapus arba atnaujinimus!

Pavyzdžiui, galite:

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

Čia bus padarytas pagrindinis įdėklas.

106
03 июля '15 в 13:57 2015-07-03 13:57 Atsakymą pateikė Pierre liepos 03 d. 15, 13:57 2015-07-03 13:57

Kiek aš žinau, nėra jokio būdo priversti ORM gaminti tūrinius intarpus. Manau, kad pagrindinė priežastis yra ta, kad SQLAlchemy turėtų sekti kiekvieną objekto identifikatorių (ty naujus pirminius raktus), o volumetriniai intarpai ją užkirsti kelią. Pvz., Jei jūsų foo lentelėje yra id stulpelis ir žemėlapiai priskiriami foo klasei:

 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 

Kadangi SQLAlchemy užėmė x.id vertę neišduodama kito užklausos, galime daryti išvadą, kad ji gavo vertę tiesiai iš INSERT . Jei per tuos pačius atvejus jums nereikia prieigos prie sukurtų objektų, galite praleisti savo įrašo ORM sluoksnį:

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

SQLAlchemy negali suderinti šių naujų linijų su jokiais esamais objektais, taigi turėsite iš naujo paprašyti jų tolesnėms operacijoms.

Kalbant apie senesnius duomenis, naudinga prisiminti, kad sesija neturi integruoto būdo žinoti, kada duomenų bazė keičiama ne sesijos metu. Jei norite pasiekti išorinius modifikuotus duomenis per esamus atvejus, egzemplioriai turi būti pažymėti kaip pasibaigę. Tai įvyksta pagal nutylėjimą session.commit() , bet gali būti atliekama rankiniu būdu, paskambinus session.expire_all() arba session.expire(instance) . Pavyzdys (trūksta 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() baigiasi x , todėl pirmasis spausdinimo pareiškimas netiesiogiai atveria naują sandorį ir pakartotinai prašo x atributų. Jei komentuosite pirmąjį spausdinimo pareiškimą, pastebėsite, kad antroji dabar gauna teisingą vertę, nes naujas prašymas neišduodamas iki atnaujinimo.

Tai prasminga sandorio izoliacijos požiūriu - jums reikia tik gauti išorinius sandorių pakeitimus. Jei tai sukelia problemų, siūlau paaiškinti ar permąstyti taikomųjų programų operacijų ribas, o ne iš karto pereiti prie session.expire_all() .

26
08 сент. atsakymas pateikiamas dhaffey 08 sep . 2010-09-08 01:03 '10, 1:03 2010-09-08 01:03

„Sqlalchemy“ dokumentai puikiai atspindi įvairius metodus, kurie gali būti naudojami dideliems intarpams:

ORM nėra pirmiausia skirti didelio našumo intarpams - tai yra priežastis, kodėl SQLAlchemy siūlo Core papildomai prie ORM kaip pirmos klasės komponentą.

Naudojant greitai įdėtus intarpus, „SQL“ generavimas ir „Vertex“ pagrindu veikianti ORM sistema yra „Core“ dalis. Naudodami šią sistemą, mes galime sukurti INSERT, kuris konkuruoja naudojant tiesioginę duomenų bazės API.

Kaip alternatyvą ORM, „SQLAlchemy“ siūlo paketų operacijų metodų paketą, pagal kurį darbo vieneto poskirsniuose yra kabliukai, skirti išskirti branduolio lygio INSERT ir UPDATE konstruktorius su mažu automatizavimo laipsniu pagal ORM.

Toliau pateiktame pavyzdyje parodyti keli skirtingų styginių įterpimo metodų laikini bandymai, pereinant nuo automatizuotų iki mažiausių. Naudojant cPython 2.7, įvyko vykdymo laikas:

 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 

Scenarijaus:

 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) 
15
18 дек. Atsakymą pateikė Grant Humphries gruodžio 18 d. 2015-12-18 00:03 '15 - 0:03 2015-12-18 00:03

Paprastai tai darau su 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() 
12
26 апр. atsakymas duotas reubano 26 Bal. 2017-04-26 13:59 '17, 13:59 pm 2017-04-26 13:59

Tiesioginė parama buvo įtraukta į SQLAlchemy nuo 0.8 versijos

Pagal docs , connection.execute(table.insert().values(data)) turėtų daryti triuką. (Atkreipkite dėmesį, kad tai ne tas pats, kaip connection.execute(table.insert(), data) , dėl kurio, kai skambinate executemany , atsiranda daug atskirų eilių. Kitaip nei vietinis ryšys, veiklos skirtumas gali būti didžiulis.

9
01 дек. Atsakymą pateikė user3805082 01 Dec. 2014-12-01 21:49 '14 ne 21:49 2014-12-01 21:49

SQLAlchemy pristatė tai 1.0.0 versijoje:

Birios operacijos - SQLAlchemy dokumentai

Naudodamiesi šiomis operacijomis, dabar galite atlikti didelius lapus arba atnaujinimus!

Pavyzdžiui (jei norite, kad paprastos INSERT lentelės būtų minimalios, galite naudoti „ Session.bulk_insert_mappings() :

 loadme = [ (1, 'a') , (2, 'b') , (3, 'c') ] dicts = [] for i in range(len(loadme)): dicts.append(dict(bar=loadme[i][0], fly=loadme[i][1])) s = Session() s.bulk_insert_mappings(Foo, dicts) s.commit() 

Arba, jei norite, praleiskite loadme ir rašykite žodynus tiesiai į dicts (bet manau, kad lengviau pašalinti visus duomenų dicts ir dicts žodynų sąrašą kilpa).

6
19 февр. Juanitogan atsakymas 19 vasario mėn 2016-02-19 03:44 '16 at 3:44 2016-02-19 03:44

Pierre atsakymas yra teisingas, tačiau viena problema yra ta, kad „ bulk_save_objects pagal nutylėjimą negrąžina pirminių objektų raktų, jei tai jums trukdo. Nustatykite „ return_defaults į „ True kad gautumėte šį elgesį.

Dokumentacija yra čia .

 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() 
5
30 мая '16 в 2:42 2016-05-30 02:42 atsakymą pateikė Matthew Moisen gegužės 30 d., 16 d., 2:42 2016-05-30 02:42

Taip yra:

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

Tai atrodys taip:

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

SQLAlchemy DUK apima įvairius fiksavimo metodus.

4
28 марта '15 в 2:27 2015-03-28 02:27 atsakymas, kurį pateikė Eefretas kovo 28 d., 15 val. 2:27 2015-03-28 02:27

Visi keliai veda į Romą, tačiau kai kurie iš jų kerta kalnus, reikalingi keltai, bet jei norite greitai nuvykti, tiesiog pasiimkite greitkelį.


Tokiu atveju greitkelis turėtų naudoti funkciją execute_batch () psycopg2 . Dokumentacija geriausiai sako:

Dabartinis executemany() (su labai labdaros nepakankamumu) nėra itin veiksmingas. Šios funkcijos gali būti naudojamos paspartinti pareiškimo pakartotinį vykdymą su parametrų rinkiniu. Sumažinus skambučių į serverį skaičių, našumas gali būti kelis dydžius geresnis nei naudojant executemany() .

Savo bandyme execute_batch() maždaug dvigubai greitesnis nei executemany() , ir tai leidžia jums pritaikyti page_size tolesniam pritaikymui (jei norite išspausti paskutinius 2-3% vairuotojo našumo).

Ta pati funkcija gali būti lengvai įjungiama, jei naudojate SQLAlchemy nustatę use_batch_mode=True kaip parametrą, kai kuriate variklį naudojant create_engine()

4
13 июня '18 в 17:13 2018-06-13 17:13 atsakymas pateikiamas chjortlund birželio 13 d. 18 val. 18:13 2018-06-13 17:13

Geriausias atsakymas buvo sqlalchemy dokumentuose:

http://docs.sqlalchemy.org/en/latest/faq/performance.html#im-inserting-400-000-rows-with-t-----------lyly

Yra išsamus galimų sprendimų standarto pavyzdys.

Kaip parodyta dokumentuose:

bulk_save_objects nėra geriausias sprendimas, tačiau jo našumas yra teisingas.

Antras geriausias įsisavinimo aspektas, manau, buvo su SQLAlchemy branduoliu:

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

Šios funkcijos kontekstas pateikiamas straipsnio dokumentuose.

0
25 апр. atsakymas pateikiamas lelabo_m 25 balandžio. 2018-04-25 15:24 '18, 15:24 pm 2018-04-25 15:24

Kiti klausimai apie „ žymes „ arba „ Užduoti klausimą“