Pandas: Gunakan sisipan multi-baris untuk percepatan besar-besaran pada to_sql melalui koneksi latensi tinggi

Dibuat pada 1 Des 2014  ·  48Komentar  ·  Sumber: pandas-dev/pandas

Saya telah mencoba memasukkan ~30k baris ke dalam database mysql menggunakan pandas-0.15.1, oursql-0.9.3.1 dan sqlalchemy-0.9.4. Karena mesin berada di seberang Atlantik dari saya, memanggil data.to_sql membutuhkan waktu >1 jam untuk memasukkan data. Saat memeriksa dengan wireshark, masalahnya adalah ia mengirim sisipan untuk setiap baris, lalu menunggu ACK sebelum mengirim yang berikutnya, dan, singkat cerita, waktu ping membunuh saya.

Namun, mengikuti instruksi dari SQLAlchemy , saya mengubah

def _execute_insert(self, conn, keys, data_iter):
    data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
    conn.execute(self.insert_statement(), data)

ke

def _execute_insert(self, conn, keys, data_iter):
    data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
    conn.execute(self.insert_statement().values(data))

dan seluruh operasi selesai dalam waktu kurang dari satu menit. (Untuk menghemat satu klik, perbedaannya adalah antara beberapa panggilan ke insert into foo (columns) values (rowX) dan satu panggilan besar insert into foo (columns) VALUES (row1), (row2), row3) ). Mengingat seberapa sering orang cenderung menggunakan panda untuk memasukkan data dalam jumlah besar, ini terasa seperti kemenangan besar yang akan sangat bagus untuk dimasukkan lebih luas.

Beberapa tantangan:

  • Tidak semua database mendukung penyisipan multibaris (dulu SQLite dan SQLServer tidak mendukung, meskipun sekarang). Saya tidak tahu cara memeriksa ini melalui SQLAlchemy
  • Server MySQL yang saya gunakan tidak mengizinkan saya untuk memasukkan data sekaligus, saya harus mengatur chunksize (5k bekerja dengan baik, tapi saya kira 30k penuh terlalu banyak). Jika kami menjadikan ini sebagai penyisipan default, kebanyakan orang harus menambahkan ukuran potongan (yang mungkin sulit untuk dihitung, karena mungkin ditentukan oleh ukuran paket maksimum server).

Cara termudah untuk melakukannya, adalah dengan menambahkan parameter boolean multirow= (default False ) ke fungsi to_sql , dan kemudian membiarkan pengguna bertanggung jawab untuk mengatur ukuran chunk, tapi mungkin ada cara yang lebih baik?

Pikiran?

IO SQL Performance

Komentar yang paling membantu

Kami telah menemukan cara menambal monyet - mungkin berguna bagi orang lain. Miliki kode ini sebelum mengimpor pandas.

from pandas.io.sql import SQLTable

def _execute_insert(self, conn, keys, data_iter):
    print "Using monkey-patched _execute_insert"
    data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
    conn.execute(self.insert_statement().values(data))

SQLTable._execute_insert = _execute_insert

Semua 48 komentar

Ini tampaknya masuk akal. Terima kasih telah menyelidiki ini!

Untuk implementasinya, itu akan tergantung pada bagaimana sqlalchemy menangani rasa basis data yang tidak mendukung ini (saya tidak dapat menguji ini saat ini, tetapi tampaknya sqlalchemy menimbulkan kesalahan (mis. http://stackoverflow.com/questions/ 23886764/multiple-insert-statements-in-mssql-with-sqlalchemy).Juga, jika memiliki konsekuensi bahwa banyak orang harus mengatur chunksize, ini memang bukan ide yang baik untuk dilakukan sebagai default (kecuali jika kita menetapkan chunksize ke nilai secara default).
Jadi menambahkan kata kunci tampaknya mungkin lebih baik.

@artemyk @mangecoeur @hayd @danielballan

Rupanya SQLAlchemy memiliki flag dialect.supports_multivalues_insert (lihat misalnya http://pydoc.net/Python/SQLAlchemy/0.8.3/sqlalchemy.sql.compiler/ , mungkin disebut supports_multirow_insert di versi lain, https ://www.mail-archive.com/[email protected]/msg202880.html ).

Karena ini berpotensi untuk banyak mempercepat penyisipan, dan kami dapat memeriksa dukungan dengan mudah, saya pikir mungkin kami dapat melakukannya secara default, dan juga menyetel chunksize ke nilai default (mis. terlalu besar dalam kebanyakan situasi). Jika penyisipan multibaris gagal, kami dapat memberikan pengecualian yang menyarankan untuk menurunkan ukuran chunk?

Sekarang saya hanya perlu meyakinkan orang-orang SQLAlchemy untuk menyetel supports_multivalues_insert menjadi true di SQL Server >2005 (saya meretasnya ke dalam kode dan berfungsi dengan baik, tetapi tidak aktif secara default).

Pada catatan yang lebih sesuai topik, saya pikir chunksize bisa jadi rumit. Pada pengaturan mysql saya (yang mungkin saya konfigurasikan untuk mengizinkan paket besar), saya dapat mengatur chunksize=5000, pada pengaturan SQLServer saya, 500 terlalu besar, tetapi 100 berfungsi dengan baik. Namun, mungkin benar bahwa sebagian besar manfaat dari teknik ini berasal dari penyisipan 1 baris sekaligus ke 100, bukan 100 ke 1000.

Bagaimana jika chunksize=None berarti "Secara adaptif pilih chunksize"? Mencoba sesuatu seperti 5000, 500, 50, 1. Pengguna dapat menonaktifkannya dengan menentukan ukuran chunk. Jika overhead dari upaya ini terlalu besar, saya suka saran @maxgrenderjones : chunksize=10 adalah default yang lebih baik daripada chunksize=1 .

Pada komentar terakhir itu " chunksize=10 adalah default yang lebih baik daripada chunksize=1 " -> menurut saya itu tidak sepenuhnya benar. Situasi saat ini adalah melakukan _one_ mengeksekusi pernyataan yang terdiri dari pernyataan penyisipan baris tunggal multibaris (yang bukan ukuran potongan 1), sedangkan chunksize=10 berarti melakukan banyak pernyataan eksekusi dengan setiap kali satu multi-baris memasukkan.
Dan saya tidak tahu apakah ini tentu lebih cepat, tetapi banyak tergantung pada situasinya. Misalnya dengan kode saat ini dan dengan database sqlite lokal:

In [4]: engine = create_engine('sqlite:///:memory:') #, echo='debug')

In [5]: df = pd.DataFrame(np.random.randn(50000, 10))

In [6]: %timeit df.to_sql('test_default', engine, if_exists='replace')
1 loops, best of 3: 956 ms per loop

In [7]: %timeit df.to_sql('test_default', engine, if_exists='replace', chunksize=10)
1 loops, best of 3: 2.23 s per loop

Tapi tentu saja ini tidak menggunakan fitur multi-baris

Kami telah menemukan cara menambal monyet - mungkin berguna bagi orang lain. Miliki kode ini sebelum mengimpor pandas.

from pandas.io.sql import SQLTable

def _execute_insert(self, conn, keys, data_iter):
    print "Using monkey-patched _execute_insert"
    data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
    conn.execute(self.insert_statement().values(data))

SQLTable._execute_insert = _execute_insert

Mungkin kita bisa mulai dengan menambahkan fitur ini melalui kata kunci multirow=True baru (dengan default False untuk saat ini), dan kemudian kita dapat selalu melihat apakah kita dapat mengaktifkannya secara default?

@maxgrenderjones @nhockham tertarik untuk melakukan PR untuk menambahkan ini?

@jorisvandenbossche Saya pikir berisiko untuk mulai menambahkan argumen kata kunci untuk mengatasi profil kinerja tertentu. Jika Anda dapat menjamin bahwa itu lebih cepat dalam semua kasus (jika perlu dengan memintanya menentukan metode terbaik berdasarkan input) maka Anda tidak memerlukan tanda sama sekali.

Pengaturan DB yang berbeda mungkin memiliki pengoptimalan kinerja yang berbeda (profil kinerja DB yang berbeda, lokal vs jaringan, memori besar vs SSD cepat, dll, dll), jika Anda mulai menambahkan tanda kata kunci untuk masing-masing, itu menjadi berantakan.

Saya akan menyarankan membuat subkelas SQLDatabase dan SQLTable untuk mengatasi implementasi spesifik kinerja, mereka akan digunakan melalui API berorientasi objek. Mungkin metode "pengalihan backend" dapat ditambahkan tetapi terus terang menggunakan api OO sangat sederhana jadi ini mungkin berlebihan untuk apa yang sudah menjadi kasus penggunaan khusus.

Saya membuat sub-kelas seperti itu untuk memuat kumpulan data besar ke Postgres (sebenarnya jauh lebih cepat untuk menyimpan data ke CSV kemudian menggunakan perintah SALIN DARI sql non-standar bawaan daripada menggunakan sisipan, lihat https://Gist.github. com/mangecoeur/1fbd63d4758c2ba0c470#file-pandas_postgres-py). Untuk menggunakannya Anda cukup melakukan PgSQLDatabase(engine, <args>).to_sql(frame, name,<kwargs>)

Sekedar referensi, saya mencoba menjalankan kode oleh @jorisvandenbossche (postingan 3 Desember) menggunakan fitur multirow. Ini sedikit lebih lambat. Jadi pertukaran kecepatan di sini tidak sepele:

In [4]: engine = create_engine('sqlite:///:memory:') #, echo='debug')

In [5]: df = pd.DataFrame(np.random.randn(50000, 10))

In [6]: 

In [6]: %timeit df.to_sql('test_default', engine, if_exists='replace')
1 loops, best of 3: 1.05 s per loop

In [7]: 

In [7]: from pandas.io.sql import SQLTable

In [8]: 

In [8]: def _execute_insert(self, conn, keys, data_iter):
   ...:         data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
   ...:         conn.execute(self.insert_statement().values(data))
   ...:     

In [9]: SQLTable._execute_insert = _execute_insert

In [10]: 

In [10]: reload(pd)
Out[10]: <module 'pandas' from '/usr/local/lib/python2.7/site-packages/pandas/__init__.pyc'>

In [11]: 

In [11]: %timeit df.to_sql('test_default', engine, if_exists='replace', chunksize=10)
1 loops, best of 3: 9.9 s per loop

Juga, saya setuju bahwa menambahkan parameter kata kunci berisiko. Namun, fitur multirow tampaknya cukup mendasar. Juga, 'penambalan monyet' mungkin tidak lebih kuat terhadap perubahan API daripada parameter kata kunci.

Seperti yang saya duga. Penambalan monyet bukanlah solusi yang saya sarankan - melainkan kami mengirimkan sejumlah subkelas berorientasi kinerja yang dapat digunakan oleh pengguna yang terinformasi melalui antarmuka OO (untuk menghindari memuat api fungsional dengan terlalu banyak opsi)

-----Pesan asli-----
Dari: "Artemy Kolchinsky" [email protected]
Dikirim: ‎26/‎02/‎2015 17:13
Kepada: "pydata/pandas" [email protected]
Cc: "mangecoeur" jon. [email protected]
Subjek: Re: [pandas] Gunakan sisipan multi-baris untuk percepatan besar-besaran pada koneksi to_sqlover latensi tinggi (#8953)

Sekedar referensi, saya mencoba menjalankan kode oleh @jorisvandenbossche (postingan 3 Desember) menggunakan fitur multirow. Ini sedikit lebih lambat. Jadi pertukaran kecepatan di sini tidak sepele:
Dalam [4]: ​​engine = create_engine('sqlite:///:memory:') #, echo='debug')

Dalam [5]: df = pd.DataFrame(np.random.randn(50000, 10))

Dalam [6]:

Dalam [6]: %timeit df.to_sql('test_default', mesin, if_exists='replace')
1 loop, terbaik dari 3: 1,05 s per loop

Dalam [7]:

Di [7]: dari pandas.io.sql impor SQLTable

Dalam [8]:

Dalam [8]: def _execute_insert(self, conn, keys, data_iter):
...: data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
...: conn.execute(self.insert_statement().values(data))
...:

Dalam [9]: SQLTable._execute_insert = _execute_insert

Dalam [10]:

Dalam [10]: memuat ulang(pd)
Keluar[10]:

Dalam [11]:

Dalam [11]: %timeit df.to_sql('test_default', engine, if_exists='replace', chunksize=10)
1 loop, terbaik dari 3: 9,9 s per loop
Juga, saya setuju bahwa menambahkan parameter kata kunci berisiko. Namun, fitur multirow tampaknya cukup mendasar. Juga, 'penambalan monyet' mungkin tidak lebih kuat terhadap perubahan API daripada parameter kata kunci.

Balas email ini secara langsung atau lihat di GitHub.

Sesuai judul tiket awal, saya tidak berpikir pendekatan ini akan lebih disukai dalam semua kasus, jadi saya tidak akan menjadikannya default. Namun, tanpa itu, panda to_sql tidak dapat digunakan untuk saya, jadi cukup penting bagi saya untuk terus meminta perubahan. (Ini juga menjadi hal pertama yang saya ubah ketika saya memutakhirkan versi panda saya). Adapun nilai chunksize yang masuk akal, saya rasa tidak ada yang benar n , karena ukuran paket akan tergantung pada berapa banyak kolom yang ada (dan apa yang ada di dalamnya) dengan cara yang sulit diprediksi . Sayangnya SQLServer gagal dengan pesan kesalahan yang terlihat sama sekali tidak terkait (tetapi sebenarnya tidak) jika Anda menyetel chunksize terlalu tinggi (mungkin itulah sebabnya penyisipan multibaris tidak diaktifkan kecuali dengan tambalan di SQLAlchemy), tetapi itu berfungsi dengan baik dengan mysql . Pengguna mungkin perlu bereksperimen untuk menentukan nilai n yang mungkin menghasilkan ukuran paket besar yang dapat diterima (untuk apa pun basis data pendukungnya). Memiliki panda yang memilih n kemungkinan akan membuat kita jauh lebih dalam dalam detail implementasi daripada yang kita inginkan (yaitu arah yang berlawanan dari pendekatan SQLALchemy abstraksi maksimum yang mungkin)

Singkatnya, rekomendasi saya adalah menambahkannya sebagai kata kunci, dengan beberapa komentar bermanfaat tentang cara menggunakannya. Ini bukan pertama kalinya kata kunci digunakan untuk memilih implementasi (lihat: http://pandas.pydata.org/pandas-docs/dev/generated/pandas.DataFrame.apply.html ) tetapi itu mungkin bukan ' t contoh terbaik, karena saya tidak memiliki ide pertama tentang apa arti raw= , bahkan setelah membaca penjelasannya!

Saya perhatikan bahwa itu juga menghabiskan banyak memori. Seperti DataFrame 1,6+ GB dengan sekitar 700.000 baris dan 301 kolom membutuhkan hampir 34 GB selama penyisipan! Itu seperti di atas tidak efisien. Adakah ide tentang mengapa itu bisa terjadi? Berikut adalah klip layar:

image

Hai kawan,
ada kemajuan dalam masalah ini?

Saya mencoba memasukkan sekitar 200 ribu baris menggunakan to_sql tetapi butuh selamanya dan menghabiskan banyak memori! Menggunakan chuncksize membantu dengan memori tetapi kecepatannya masih sangat lambat.

Kesan saya, melihat jejak MSSQL DBase adalah bahwa penyisipan sebenarnya dilakukan satu baris pada saat itu.

Satu-satunya pendekatan yang layak sekarang adalah membuang ke file csv di folder bersama dan menggunakan BULK INSERT. Tapi itu sangat menjengkelkan dan tidak elegan!

@andreacassioli Anda dapat menggunakan odo untuk memasukkan DataFrame ke dalam database SQL melalui file CSV perantara. Lihat Memuat CSV ke Database SQL .

Saya tidak berpikir Anda bisa mendekati kinerja BULK INSERT menggunakan ODBC.

@ostrokach terima kasih, memang saya menggunakan file csv sekarang. Jika saya bisa mendekat, saya akan menukar sedikit waktu untuk kesederhanaan!

Saya pikir ini mungkin membantu seseorang:
http://docs.sqlalchemy.org/en/latest/faq/performance.html#i -m-inserting-400-000-rows-with-the-orm-and-it-s-really-slow

@indera pandas tidak menggunakan ORM, hanya sqlalchemy Core (yang disarankan oleh entri dokumen di sana untuk digunakan untuk sisipan besar)

apakah ada konsensus tentang cara mengatasi hal ini untuk sementara? Saya memasukkan beberapa juta baris ke dalam postgres dan itu membutuhkan waktu lama. Apakah CSV / odo cara yang tepat?

@russlamb cara praktis untuk mengatasi masalah ini adalah dengan mengunggah secara massal. Ini adalah seseorang yang spesifik untuk db, jadi odo memiliki solusi untuk postgresl (dan mungkin mysql ) saya pikir. untuk sesuatu seperti sqlserver Anda harus 'melakukan ini sendiri' (Anda harus menulisnya).

Untuk sqlserver saya menggunakan driver FreeTDS (http://www.freetds.org/software.html dan https://github.com/mkleehammer/pyodbc ) dengan entitas SQLAlchemy yang menghasilkan penyisipan yang sangat cepat (20K baris per bingkai data) :

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()


class DemographicEntity(Base):
    __tablename__ = 'DEMOGRAPHIC'

    patid = db.Column("PATID", db.Text, primary_key=True)
    """
    patid = db.Column("PATID", db.Text, primary_key=True, autoincrement=False, nullable=True)
    birth_date = db.Column("BIRTH_DATE", db.Date)
    birth_time = db.Column("BIRTH_TIME", db.Text(5))
    sex = db.Column("SEX", db.Text(2))

def get_db_url(db_host, db_port, db_name, db_user, db_pass):
    params = parse.quote(
        "Driver={{FreeTDS}};Server={};Port={};"
        "Database={};UID={};PWD={};"
        .format(db_host, db_port, db_name, db_user, db_pass))
    return 'mssql+pyodbc:///?odbc_connect={}'.format(params)

def get_db_pool():
    """
    Create the database engine connection.
    <strong i="6">@see</strong> http://docs.sqlalchemy.org/en/latest/core/engines.html

    :return: Dialect object which can either be used directly
            to interact with the database, or can be passed to
            a Session object to work with the ORM.
    """
    global DB_POOL

    if DB_POOL is None:
        url = get_db_url(db_host=DB_HOST, db_port=DB_PORT, db_name=DB_NAME,
                         db_user=DB_USER, db_pass=DB_PASS)
        DB_POOL = db.create_engine(url,
                                   pool_size=10,
                                   max_overflow=5,
                                   pool_recycle=3600)

    try:
        DB_POOL.execute("USE {db}".format(db=DB_NAME))
    except db.exc.OperationalError:
        logger.error('Database {db} does not exist.'.format(db=DB_NAME))

    return DB_POOL


def save_frame():
    db_pool = get_db_pool()
    records = df.to_dict(orient='records')
    result = db_pool.execute(entity.__table__.insert(), records)

Apakah CSV / odo cara yang tepat?

Solusi ini hampir selalu akan lebih cepat menurut saya, terlepas dari pengaturan multi-baris / chunksize.

Tapi, @russlamb , selalu menarik untuk mendengar apakah kata kunci multi-baris seperti itu akan menjadi peningkatan dalam kasus Anda. Lihat misalnya https://github.com/pandas-dev/pandas/issues/8953#issuecomment -76139975 tentang cara untuk menguji ini dengan mudah.

Saya pikir ada kesepakatan bahwa kami ingin memiliki cara untuk menentukan ini (tanpa harus mengubah default). Jadi, jika seseorang ingin membuat PR untuk ini, tentu saja diterima.
Hanya ada beberapa diskusi tentang cara menambahkan kemampuan ini (kata kunci baru vs subkelas menggunakan OO api).

@jorisvandenbossche Dokumen yang saya tautkan di atas menyebutkan "Atau, SQLAlchemy ORM menawarkan rangkaian metode Operasi Massal, yang menyediakan kait ke dalam subbagian unit proses kerja untuk memancarkan konstruksi INSERT dan UPDATE tingkat Inti dengan tingkat ORM yang kecil otomatisasi berbasis."

Apa yang saya sarankan adalah untuk mengimplementasikan versi khusus sqlserver untuk to_sql yang di bawah tenda menggunakan ORM SQLAlchemy untuk mempercepat seperti pada kode yang saya posting di atas.

Ini diusulkan sebelumnya. Cara yang Anda lakukan adalah mengimplementasikan pandas sql
kelas dioptimalkan untuk backend. Saya memposting inti di masa lalu untuk menggunakan
postgres COPY FROM perintah yang jauh lebih cepat. Namun sesuatu yang mirip
sekarang tersedia di odo, dan dibuat dengan cara yang lebih kuat. Tidak banyak
titik IMHO dalam menduplikasi pekerjaan dari odo.

Pada 7 Mar 2017 00:53, "Andrei Sura" [email protected] menulis:

@jorisvandenbossche https://github.com/jorisvandenbossche Dokumen
Saya menautkan di atas menyebutkan "Atau, SQLAlchemy ORM menawarkan Massal
Rangkaian metode operasi, yang menyediakan kait ke dalam subbagian dari
unit proses kerja untuk memancarkan INSERT dan UPDATE tingkat Inti
konstruksi dengan tingkat kecil otomatisasi berbasis ORM."

Yang saya sarankan adalah mengimplementasikan versi khusus sqlserver untuk
"to_sql" yang di bawah tenda menggunakan inti SQLAlchemy untuk percepatan.


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/pandas-dev/pandas/issues/8953#issuecomment-284437587 ,
atau matikan utasnya
https://github.com/notifications/unsubscribe-auth/AAtYVDXKLuTlsh9ycpMQvU5C0hs_RxuYks5rjCwBgaJpZM4DCjLh
.

Perhatikan juga bahwa Anda menyebutkan sqlalchemy sebagai gantinya. Kecuali sesuatu
telah banyak berubah, hanya inti sqlalchemy yang digunakan dalam hal apa pun, tidak ada orm. Jika kamu
ingin mempercepat lebih dari menggunakan inti Anda harus pergi ke level yang lebih rendah, db
optimasi tertentu

Pada 7 Mar 2017 00:53, "Andrei Sura" [email protected] menulis:

@jorisvandenbossche https://github.com/jorisvandenbossche Dokumen
Saya menautkan di atas menyebutkan "Atau, SQLAlchemy ORM menawarkan Massal
Rangkaian metode operasi, yang menyediakan kait ke dalam subbagian dari
unit proses kerja untuk memancarkan INSERT dan UPDATE tingkat Inti
konstruksi dengan tingkat kecil otomatisasi berbasis ORM."

Yang saya sarankan adalah mengimplementasikan versi khusus sqlserver untuk
"to_sql" yang di bawah tenda menggunakan inti SQLAlchemy untuk percepatan.


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/pandas-dev/pandas/issues/8953#issuecomment-284437587 ,
atau matikan utasnya
https://github.com/notifications/unsubscribe-auth/AAtYVDXKLuTlsh9ycpMQvU5C0hs_RxuYks5rjCwBgaJpZM4DCjLh
.

Apakah ini diperbaiki/diurus? Sampai sekarang memasukkan pandas dataframe ke dalam SQL db sangat lambat kecuali jika itu adalah dataframe mainan. Mari kita putuskan solusi dan mendorongnya ke depan?

@dfernan Seperti disebutkan di atas, Anda mungkin ingin melihat odo . Menggunakan file CSV perantara akan selalu lebih cepat daripada melalui sqlalchemy, tidak peduli apa jenis perbaikan yang terjadi di sini...

@ostrokach , saya tidak yakin bahwa perilaku odo adalah yang diinginkan oleh pengguna Panda biasa. Penyisipan multi-baris melalui ODBC mungkin cukup cepat untuk sebagian besar analis.

Untuk berbicara sendiri, saya hanya menghabiskan beberapa jam beralih dari patch monyet di atas ke odo. Runtime panda biasa adalah 10+ jam, RBAR. Patch monyet berjalan dalam 2 pada kumpulan data yang sama.
Rute odo/CSV lebih cepat, seperti yang diharapkan, tetapi tidak cukup untuk membuatnya sepadan dengan usaha. Saya mengutak-atik masalah konversi CSV yang tidak terlalu saya pedulikan, semuanya atas nama menghindari patch monyet. Saya mengimpor 250 ribu baris dari ~ 10 mysql dan PG DB ke area umum di Postgres, untuk analisis NLP.

Saya sangat akrab dengan pendekatan pemuatan massal yang didukung oleh odo. Saya telah menggunakannya selama bertahun-tahun di mana saya mulai dengan data CSV. Ada batasan utama untuk mereka:

  1. Untuk kasus df->CSV->Postgres, akses shell dan langkah scp diperlukan untuk mendapatkan CSV pada host PG. Sepertinya @mangecoeur telah menyiasatinya dengan aliran ke STDIN.
  2. Untuk tujuan saya (250 ribu baris komentar, dengan banyak kasus khusus dalam konten teks) saya berjuang untuk mendapatkan parameter CSV dengan benar. Saya tidak ingin peningkatan kinerja cukup buruk untuk terus berinvestasi dalam hal ini.

Saya beralih kembali ke tambalan, jadi saya bisa melanjutkan pekerjaan analisis.

Saya setuju dengan @jorisvandenbossche , @maxgrenderjones. Opsi (bukan default) untuk memilih ini akan sangat berguna. Poin @artemyk tentang dialect.supports_multivalues_insert bahkan mungkin menjadikan ini sebagai default yang masuk akal.

Saya senang untuk mengirimkan PR jika itu akan memajukan ini.

hanya untuk menambahkan pengalaman saya dengan odo, itu tidak berfungsi untuk penyisipan massal MS Sql karena masalah yang diketahui dengan penyandian. imho m-row insert adalah solusi praktis yang baik untuk sebagian besar ppl.

@markschwarz opsi untuk mengaktifkan ini bekerja lebih cepat akan sangat disambut!

Menelusuri kueri menggunakan sqlite, sepertinya saya memasukkan multi-sisipan saat menggunakan chunksize :

2017-09-28 00:21:39,007 INFO sqlalchemy.engine.base.Engine INSERT INTO country_hsproduct_year (location_id, product_id, year, export_rca, import_value, cog, export_value, distance, location_level, product_level) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2017-09-28 00:21:39,007 INFO sqlalchemy.engine.base.Engine ((75, 1237, 1996, 1.7283086776733398, 273487116.0, 0.0, 514320160.0, 0.5413745641708374, 'country', '4digit'), (75, 1237, 1997, 1.7167805433273315, 312047528.0, 0.0, 592372864.0, 0.5314807891845703, 'country', '4digit'), (75, 1237, 1998, 1.2120152711868286, 341676961.0, 0.0, 468860608.0, 0.5472233295440674, 'country', '4digit'), (75, 1237, 1999, 1.236651062965393, 334604240.0, 0.0, 440722336.0, 0.5695921182632446, 'country', '4digit'), (75, 1237, 2000, 1.189828872680664, 383555023.0, 0.0, 426384832.0, 0.5794379711151123, 'country', '4digit'), (75, 1237, 2001, 0.9920380115509033, 374157144.0, 0.3462945520877838, 327031392.0, 0.6234743595123291, 'country', '4digit'), (75, 1237, 2002, 1.0405025482177734, 471456583.0, 0.0, 377909376.0, 0.6023964285850525, 'country', '4digit'), (75, 1237, 2003, 1.147829532623291, 552441401.0, 0.0, 481313504.0, 0.5896202325820923, 'country', '4digit')  ... displaying 10 of 100000 total bound parameter sets ...  (79, 1024, 2015, 0.0, None, 0.8785018920898438, 0.0, 0.9823430776596069, 'country', '4digit'), (79, 1025, 1995, 0.0, None, 0.5624096989631653, 0.0, 0.9839603304862976, 'country', '4digit'))

(tanpa patch monyet, yaitu)

Menariknya, dengan patch monyet, itu rusak ketika saya memberikannya chunksize 10^5, tetapi tidak 10^3. Kesalahannya adalah "terlalu banyak variabel sql" di sqlite.

@makmanalp , saya belum melacak di PG untuk memeriksa perilaku itu, tapi saya hampir selalu mengatur chunksize pada insert. Dalam contoh saya di atas, saya secara acak mengaturnya ke 5 nilai antara 200-5000. Saya tidak melihat perbedaan waktu yang drastis di antara pilihan-pilihan itu, tanpa patch monyet. Dengan tambalan, waktu yang berlalu turun ~80%.

https://github.com/pandas-dev/pandas/issues/8953#issuecomment -76139975

Apakah patch monyet ini masih berfungsi? Saya mencobanya di MS SQL Server tetapi tidak melihat peningkatan. Juga, itu melempar pengecualian:

(pyodbc.Error) ('07002', '[07002] [Microsoft][SQL Server Native Client 11.0]COUNT field incorrect or syntax error (0) (SQLExecDirectW)')

@hangyao Saya pikir tambalan itu khusus untuk implementasi, salah satu hal yang diserahkan python DBAPI ke driver DBAPI tentang cara menanganinya. Jadi bisa lebih cepat atau tidak. RE: kesalahan sintaks, tidak yakin tentang itu.

Saya berpikir untuk menambahkan fungsi di bawah ini dalam file /io/sql.py pada baris 507 dalam fungsi _engine_builder dalam clouse JIKA baru pada baris 521 menjadikan _engine_builder 'baru' di bawah potongan. Saya telah mengujinya secara singkat di lingkungan saya dan ini berfungsi dengan baik untuk database MSSQL, mencapai kecepatan >100x. Saya belum mengujinya di database lain.

Hal dari menahan saya untuk membuat PR adalah saya pikir lebih banyak upaya untuk membuatnya rapi dan aman daripada hanya memasukkannya seperti di bawah ini, ini mungkin tidak selalu menjadi spesifikasi yang diinginkan dan menambahkan sakelar boolean, yang mengaktifkan/menonaktifkan pengaturan ini , (misalnya fast_executemany=True ) di to_sql sepertinya usaha yang terlalu besar untuk dilakukan tanpa bertanya, kurasa.

Jadi pertanyaan saya adalah:

  • Apakah fungsi di bawah ini berfungsi dan juga meningkatkan kecepatan INSERT untuk PostgreSQL?

  • Apakah acara pandas menginginkan cuplikan ini di sumbernya? Jika begitu:

  • Apakah diinginkan untuk menambahkan fungsi ini ke fungsi default sql.py atau adakah tempat yang lebih baik untuk menambahkan ini?

Senang mendengar beberapa komentar.

Sumber untuk jawabannya: https://stackoverflow.com/questions/48006551/speeding-up-pandas-dataframe-to-sql-with-fast-executemany-of-pyodbc/48861231#48861231

@event.listens_for(engine, 'before_cursor_execute')
def receive_before_cursor_execute(conn, cursor, statement, params, context, executemany):
    if executemany:
        cursor.fast_executemany = True
def _engine_builder(con):
    """
    Returns a SQLAlchemy engine from a URI (if con is a string)
    else it just return con without modifying it.
    """
    global _SQLALCHEMY_INSTALLED
    if isinstance(con, string_types):
        try:
            import sqlalchemy
        except ImportError:
            _SQLALCHEMY_INSTALLED = False
        else:
            con = sqlalchemy.create_engine(con)

    @event.listens_for(engine, 'before_cursor_execute')
    def receive_before_cursor_execute(conn, cursor, statement, params, context, executemany):
        if executemany:
            cursor.fast_executemany = True
return con

@tsktsktsk123 Baru-baru ini ada PR yang digabungkan terkait dengan ini: https://github.com/pandas-dev/pandas/pull/19664. Saya belum melihat secara detail ke posting Anda, dan itu pasti tidak persis sama (menggunakan atribut supports_multivalues_insert dari mesin sqlalchemy), tetapi hanya untuk memastikan Anda mengetahuinya jika ini sudah membantu demikian juga.

Itu berita bagus! Saya belum melihat PR tetapi akan membandingkannya akhir pekan ini dan kembali dengan hasilnya. Terimakasih atas peringatannya.

Saya baru saja mencoba 0.23.0 RC2 (di postgresql) dan alih-alih meningkatkan kinerja, skrip saya menjadi lebih lambat secara signifikan. Kueri DB menjadi jauh lebih cepat tetapi mengukur waktu untuk to_sql() sebenarnya menjadi 1,5 kali lebih lambat (seperti dari 7 hingga 11 detik)...

Tidak yakin perlambatan berasal dari PR ini karena saya baru saja menguji RC.

Adakah orang lain yang mengalami masalah yang sama?

@schettino72 Berapa banyak data yang Anda masukkan?

Sekitar 30 ribu baris dengan 10 kolom. Tapi sebenarnya hampir semua yang saya coba lebih lambat (SQL lebih cepat tetapi secara keseluruhan lebih lambat). Itu menciptakan pernyataan SQL besar di mana ada interpolasi nilai untuk SETIAP nilai. Sesuatu seperti

 %(user_id_m32639)s, %(event_id_m32639)s, %(colx_m32639)s,

Saya menemukan d6tstack lebih mudah digunakan, ini adalah satu baris d6tstack.utils.pd_to_psql(df, cfg_uri_psql, 'benchmark', if_exists='replace') dan jauh lebih cepat daripada df.to_sql() . Mendukung postgres dan mysql. Lihat https://github.com/d6t/d6tstack/blob/master/examples-sql.ipynb

Saya telah menggunakan Solusi Patch Monyet:

from pandas.io.sql import SQLTable

def _execute_insert(self, conn, keys, data_iter):
    print "Using monkey-patched _execute_insert"
    data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
    conn.execute(self.insert_statement().values(data))

SQLTable._execute_insert = _execute_insert

untuk beberapa waktu sekarang, tetapi sekarang saya mendapatkan kesalahan:

TypeError: insert_statement() missing 2 required positional arguments: 'data' and 'conn'

Apakah ada orang lain yang mendapatkan ini? Saya menggunakan Python 3.6.5 (Anaconda) dan pandas==0.23.0

apakah ini sedang diperbaiki? Saat ini, df.to_sql sangat lambat dan tidak dapat digunakan sama sekali untuk banyak kasus penggunaan praktis. Proyek Odo tampaknya sudah ditinggalkan.
Saya telah mengikuti kasus penggunaan dalam deret waktu keuangan di mana df.to_sql hampir tidak dapat digunakan:
1) menyalin data csv historis ke database postgres - tidak dapat menggunakan df.to_sql dan harus menggunakan kode khusus di sekitar fungsionalitas copy_from psycopg2
2) streaming data (datang dalam batch ~500-3000 baris per detik) untuk dibuang ke database postgres - sekali lagi kinerja df.to_sql cukup mengecewakan karena terlalu banyak waktu untuk memasukkan kumpulan data alami ini ke postgres.
Satu-satunya tempat di mana saya menemukan df.to_sql berguna sekarang adalah membuat tabel secara otomatis !!! - yang bukan kasus penggunaan yang dirancang untuknya.
Saya tidak yakin apakah orang lain juga memiliki masalah yang sama tetapi masalah ini memerlukan perhatian agar antarmuka "dataframe-to-database" berfungsi dengan lancar.
Tidak sabar.

Hai, saya mendapatkan kesalahan ini ketika saya mencoba melakukan multi-insert ke database SQLite:

Ini kode saya:
df.to_sql("financial_data", con=conn, if_exists="append", index=False, method="multi")

dan saya mendapatkan kesalahan ini:

Traceback (most recent call last):

  File "<ipython-input-11-cf095145b980>", line 1, in <module>
    handler.insert_financial_data_from_df(data, "GOOG")

  File "C:\Users\user01\Documents\Code\FinancialHandler.py", line 110, in insert_financial_data_from_df
    df.to_sql("financial_data", con=conn, if_exists="append", index=False, method="multi")

  File "C:\Users\user01\AppData\Local\Continuum\anaconda3\lib\site-packages\pandas\core\generic.py", line 2531, in to_sql
    dtype=dtype, method=method)

  File "C:\Users\user01\AppData\Local\Continuum\anaconda3\lib\site-packages\pandas\io\sql.py", line 460, in to_sql
    chunksize=chunksize, dtype=dtype, method=method)

  File "C:\Users\user01\AppData\Local\Continuum\anaconda3\lib\site-packages\pandas\io\sql.py", line 1547, in to_sql
    table.insert(chunksize, method)

  File "C:\Users\user01\AppData\Local\Continuum\anaconda3\lib\site-packages\pandas\io\sql.py", line 686, in insert
    exec_insert(conn, keys, chunk_iter)

  File "C:\Users\user01\AppData\Local\Continuum\anaconda3\lib\site-packages\pandas\io\sql.py", line 609, in _execute_insert_multi
    conn.execute(self.table.insert(data))

TypeError: insert() takes exactly 2 arguments (1 given)

Mengapa ini terjadi? Saya menggunakan Python 3.7.3 (Anaconda), pandas 0.24.2 dan sqlite3 2.6.0.

Terima kasih banyak sebelumnya!

@jconstanzo dapatkah Anda membuka ini sebagai masalah baru?
Dan jika memungkinkan, dapatkah Anda mencoba memberikan contoh yang dapat direproduksi? (misalnya contoh kecil kerangka data yang dapat menunjukkan masalah)

@jconstanzo Memiliki masalah yang sama di sini. Menggunakan method='multi' (dalam kasus saya, dalam kombinasi dengan chunksize ) tampaknya memicu kesalahan ini ketika Anda mencoba memasukkan ke database SQLite.

Sayangnya saya tidak bisa memberikan contoh kerangka data karena kumpulan data saya sangat besar, itulah alasan saya menggunakan method dan chunksize sejak awal.

Saya minta maaf atas keterlambatannya. Saya baru saja membuka masalah untuk masalah ini: https://github.com/pandas-dev/pandas/issues/29921

Apakah halaman ini membantu?
0 / 5 - 0 peringkat