Как получить построчный MySQL ResultSet в python - PullRequest
47 голосов
/ 03 декабря 2008

MySQL ResultSets по умолчанию полностью извлекается с сервера, прежде чем можно будет выполнить какую-либо работу. В случае огромных наборов результатов это становится непригодным для использования. Вместо этого я бы хотел получить строки по очереди с сервера.

В Java, следуя инструкциям здесь (в разделе "ResultSet"), я создаю следующее утверждение:

stmt = conn.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY,
              java.sql.ResultSet.CONCUR_READ_ONLY);
stmt.setFetchSize(Integer.MIN_VALUE);

Это хорошо работает в Java. Мой вопрос: есть ли способ сделать то же самое в Python?

Я пытался ограничить запрос до 1000 строк за раз, например:

start_row = 0
while True:
    cursor = conn.cursor()
    cursor.execute("SELECT item FROM items LIMIT %d,1000" % start_row)
    rows = cursor.fetchall()
    if not rows:
        break
    start_row += 1000
    # Do something with rows...

Тем не менее, это выглядит медленнее, чем выше start_row.

И нет, использование fetchone() вместо fetchall() ничего не меняет.

Пояснение:

Наивный код, который я использую для воспроизведения этой проблемы, выглядит следующим образом:

import MySQLdb

conn = MySQLdb.connect(user="user", passwd="password", db="mydb")
cur = conn.cursor()
print "Executing query"
cur.execute("SELECT * FROM bigtable");

print "Starting loop"
row = cur.fetchone()
while row is not None:
    print ", ".join([str(c) for c in row])
    row = cur.fetchone()

cur.close()
conn.close()

В таблице ~ 700 000 строк этот код выполняется быстро. Но в таблице ~ 9 000 000 строк она печатает «Выполнение запроса», а затем зависает надолго. Вот почему не имеет значения, если я использую fetchone() или fetchall().

Ответы [ 5 ]

50 голосов
/ 03 декабря 2008

Я думаю, вам нужно подключиться, передавая cursorclass = MySQLdb.cursors.SSCursor:

 MySQLdb.connect(user="user", 
                 passwd="password",
                 db="mydb",
                 cursorclass = MySQLdb.cursors.SSCursor
                )

Курсор по умолчанию извлекает все данные одновременно, даже если вы не используете fetchall.

Редактировать: SSCursor или любой другой класс курсоров, поддерживающий наборы результатов на стороне сервера - проверьте документацию по модулю на MySQLdb.cursors.

17 голосов
/ 03 декабря 2008

Решение по ограничению / смещению выполняется за квадратичное время, потому что mysql должен повторно сканировать строки, чтобы найти смещение. Как вы и подозревали, курсор по умолчанию хранит весь набор результатов на клиенте, который может занимать много памяти.

Вместо этого вы можете использовать серверный курсор, который поддерживает запрос и извлекает результаты по мере необходимости. Класс курсора можно настроить, указав значение по умолчанию для самого вызова соединения или указав класс для метода курсора каждый раз.

from MySQLdb import cursors
cursor = conn.cursor(cursors.SSCursor)

Но это еще не все. В дополнение к сохранению результата mysql, клиентский курсор по умолчанию фактически выбирает каждую строку независимо. Такое поведение недокументировано и очень неудачно. Это означает, что для всех строк создаются полные объекты Python, которые занимают гораздо больше памяти, чем исходный результат mysql.

В большинстве случаев результат, сохраненный на клиенте, упакованном как итератор, даст лучшую скорость при разумном использовании памяти. Но вам придется свернуть свой собственный, если вы этого хотите.

7 голосов
/ 03 декабря 2008

Вы пробовали эту версию fetchone? Или что-то другое?

row = cursor.fetchone() 
while row is not None:
    # process
    row = cursor.fetchone()

Кроме того, вы пробовали это?

 row = cursor.fetchmany(size=1)
 while row is not None:
     # process
     row = cursor.fetchmany( size=1 )

Не все драйверы поддерживают их, поэтому, возможно, вы получили ошибки или нашли их слишком медленными.


Редактировать.

Когда он зависает при выполнении, вы ожидаете базу данных. Это не строка за строкой Python; это вещь MySQL.

MySQL предпочитает извлекать все строки как часть своего собственного управления кэшем. Это отключается предоставлением значения fetch_size для Integer.MIN_VALUE (-2147483648L).

Вопрос в том, какая часть DBAPI Python становится эквивалентом JDBC fetch_size?

Я думаю, что это может быть атрибут массива курсора. Попробуйте

cursor.arraysize=-2**31

И посмотрите, заставляет ли это MySQL передавать поток результатов вместо кэширования.

2 голосов
/ 25 октября 2014

Я нашел лучшие результаты, немного смешивающиеся с некоторыми другими ответами.

Это включает настройку cursorclass=MySQLdb.cursors.SSDictCursor (для MySQLdb) или pymysql.cursors.SSDictCursor (для PyMySQL) как часть настроек соединения. Это позволит серверу хранить запрос / результаты («SS» означает сторону сервера, а не курсор по умолчанию, который выводит клиентскую часть результатов) и создавать словарь из каждой строки (например, {'id': 1, ' name ':' Cookie Monster '}).

Затем для обхода строк в Python 2.7 и 3.4 был бесконечный цикл, вызванный while rows is not None, потому что даже при вызове cur.fetchmany(size=10000) и отсутствии результатов метод возвращал пустой список ([]) вместо None.

Фактический пример:

query = """SELECT * FROM my_table"""
conn = pymysql.connect(host=MYSQL_CREDENTIALS['host'], user=MYSQL_CREDENTIALS['user'],
                          passwd=MYSQL_CREDENTIALS['passwd'], charset='utf8', cursorclass = pymysql.cursors.SSDictCursor)
cur = conn.cursor()
results = cur.execute(query)
rows = cur.fetchmany(size=100)
while rows:
    for row in rows: 
        process(row)
    rows = cur.fetchmany(size=100)
cur.close()
conn.close()
1 голос
/ 10 сентября 2013

Попробуйте использовать MySQLdb.cursors.SSDictCursor

con = MySQLdb.connect(host=host,
                  user=user,
                  passwd=pwd,
                  charset=charset,
                  port=port,
                  cursorclass=MySQLdb.cursors.SSDictCursor);
cur = con.cursor()
cur.execute("select f1, f2 from table")
for row in cur:
    print row['f1'], row['f2']
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...