Django(正確にはMySQLdb)をつかってMySQLの巨大な結果を返すselect文を処理する

移転しました。

最近djangoでデータ件数が多いテーブルから

hoge = Hoge.objects.all()

のように取得しようとすると、本ケースのようにデータが多い場合メモリ不足で処理が落ちる事がある。

なんとか他の方法でデータ取得できないかと以下のようにこころみたが"killed"で同じように落ちてしまた。

from django.db import connection, transaction
cursor = connection.cursor()
cursor.execute("select * from hoge")

killed

知り合いの方がJavaで同じような現象にはまったことを聞いて、どうやって解決したかを聞いてみると
JDBCのドライバ設定をかえて1行1行とるようにしたそうな。
※具体的なパラメータ名までは聞かなかった。。。

「Pythonにもあるんじゃないの?」

というアドバイスを頂いたので、調べていると案の定あるではないか。

DjangoではDB接続時にMySQLを使用する場合にMySQLdbを使用しているのだが、
こいつのuse_resultメソッドを使えば解決できそうなことがわかった。

解決した

ここ「PythonをつかってMySQLの巨大な結果を返すselect文を処理する」のソースを参考に以下のようなソースを作成して実行したところ、処理がメモリ不足で落ちる事なく最後まで実行できた。

from django.conf import settings
from django.db import connection

from prj.app.models import Hoge

# MySQLdbのconnectionを取得
# 1.connection.connectionにMySQLdbのconnectionを入れるために実行 
connection.cursor() 
conn = connection.connection # MySQLdb.connection を取得
# ユーザーデータを1行1行処理していく
# 必ず使用する列のみの取得とすること!!!!
# (保存する列数分のメモリが消費されるため)
conn.query("select id, name from hoge")
# 2.use_resultつかってcursor?取得
cur = conn.use_result()
while(True):
    # row = cur.fetch_row(返す行数, 返す型(0:Tuple, 1:dict))
    # デフォは 返す行数:1, 返す型:Tuple
    rows = cur.fetch_row(1,1)
    if not rows: break
    print rows
1.connection.connectionにMySQLdbのconnectionを入れる

djangoのconnection.connectionにMySQLdbのconnectionオブジェクトが入るのだが一度DBアクセスしないとconnectionに値が入らない模様。このため connection.cursor() してオブジェクトとってくる。
※もっとよい方法ないだろうか?

2.use_resultつかってcursor?取得

そのごuse_result使って欲しいcursor取得してループしてあげるだけ。
返す型はタプルか辞書かの指定ができる。
詳しくはここ参照

気をつけよう

サーバー側のメモリを使うため、あまり同時にuse_result使うようなPGは動かさない方が良い事に気をつける。