今川館

都内勤務の地味OLです

MySQLdb カーソルをN件ずつフェッチする

↓ここを参考。
Django(正確にはMySQLdb)をつかってMySQLの巨大な結果を返すselect文を処理する
http://d.hatena.ne.jp/shohu33/20091122

↓こちらも。
http://www.ueblog.org/blog/entry/pythonmysqldb/


MySQLdbで大量のレコードをSELECTする場合、
cursor.execute & cursor.fetchoneを使うと処理が著しく遅くなる。
フェッチする件数が多いとmysqlが落ちたり、タイムアウトしてしまう。
※cursor.fetchallをすると巨大なリストをメモリに展開する問題とは違う。

どうも、

  1. SQL文をデータベースエンジンに処理させる。
  2. データベースエンジンが内部のカーソルを生成する。
  3. 呼び出しプログラム(Python)側がカーソルをフェッチする。

↑このうち、2に失敗しているようだ。理由はよくわからない。

DBが内部にカーソルを展開する時点で失敗するので始末が悪いのだが、
connection.query & connection.use_result() を使うとこの問題を解決できる。

for-inループで使いたかったので、__iter__を持つクラスを作ってみた。

# -*- coding: utf-8 -*-

import MySQLdb

def get_connection(connect_info):
    ''' 単純にMySQLのコネクションを作って返します。 '''
    return MySQLdb.connect(**connect_info)

class low_level_cursor(object):
    def __init__(self, connection, rows=1, value_type=1):
        self.connection = connection
        self.rows = rows
        self.value_type = value_type
        self.rs = None
        self.row = None
    
    def query(self, sql):
        self.connection.query(sql)
        self.rs = self.connection.use_result()
    
    def has_next(self):
        self.row = self.rs.fetch_row(self.rows, self.value_type)
        return bool(self.row)
    
    def next(self):
        return self.row[0] if self.rows == 1 else self.row
    
    def __iter__(self):
        while(self.has_next()):
            yield self.next()


foo = {
    'db' : "imagawa",
    'host' : "127.0.0.1",
    'port' : 3306,
    'user' : "user",
    'passwd' : "password"
}

connection = get_connection(foo)
cursor = low_level_cursor(connection)
cursor.query('select id, name, email from huge_data')
for i in cursor:
  # do something...

以下、参考。
http://mysql-python.sourceforge.net/MySQLdb.html#using-and-extending