Pandas: 待ち時間の長い接続でto_sqlを大幅に高速化するには、複数行の挿入を使用します

作成日 2014年12月01日  ·  48コメント  ·  ソース: pandas-dev/pandas

pandas-0.15.1、oursql-0.9.3.1、sqlalchemy-0.9.4を使用して、mysqlデータベースに最大30k行を挿入しようとしています。 マシンは私から大西洋を越えているので、 data.to_sqlを呼び出すと、データを挿入するのに1時間以上かかりました。 Wiresharkで検査すると、問題は、すべての行に挿入を送信し、次の行を送信する前にACKを待機することです。簡単に言うと、pingの時間が私を殺してしまいます。

ただし、 SQLAlchemyの指示に従って、変更しました

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)

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

そして、操作全体が1分以内に完了します。 (クリックを節約するために、 insert into foo (columns) values (rowX)への複数の呼び出しと1つの大規模なinsert into foo (columns) VALUES (row1), (row2), row3)の違いがあります)。 人々がパンダを使用して大量のデータを挿入する可能性が高いことを考えると、これは、より広く含まれることが素晴らしいと思われる大きな勝利のように感じます。

いくつかの課題:

  • すべてのデータベースが複数行の挿入をサポートしているわけではありません(SQLiteとSQLServerは、現在はサポートしていますが、以前はサポートしていませんでした)。 SQLAlchemyでこれを確認する方法がわかりません
  • 私が使用していたMySQLサーバーでは、データを一度に挿入することができず、チャンクサイズを設定する必要がありました(5kは正常に機能しましたが、30k全体では多すぎたと思います)。 これをデフォルトの挿入にした場合、ほとんどの人はチャンクサイズを追加する必要があります(サーバーの最大パケットサイズによって決定される可能性があるため、計算が難しい場合があります)。

これを行う最も簡単な方法は、 multirow=ブールパラメータ(デフォルトFalse )をto_sql関数に追加し、ユーザーにチャンクサイズの設定を任せることです。しかし、おそらくもっと良い方法がありますか?

考え?

IO SQL Performance

最も参考になるコメント

モンキーパッチを適用する方法を理解しました-他の誰かに役立つかもしれません。 パンダをインポートする前にこのコードを用意してください。

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

全てのコメント48件

これは合理的なようです。 これを調査してくれてありがとう!

実装については、sqlalchemyがこれをサポートしていないデータベースフレーバーをどのように処理するかによって異なります(現時点ではこれをテストできませんが、sqlalchemyでエラーが発生するようです(例:http://stackoverflow.com/questions/ 23886764 / multiple-insert-statements-in-mssql-with-sqlalchemy)また、多くの人がチャンクサイズを設定しなければならないという結果になる場合、これをデフォルトとして設定することは実際には良い考えではありません(設定しない限り)チャンクサイズをデフォルトの値に設定します)。
したがって、キーワードを追加する方が良いようです。

@artemyk @mangecoeur @hayd @danielballan

どうやらSQLAlchemyにはフラグdialect.supports_multivalues_insertがあります(たとえばhttp://pydoc.net/Python/SQLAlchemy/0.8.3/sqlalchemy.sql.compiler/を参照してください。他のバージョンではsupports_multirow_insertと呼ばれることもあります。https ://www.mail-archive.com/[email protected]/msg202880.html)。

これは挿入を大幅に高速化する可能性があり、サポートを簡単に確認できるため、デフォルトでそれを実行でき、チャンクサイズをデフォルト値に設定できると思います(例:16kbチャンク...何がわからない)ほとんどの状況で大きすぎます)。 複数行の挿入が失敗した場合、チャンクサイズを下げることを提案する例外をスローできますか?

ここで、SQLAlchemyの人々を説得して、SQL Server> 2005でsupports_multivalues_insertをtrueに設定する必要があります(コードにハッキングして正常に動作しますが、デフォルトではオンになっていません)。

もっと話題になっているのは、チャンクサイズには注意が必要だと思います。 私のmysqlセットアップ(おそらく大きなパケットを許可するように構成した)では、chunksize = 5000を設定できます。SQLServerセットアップでは、500は大きすぎましたが、100は正常に機能しました。 ただし、この手法の利点のほとんどは、100から1000ではなく、一度に1行を100に挿入することから得られることはおそらく事実です。

chunksize=Noneが「チャンクサイズを適切に選択する」ことを意味する場合はどうなりますか? 5000、500、50、1のようなものを試してください。ユーザーはチャンクサイズを指定することでこれをオフにすることができます。 これらの試行によるオーバーヘッドが大きすぎる場合は、 @ maxgrenderjonesの提案が好きです。 chunksize=10chunksize=1よりも適切なデフォルトです。

その最後のコメントで「 chunksize=10chunksize=1よりも良いデフォルトです」->それは完全には真実ではないと思います。 現在の状況は、複数行の単一行挿入ステートメント(1のチャンクサイズではない)で構成される_one_実行ステートメントを実行することですが、 chunksize=10は、1つの複数行ごとに多数の実行ステートメントを実行することを意味します入れる。
そして、これが必ずしも速いかどうかはわかりませんが、状況によって大きく異なります。 たとえば、現在のコードとローカルのsqliteデータベースでは次のようになります。

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

しかしもちろん、これは複数行機能を使用しません

モンキーパッチを適用する方法を理解しました-他の誰かに役立つかもしれません。 パンダをインポートする前にこのコードを用意してください。

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

たぶん、新しいmultirow=Trueキーワード(今のところデフォルトはFalse)を使用してこの機能を追加することから始めて、後でデフォルトで有効にできるかどうかをいつでも確認できますか?

@maxgrenderjones @nhockhamこれを追加するためのPRを行うことに興味がありますか?

@jorisvandenbossche特定のパフォーマンスプロファイルに対処するためにキーワード引数を追加し始めるのは危険だと思います。 すべての場合に高速であることを保証できる場合(必要に応じて、入力に基づいて最適な方法を決定することにより)、フラグはまったく必要ありません。

DBセットアップが異なれば、パフォーマンスの最適化も異なる可能性があります(DBパフォーマンスプロファイル、ローカルとネットワーク、大容量メモリと高速SSDなど)。それぞれにキーワードフラグを追加し始めると、混乱します。

SQLDatabaseとSQLTableのサブクラスを作成して、パフォーマンス固有の実装に対応することをお勧めします。これらは、オブジェクト指向APIを介して使用されます。 おそらく「バックエンドスイッチング」メソッドを追加することもできますが、率直に言ってOO apiを使用するのは非常に簡単なので、これはすでに特殊なユースケースではおそらくやり過ぎです。

大きなデータセットをPostgresにロードするためにこのようなサブクラスを作成しました(実際には、データをCSVに保存してから、組み込みの非標準のCOPY FROMsqlコマンドを使用する方が挿入を使用するよりもはるかに高速です。https://gist.githubを参照してください。 com / mangecoeur / 1fbd63d4758c2ba0c470#file-pandas_postgres-py)。 それを使用するには、 PgSQLDatabase(engine, <args>).to_sql(frame, name,<kwargs>)を実行するだけです

参考までに、複数行機能を使用して@jorisvandenbossche (12月3日の投稿)でコードを実行してみました。 かなり遅いです。 したがって、ここでの速度のトレードオフは簡単ではありません。

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

また、キーワードパラメータを追加するのは危険であることに同意します。 ただし、複数行の機能はかなり基本的なようです。 また、「monkey-patching」は、おそらくキーワードパラメータよりもAPIの変更に対して堅牢ではありません。

それは私が思った通りです。 モンキーパッチは私が提案した解決策ではありません-むしろ、情報に通じたユーザーがOOインターフェイスを介して使用できるパフォーマンス指向のサブクラスをいくつか出荷します(オプションが多すぎる機能APIのロードを回避するため)

- - -オリジナルメッセージ - - -
差出人:「ArtemyKolchinsky」 [email protected]
送信済み:26/02/2015 17:13
宛先: "pydata / pandas" [email protected]
Cc:「mangecoeur」ジョン。 [email protected]
件名:Re:[pandas] to_sqloverの高遅延接続を大幅に高速化するために複数行の挿入を使用する(#8953)

参考までに、複数行機能を使用して@jorisvandenbossche (12月3日の投稿)でコードを実行してみました。 かなり遅いです。 したがって、ここでの速度のトレードオフは簡単ではありません。
[4]の場合:engine = create_engine('sqlite:///:memory:')#、echo ='debug')

[5]の場合:df = pd.DataFrame(np.random.randn(50000、10))

[6]:

[6]の場合:%timeit df.to_sql('test_default'、engine、if_exists ='replace')
1ループ、ベスト3:ループあたり1.05秒

[7]:

[7]の場合:pandas.io.sqlからSQLTableをインポートします

[8]:

[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))
...:

[9]の場合:SQLTable._execute_insert = _execute_insert

[10]:

[10]の場合:reload(pd)
Out [10]:

[11]:

[11]の場合:%timeit df.to_sql('test_default'、engine、if_exists ='replace'、chunksize = 10)
1ループ、ベスト3:ループあたり9.9秒
また、キーワードパラメータを追加するのは危険であることに同意します。 ただし、複数行の機能はかなり基本的なようです。 また、「monkey-patching」は、おそらくキーワードパラメータよりもAPIの変更に対して堅牢ではありません。

このメールに直接返信するか、GitHubで表示してください。

最初のチケットタイトルによると、このアプローチがすべての場合に好ましいとは思わないので、デフォルトにはしません。 ただし、パンダがないとto_sql使用できなくなるため、変更をリクエストし続けることが重要です。 (パンダのバージョンをアップグレードするときに最初に変更するものにもなります)。 賢明なchunksizeの値については、真のnは1つではないと思います。パケットのサイズは、列の数(および列の内容)に依存し、予測が難しいためです。 。 残念ながら、 chunksizeを高く設定しすぎると、SQLServerは完全に無関係に見える(ただしそうではない)エラーメッセージで失敗します(これが、SQLAlchemyのパッチを除いて複数行の挿入がオンにならない理由です)が、 mysqlで正常に動作します。 ユーザーは、 nの値が、許容できるほど大きなパケットサイズになる可能性があるかを判断するために実験する必要がある場合があります(バッキングデータベースが何であれ)。 パンダにnを選択させると、実装の詳細において、私たちが望んでいるよりもはるかに下に着地する可能性があります(つまり、最大可能な抽象化SQLALchemyアプローチとは反対の方向)

要するに、私の推奨事項は、それをキーワードとして追加し、それを使用する方法についていくつかの役立つ解説を付けることです。 実装を選択するためにキーワードが使用されたのはこれが初めてではありませんが(http://pandas.pydata.org/pandas-docs/dev/generated/pandas.DataFrame.apply.htmlを参照)、おそらくそうではありません。説明を読んだとしても、 raw=が何を意味するのかについて最初の考えがないので、最良の例です。

また、大量のメモリを消費することに気づきました。 約700,000行と301列の1.6GB以上のDataFrameのように、挿入時にほぼ34GBが必要です。 それは上から非効率的なようなものです。 なぜそうなるのかについてのアイデアはありますか? これがスクリーンクリップです:

image

こんにちは、みんな、
この問題に関する進展はありますか?

to_sqlを使用して約200K行を挿入しようとしていますが、これには永遠に時間がかかり、大量のメモリを消費します。 chuncksizeを使用するとメモリに役立ちますが、それでも速度は非常に遅くなります。

私の印象では、MSSQL DBaseトレースを見ると、挿入は実際には一度に1行ずつ実行されます。

現在実行可能な唯一のアプローチは、共有フォルダーのcsvファイルにダンプし、BULKINSERTを使用することです。 しかし、それは非常に迷惑でエレガントではありません!

@andreacassioli odo使用して、中間CSVファイルを介してDataFrameをSQLデータベースに挿入できます。 CSVのSQLデータベースへのロードを参照してください。

ODBCを使用してBULK INSERTのパフォーマンスに近づくことさえできないと思います。

@ostrokachありがとうございます。確かに私は現在csvファイルを使用しています。 私が近づくことができれば、私は単純さのために少し時間を交換するでしょう!

私はこれが誰かを助けるかもしれないと思った:
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はORMを使用せず、sqlalchemy Coreのみを使用します(これは、大きな挿入に使用することを提案しているドキュメントエントリです)

それまでの間、これを回避する方法についてのコンセンサスはありますか? 私はpostgresに数百万行を挿入していますが、それは永遠にかかります。 CSV / odoは進むべき道ですか?

@russlambこの問題を解決する実用的な方法は、単に一括アップロードすることです。 これはデータベース固有のものなので、 odoにはpostgreslのソリューションがあります( mysqlの場合もあります)。 sqlserverのようなものの場合、「これを自分で行う」必要があります(IOWを作成する必要があります)。

sqlserverの場合、SQLAlchemyエンティティでFreeTDSドライバー(http://www.freetds.org/software.htmlおよびhttps://github.com/mkleehammer/pyodbc)を使用したため、挿入が非常に高速になりました(データフレームあたり20K行)。 :

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)

CSV / odoは進むべき道ですか?

このソリューションは、複数行/チャンクサイズの設定に関係なく、ほとんどの場合、より高速になると思います。

しかし、 @ russlamb 、そのような複数行のキーワードがあなたの場合の改善になるかどうかを聞くことは常に興味深いことです。 これを簡単にテストする方法については、たとえばhttps://github.com/pandas-dev/pandas/issues/8953#issuecomment-76139975を参照してください。

これを(必ずしもデフォルトを変更せずに)指定する方法が必要であるという合意があると思います。 したがって、誰かがこれのPRを作成したい場合、それは確かに歓迎されます。
この機能を追加する方法については、いくつかの議論しかありませんでした(新しいキーワードとOO apiを使用したサブクラス)。

@jorisvandenbossche上記でリンクしたドキュメントには、次のように記載されています。ベースの自動化。」

私が提案しているのは、 to_sqlのsqlserver固有のバージョンを実装することです。これは、上記のコードのように、スピードアップのためにSQLAlchemyORMを使用します。

これは以前に提案されました。 あなたが行く方法は、パンダのSQLを実装することです
バックエンド用に最適化されたクラス。 過去に使用の要点を投稿しました
はるかに高速なpostgresCOPYFROMコマンド。 しかし似たようなもの
odoで利用可能になり、より堅牢な方法で構築されました。 あまりありません
odoからの複製作業でIMHOを指摘します。

2017年3月7日00:53、「AndreiSura」 [email protected]は次のように書いています。

@jorisvandenbosschehttps ://github.com/jorisvandenbosscheドキュメント
私は上記の言及をリンクしました「あるいは、SQLAlchemyORMはバルクを提供します
メソッドのオペレーションスイート。これは、のサブセクションへのフックを提供します。
コアレベルのINSERTおよびUPDATEを発行するための作業プロセスの単位
わずかなORMベースの自動化を備えたコンストラクト。」

私が提案しているのは、SQLServer固有のバージョンを実装することです
内部的にはSQLAlchemyコアを使用してスピードアップする「to_sql」。


あなたが言及されたので、あなたはこれを受け取っています。
このメールに直接返信し、GitHubで表示してください
https://github.com/pandas-dev/pandas/issues/8953#issuecomment-284437587
またはスレッドをミュートします
https://github.com/notifications/unsubscribe-auth/AAtYVDXKLuTlsh9ycpMQvU5C0hs_RxuYks5rjCwBgaJpZM4DCjLh

また、代わりにsqlalchemyがコアになる可能性があるとおっしゃっていたことにも気づきました。 何かがなければ
大きく変更されましたが、どのような場合でもsqlalchemyコアのみが使用され、ormは使用されません。 もし、あんたが
コアを使用するよりも高速化したい場合は、下位レベルに移動する必要があります、db
特定の最適化

2017年3月7日00:53、「AndreiSura」 [email protected]は次のように書いています。

@jorisvandenbosschehttps ://github.com/jorisvandenbosscheドキュメント
私は上記の言及をリンクしました「あるいは、SQLAlchemyORMはバルクを提供します
メソッドのオペレーションスイート。これは、のサブセクションへのフックを提供します。
コアレベルのINSERTおよびUPDATEを発行するための作業プロセスの単位
わずかなORMベースの自動化を備えたコンストラクト。」

私が提案しているのは、SQLServer固有のバージョンを実装することです
内部的にはSQLAlchemyコアを使用してスピードアップする「to_sql」。


あなたが言及されたので、あなたはこれを受け取っています。
このメールに直接返信し、GitHubで表示してください
https://github.com/pandas-dev/pandas/issues/8953#issuecomment-284437587
またはスレッドをミュートします
https://github.com/notifications/unsubscribe-auth/AAtYVDXKLuTlsh9ycpMQvU5C0hs_RxuYks5rjCwBgaJpZM4DCjLh

これは修正/処理されていますか? 現在のところ、パンダのデータフレームをSQLデータベースに挿入するのは、おもちゃのデータフレームでない限り非常に遅くなります。 解決策を決めて前進させましょう。

@dfernan上記のように、あなたはodoを見たいと思うかもしれません。 中間のCSVファイルを使用すると、ここでどのような改善が行われたとしても、sqlalchemyを使用するよりも常に桁違いに高速になります...

@ostrokach 、私はodoの振る舞いが典型的なPandasユーザーが望んでいるものであるとは確信していません。 ODBCを介した複数行の挿入は、ほとんどのアナリストにとっておそらく十分に高速です。

自分で言うと、上のモンキーパッチからオドに切り替えるのに数時間かかりました。 プレーンパンダの実行時間は10時間以上、RBARでした。 モンキーパッチは、同じデータセットで2で実行されます。
odo / CSVルートは予想どおり高速でしたが、努力する価値があるほどではありませんでした。 モンキーパッチを回避するという名目で、あまり気にしないCSV変換の問題をいじりました。 NLP分析のために、約10個のmysqlおよびPGDBからPostgresの共通領域に25万行をインポートしています。

私は、バルクローディングアプローチのodoespousesに精通しています。 私はCSVデータから始めて何年もそれらを使用してきました。 それらには重要な制限があります。

  1. df-> CSV-> Postgresの場合、PGホストでCSVを取得するには、シェルアクセスとscpステップが必要です。 @mangecoeurは、STDINへのストリームでこれを回避したようです。
  2. 私の目的(25万行のコメント、テキストコンテンツに多くの特殊なケースがある)のために、CSVパラメーターを正しく取得するのに苦労しました。 私はこれに投資し続けるのに十分なほどパフォーマンスが向上することを望んでいませんでした。

パッチに戻ったので、分析作業に取り掛かることができました。

@ jorisvandenbossche 、@maxgrenderjonesに同意します。 これを選択するオプション(デフォルトではない)は非常に便利です。 @artemykのdialect.supports_multivalues_insertに関する指摘は、これを妥当なデフォルトにする可能性さえあります。

それがこれを前進させるならば、私はPRを提出することをうれしく思います。

odoの経験を追加するだけで、エンコーディングに関する既知の問題のため、MSSqlの一括挿入では機能しませんでした。 imho m-rowインサートは、ほとんどのpplにとって実用的なソリューションです。

@markschwarzこれをより速く動作させるためのオプションは大歓迎です!

sqliteを使用してクエリをトレースすると、 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'))

(モンキーパッチなし、つまり)

興味深いことに、モンキーパッチでは、チャンクサイズを10 ^ 5にすると壊れますが、10^3にはなりません。 エラーは、sqliteの「SQL変数が多すぎます」です。

@makmanalp 、私はまだその動作をチェックするためにPGをトレースしていませんが、ほとんどの場合、挿入時にチャンクサイズを設定します。 上記の例では、200〜5000の5つの値にランダムに設定しました。 モンキーパッチがなければ、これらの選択肢の間に劇的な経過時間の違いは見られませんでした。 パッチを使用すると、経過時間は約80%減少しました。

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

このモンキーパッチはまだ機能していますか? MS SQL Serverで試しましたが、改善は見られませんでした。 また、例外をスローします。

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

@hangyaoパッチは実装固有のものだと思います。これは、PythonDBAPIが処理方法についてDBAPIドライバーに任せるものの1つです。 したがって、高速であるかどうかはわかりません。 RE:構文エラーです。よくわかりません。

521行目の新しいIFクロース内の_engine_builder関数内の507行目の/io/sql.pyファイルに以下の関数を追加して、「新しい」 _engine_builderを以下にすることを考えています。スニペット。 私は自分の環境で簡単にテストしましたが、MSSQLデータベースでうまく機能し、100倍以上の高速化を実現しています。 他のデータベースではまだテストしていません。

PRを作るのをやめたのは、以下のように挿入するよりも、きちんと安全にする方が努力だと思うのですが、これは必ずしも望ましい仕様ではなく、この設定をオン/オフにするブールスイッチを追加することもできます。 、(たとえば、 to_sqlfast_executemany=True )は、質問せずに行うには少し大きすぎる努力のように思えたと思います。

だから私の質問は:

  • 以下の関数は機能し、PostgreSQLのINSERT速度も向上しますか?

  • パンダのイベントでは、ソースにこのスニペットが必要ですか? もしそうなら:

  • この関数をデフォルトのsql.py機能に追加することが望まれますか、それともこれを追加するためのより良い場所がありますか?

いくつかのコメントを聞くのが大好きです。

回答のソース: 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最近、これに関連してマージされたPRがあります: https://github.com/pandas-dev/pandas/pull/19664。 私はまだあなたの投稿を詳しく調べていません、そしてそれは確かに完全に同じではありません(それはsqlalchemyエンジンのsupports_multivalues_insert属性を使用します)が、これがすでに役立つ場合に備えてあなたがそれを知っていることを確認するためだけです同じように。

それは良い知らせだ! 私はPRを調べていませんが、今週末にそれを比較して、結果に戻ります。 ヘッドアップをありがとう。

0.23.0 RC2(postgresql上)を試していたところ、パフォーマンスを向上させる代わりに、スクリプトの速度が大幅に低下しました。 DBクエリははるかに高速になりましたが、 to_sql()の時間を測定すると、実際には最大1.5倍遅くなりました(7〜11秒など)...

RCをテストしたばかりなので、このPRによる速度低下が原因かどうかはわかりません。

他の誰かが同じ問題を経験しましたか?

@ schettino72どのくらいのデータを挿入しましたか?

10列で約30K行。 しかし、実際に私が試しているほとんどすべてのものは低速です(SQLは高速ですが、全体的に低速です)。 これは、すべての値に対して値の補間が行われる巨大なSQLステートメントを作成しています。 何かのようなもの

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

d6tstackの方がはるかに使いやすく、ワンライナーd6tstack.utils.pd_to_psql(df, cfg_uri_psql, 'benchmark', if_exists='replace')であり、 df.to_sql()よりもはるかに高速です。 postgresとmysqlをサポートします。 https://github.com/d6t/d6tstack/blob/master/examples-sql.ipynbを参照してください

私はモンキーパッチソリューションを使用しています:

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

しばらくの間、しかし今、私はエラーを受け取っています:

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

他の誰かがこれを手に入れていますか? 私はPython3.6.5(Anaconda)とpandas==0.23.0を使用しています

これは修正されていますか? 現在、df.to_sqlは非常に低速であり、多くの実際のユースケースではまったく使用できません。 Odoプロジェクトはすでに放棄されているようです。
df.to_sqlがほとんど使用できない金融時系列の次のユースケースがあります。
1)履歴csvデータをpostgresデータベースにコピーする-df.to_sqlを使用できず、psycopg2copy_from機能の周りにカスタムコードを使用する必要がありました
2)postgresデータベースにダンプされるストリーミングデータ(毎秒約500〜3000行のバッチで送信)-これらの自然なデータバッチをpostgresに挿入するのに時間がかかりすぎるため、df.to_sqlのパフォーマンスはかなり期待外れです。
df.to_sqlが便利だと思うのは、テーブルを自動的に作成することだけです。 -これは、設計されたユースケースではありません。
他の人も同じ懸念を共有しているかどうかはわかりませんが、この問題は、「データフレームからデータベースへ」のインターフェイスがスムーズに機能するために注意が必要です。
楽しみにする。

ねえ、SQLiteデータベースに複数挿入を実行しようとすると、このエラーが発生します。

これは私のコードです:
df.to_sql("financial_data", con=conn, if_exists="append", index=False, method="multi")

そして私はこのエラーを受け取ります:

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)

なぜこうなった? Python 3.7.3(Anaconda)、pandas 0.24.2、sqlite32.6.0を使用しています。

事前にどうもありがとうございました!

@jconstanzoこれを新しい問題として開くことができますか?
そして、可能であれば、再現可能な例を提供してみてください。 (たとえば、問題を示すことができる小さなサンプルデータフレーム)

@jconstanzoここでも同じ問題があります。 method='multi' (私の場合はchunksizeと組み合わせて)を使用すると、SQLiteデータベースに挿入しようとしたときにこのエラーが発生するようです。

残念ながら、私のデータセットは巨大であるため、サンプルのデータフレームを実際に提供することはできません。それが、最初にmethodchunksizeを使用している理由です。

遅れてすみません。 この問題の問題を開いたところです: https ://github.com/pandas-dev/pandas/issues/29921

このページは役に立ちましたか?
0 / 5 - 0 評価