2016-11-03 9 views
1

Python MySQLdbを使用して大きなテーブルからデータをフェッチするときに、非常に長い実行時間(長いフェッチ時間とは対照的に)があり、明らかに間違っているかどうかを知りたいと思います。Python MySQLdbが遅く実行する

次のように私のテーブルが定義されている:

create table mytable(
    a varchar(3), 
    b bigint, 
    c int, 
    d int, 
    e datetime, 
    f varchar(20), 
    g varchar(10), 
    primary key(a, b, c, d)) 
ENGINE=InnoDB; 

それが現在1.5億行が含まれており、テーブルサイズの見積もりは19ギガバイトです。 executeコマンドに費やす時間から来

import MySQLdb 
database = MySQLdb.connect(passwd="x", host="dbserver", user="user", db="database", port=9999) 
mysql_query = """select a, b, c, d, e, f, g from mytable use index (primary) where a = %s order by a, b, c, d""" 
mysql_cursor = database.cursor() 
mysql_cursor.execute(mysql_query, ["AA"]) 
for a, b, c, d, e, f, g in mysql_cursor: 
    #Do something 

私の驚きを次のように

ザ・Pythonのコードがあります。 executeは、ほとんどの時間を費やすことはない(主キーを使用してテーブルを移動する必要があるため)、そしてforループでかなりの時間を費やしているとは思うが、ここでは古くからの歳月を費やしている。

実行計画は次のとおりです。

現時点で
explain select a, b, c, d, e, f, g from mytable use index (primary) where a = %s order by a, b, c, d 
'1','SIMPLE','eventindex','ref','PRIMARY','PRIMARY','5','const','87402369','Using where' 

は、すべての行が(私は後で他の値を追加するために念頭に置いていた列に同じ値が含まれていますが、現時点列の分布で、コンテンツは実際にはバランスが取れていません)。列bがより良く分散されています

MySQLはクエリの実行に時間を費やしています(行をフェッチする時間を費やすのではなく)と説明できますか?

ボーナスの質問です。このユースケースを最適化するための明白な迅速な勝利?カラムbのテーブルを分割する?列a?列aを削除し、代わりに別のテーブルを使用しますか?

答えて

0

実際にはMySQLの質問によく似ていますが、問題はPythonやmysql-pythonとは関係ありません。

WRT/SQLのもの:十分な選択ではありませんインデックス(あまりにも多くの同様の値を持っている)を使用すると、インデックスツリートラバーサルに加えでシーケンシャルスキャンをやってしまうので、非常に有害である可能性が - よりも、実際にはより多くのディスクアクセス単純なテーブルスキャンでは、両側が緩くなります(IOW:インデックスツリーのオーバーヘッドだけがトラバースされますが、メリットはありません)。あなたはuse index句を使用せずにクエリをしようとし、おそらく代わりにignore index clauseを使用して、プレーンバイパスにインデックスをオプティマイザに強制することもできますあなたの場合はMySQL: low cardinality/selectivity columns = how to index?とここRole of selectivity in index scan/seek

:あなたはここにこの詳細を見つけることができます。

0

これを見ると、これはMySQLの通常の動作です。さまざまなソースから、MySQLの実行フェーズではほとんどの選択作業が行われているように見えますが、フェッチ中はネットワーク転送のみが行われます。私は、Oracleが(実際にはほとんど何も実行せず、処理の肉はフェッチ時に行われますが)MySQLに多大な時間を費やしていました。

コンテキストによっては、アイテムを介してレイテンシなしで反復できる回避策は、ページングシステムを実装することです。これは、Pythonジェネレータに小さなフェッチをカプセル化することで実現できます。一方、コール間でデータの一貫性が失われていますが、私の場合はこれが受け入れられます。このアプローチに興味のある人のための基礎があります。次のページを取得するために必要なアダプテーションは、SQLクエリをどうにかして 複合語 にすると、あなたのコードをあなたの主キー構造にバインドすることができるので、おそらくあなたは前に長所と短所を比較する必要がありますこれのために行く。一つの良いニュースは、この複雑さが発電機の後ろに隠れることができるということです。 MySQLの上

import MySQLdb 
database = MySQLdb.connect(passwd="x", host="dbserver", user="user", db="database", port=9999) 

def get_next_item(database): #Definition of this generator encapsulating the paging system 
    first_call = True 
    mysql_cursor = database.cursor() 
    nothing_more_found = False 
    while not nothing_more_found: 
     mysql_query = """select a, b, c, d, e, f, g from mytable use index (primary) 
      where a = %s order by a, b, c, d 
      limit 10000""" if first_call else """select a, b, c, d, e, f, g from mytable use index (primary) 
      where a = %s and ((b > %s) or (b = %s and c > %s) or (b = %s and c = %s and d > %s)) 
      order by a, b, c, d 
      limit 10000""" 

     if first_call: 
      mysql_cursor.execute(mysql_query, ["AA", last_b, last_b, last_c, last_b, last_c, last_d]) 
      first_call = False 
     else: 
      mysql_cursor.execute(mysql_query, ["AA"]) 
     if mysql_cursor.rowcount == 0: 
      nothing_more_found = True 
     for a, b, c, d, e, f, g in mysql_cursor: 
      yield (a, b, c, d, e, f, g) 
      last_b, last_c, last_d = b, c, d 

for a, b, c, d, e, f, g in get_next_item(database): #Usage of the generator 
    #Do something 

説明実行対マイクLischkeからこのpostにフェッチ。

フェッチ時間は純粋にそれがクエリを実行するとは全く関係ありません結果、 を転送するためにかかった測定します。フェッチの は、クエリを実行するたびに変わる可能性があります。 ネットワーク接続がクエリの良否を判断するのはなぜですか? [OK]を使用して が実際に存在します。クエリで返されるデータが多すぎると、転送にはより多くの時間がかかります 。しかし、時には の結果がキャッシュされるので、これも完全に真実ではありませんので、それらをより速く送ることができます。

一方、Oracleでは、選択時に、ほとんどの処理がフェッチ中に行われます。かなりよく定義されたが、それはのprepareStatementです - - 私たちは ソフトやハード解析を行い、把握、ステートメントをコンパイルこれは、解析トム・カイト自身それのhere

と思うこのよう

1)によって説明されます実行方法 です。

2)実行 - 文を開きます。更新のために、削除のために、 の挿入 - つまり、それを開くときに文を開くと、 が実行されます。すべての作業がここで行われます。

選択するともっと複雑です。ほとんどの選択肢は、 の実行中にZEROの作業を行います。私たちがやっていることは、カーソルを開くことです。カーソルは、 プランがある共有プール内のスペースへのポインタ、バインド 変数値、 クエリの時刻の現状を表すSCNです。この時点でのカーソルはあなたの文脈であり、 仮想マシンの状態は、バイトコードであるかのようにSQL計画を考えると (それ)は仮想マシン(それ)のプログラムとして実行されます。 カーソルは命令ポインタです(この命令の実行 )、状態(レジスタのようなもの)などです。通常、 セレクトはここでは何も行いません。「ロックンロールする準備ができました。 プログラムはまだ準備ができていますが、実際には始まっていません。

しかし、すべてに例外があります。トレースをオンにし、 select * from scott.emp FOR UPDATEを実行します。それは選択ですが、また アップデートです。実行時と フェッチフェーズで作業が完了したことがわかります。実行中に行われた作業は、 に出て、すべての行に触れ、それをロックする作業でした。 フェーズの実行中に行われた作業は、外に出て、 クライアントにデータを戻すことでした。

3)フェッチ - あなたは 更新からフェッチしていないとして、我々は、ほぼすべて選択 ための作業(および他のDMLSのために本当に何もないのを見る場所です)。

SELECTを処理するには2通りの方法があります。私は

http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:275215756923#39255764276301

「クイック リターンクエリ」と「スローリターンクエリ」と呼ぶこと 深さでこれを記述した設計により、効果的なオラクルからの抜粋ですが、フォームのクエリを言えば十分。

select * from one_billion_row_table;

は、データをどこにもコピーしないため、最初の行を返す前に最後の 行にアクセスする必要はありません。 が存在するブロックからデータを取得するだけで、データを読み取ることができます。

しかし、フォームのクエリ:

がunindexed_columnによってone_billion_row_table順から選択*; (最後の行の読み取りがうまく返された最初の行 可能性があるため!)私たちは、おそらく 最初の行を返す前に、最後の行を読まなければならないだろうと私たちはどこか(一時、ソート面積ことをコピーする必要があると思い

スペース)を使用してください。最初のクエリの場合

、あなたの場合:

がそれを解析された(少し作業の解析)(ちょうど が準備、本当の世界を)それを開いていないが1行をフェッチし、あなたが希望それを

を閉じましたフェッチフェーズで実行された非常に小さな作業を参照してください。 はおそらく最初のレコードを返すために1つのブロックを読み取る必要があります。

しかし、2番目のクエリに対して同じ手順を実行してください。 最初の行が返される前に最後の行 を見つけなければならないため、1行のフェッチにはTONが発生します。

関連する問題