Czy istnieje sposób, aby SQLAlchemy wykonywać zbiorcze wstawianie zamiast wstawiania poszczególnych obiektów? to znaczy,
robić:
INSERT INTO `foo` (`bar`) VALUES (1), (2), (3)
zamiast:
INSERT INTO `foo` (`bar`) VALUES (1)
INSERT INTO `foo` (`bar`) VALUES (2)
INSERT INTO `foo` (`bar`) VALUES (3)
Właśnie przekonwertowałem kod tak, aby używał sqlalchemy zamiast surowego sql i chociaż teraz praca z nim jest znacznie przyjemniejsza, wydaje się, że jest teraz wolniejsza (do współczynnika 10), zastanawiam się, czy to jest powód.
Może mógłbym poprawić sytuację, efektywniej wykorzystując sesje. W tej chwili mam autoCommit=False
i robię session.commit()
po dodaniu kilku rzeczy. Chociaż wydaje się, że dane stają się nieaktualne, jeśli baza danych zostanie zmieniona w innym miejscu, na przykład nawet jeśli wykonam nowe zapytanie, nadal otrzymuję stare wyniki?
Dzięki za pomoc!
Odpowiedzi:
SQLAlchemy wprowadziło to w wersji
1.0.0
:Operacje zbiorcze - SQLAlchemy Docs
Dzięki tym operacjom możesz teraz wykonywać zbiorcze wstawianie lub aktualizacje!
Na przykład możesz:
Tutaj zostanie wykonana wkładka zbiorcza.
źródło
\copy
psql (od tego samego klienta do tego samego serwera), widzę ogromną różnicę w wydajności po stronie serwera, co daje około 10 razy więcej wstawień / s. Najwyraźniej ładowanie zbiorcze przy użyciu\copy
(lubCOPY
na serwerze) przy użyciu pakietu w komunikacji z klienta do serwera jest DUŻO lepsze niż używanie SQL przez SQLAlchemy. Więcej informacji: Duża masa wkładka różnica wydajności PostgreSQL vs ... .Dokumentacja sqlalchemy zawiera opis wydajności różnych technik, których można użyć do wstawiania zbiorczego:
źródło
O ile wiem, nie ma sposobu, aby ORM wydawał zbiorcze wkładki. Uważam, że głównym powodem jest to, że SQLAlchemy musi śledzić tożsamość każdego obiektu (tj. Nowe klucze podstawowe), a wstawianie zbiorcze koliduje z tym. Na przykład zakładając, że Twoja
foo
tabela zawieraid
kolumnę i jest odwzorowana naFoo
klasę:Ponieważ SQLAlchemy pobrał wartość
x.id
bez wydawania kolejnego zapytania, możemy wywnioskować, że pobrał wartość bezpośrednio zINSERT
instrukcji. Jeśli nie potrzebujesz późniejszego dostępu do utworzonych obiektów za pośrednictwem tych samych instancji, możesz pominąć warstwę ORM dla swojej wstawki:SQLAlchemy nie może dopasować tych nowych wierszy do żadnych istniejących obiektów, więc będziesz musiał ponownie zapytać o nie w celu wykonania kolejnych operacji.
Jeśli chodzi o nieaktualne dane, warto pamiętać, że sesja nie ma wbudowanego sposobu, aby dowiedzieć się, kiedy baza danych zostanie zmieniona poza sesją. Aby uzyskać dostęp do danych zmodyfikowanych zewnętrznie za pośrednictwem istniejących wystąpień, należy je oznaczyć jako wygasłe . Dzieje się tak domyślnie
session.commit()
, ale można to zrobić ręcznie, dzwoniącsession.expire_all()
lubsession.expire(instance)
. Przykład (pominięty SQL):session.commit()
wygasax
, więc pierwsza instrukcja print niejawnie otwiera nową transakcję i ponownie wysyła zapytaniex
do atrybutów. Jeśli skomentujesz pierwszą instrukcję print, zauważysz, że druga pobiera teraz poprawną wartość, ponieważ nowe zapytanie jest emitowane dopiero po aktualizacji.Ma to sens z punktu widzenia izolacji transakcyjnej - należy odbierać jedynie zewnętrzne modyfikacje pomiędzy transakcjami. Jeśli sprawia Ci to kłopoty, sugeruję wyjaśnienie lub ponowne przemyślenie granic transakcji w aplikacji zamiast natychmiastowego sięgania po
session.expire_all()
.źródło
autocommit=False
wierzę, należy dzwonićsession.commit()
po zakończeniu żądanie (nie jestem zaznajomiony z TurboGears, więc ignorować tego, że jeśli jest obsługiwane przez ciebie na poziomie ramowej). Oprócz upewnienia się, że zmiany wprowadziły je do bazy danych, spowodowałoby to wygaśnięcie wszystkiego w sesji. Następna transakcja nie rozpocznie się do następnego użycia tej sesji, więc przyszłe żądania w tym samym wątku nie będą widzieć nieaktualnych danych.session.execute(Foo.__table__.insert(), values)
Zwykle robię to za pomocą
add_all
.źródło
.add
ich na sesję pojedynczo?Add the given collection of instances to this Session.
Czy masz jakiś powód, by sądzić, że nie wykonuje ona zbiorczego wstawiania?.add
każdy element jest osobno.bulk_save_objects()
,flush()
gdy możemy uzyskać identyfikator obiektu, alebulk_save_objects()
nie możemy (zdarzenie zflush()
wywołane).Bezpośrednie wsparcie zostało dodane do SQLAlchemy w wersji 0.8
Zgodnie z dokumentacją ,
connection.execute(table.insert().values(data))
powinno załatwić sprawę. (Zwróć uwagę, że to nie to samo,connection.execute(table.insert(), data)
co powoduje wstawianie wielu pojedynczych wierszy przez wywołanieexecutemany
). W przypadku wszystkich połączeń innych niż lokalne różnica w wydajności może być ogromna.źródło
SQLAlchemy wprowadziło to w wersji
1.0.0
:Operacje zbiorcze - SQLAlchemy Docs
Dzięki tym operacjom możesz teraz wykonywać zbiorcze wstawianie lub aktualizacje!
Na przykład (jeśli chcesz najmniejszego narzutu dla prostych wstawek w tabeli), możesz użyć
Session.bulk_insert_mappings()
:Lub, jeśli chcesz, pomiń
loadme
krotki i napisz słowniki bezpośrednio dodicts
(ale łatwiej mi jest pozostawić całą rozmowę z danymi i załadować listę słowników w pętli).źródło
Odpowiedź Piere'a jest poprawna, ale jedną kwestią jest to, że
bulk_save_objects
domyślnie nie zwraca kluczy głównych obiektów, jeśli cię to interesuje. Ustaw,return_defaults
abyTrue
uzyskać takie zachowanie.Dokumentacja jest tutaj .
źródło
Wszystkie drogi prowadzą do Rzymu , ale niektóre z nich przecinają góry, wymagają promów, ale jeśli chcesz się tam szybko dostać, po prostu jedź autostradą.
W tym przypadku autostrady jest użycie execute_batch () cechę psycopg2 . Dokumentacja mówi to najlepiej:
Obecna implementacja
executemany()
(używając niezwykle charytatywnego niedomówienia) nie jest szczególnie skuteczna. Funkcje te mogą służyć do przyspieszenia powtarzania instrukcji dla zestawu parametrów. Zmniejszając liczbę obiegów serwera w obie strony, wydajność może być o rząd wielkości lepsza niż przy użyciuexecutemany()
.W moim własnym teście
execute_batch()
jest około dwa razy szybszy niżexecutemany()
i daje możliwość skonfigurowania page_size do dalszych poprawek (jeśli chcesz wycisnąć ostatnie 2-3% wydajności ze sterownika).Tę samą funkcję można łatwo włączyć, jeśli używasz SQLAlchemy, ustawiając
use_batch_mode=True
jako parametr podczas tworzenia wystąpienia silnika za pomocącreate_engine()
źródło
execute_values
jest szybszy niż psycopg2execute_batch
podczas wstawiania zbiorczego!To jest sposób:
To wstawi w ten sposób:
Odniesienie: FAQ SQLAlchemy zawiera testy porównawcze dla różnych metod zatwierdzania.
źródło
Najlepszą odpowiedzią, jaką do tej pory znalazłem, była dokumentacja sqlalchemy:
http://docs.sqlalchemy.org/en/latest/faq/performance.html#im-inserting-400-000-rows-with-the-orm-and-it-s-really-slow
Istnieje kompletny przykład wzorców możliwych rozwiązań.
Jak pokazano w dokumentacji:
bulk_save_objects nie jest najlepszym rozwiązaniem, ale jego wydajność jest poprawna.
Wydaje mi się, że drugą najlepszą implementacją pod względem czytelności był rdzeń SQLAlchemy:
Kontekst tej funkcji podano w artykule dokumentacji.
źródło