2014-09-10 15 views
5

Typowe zapytanie biblioteki MySQLdb może zużywać dużo pamięci i słabo działać w Pythonie, gdy generowany jest duży zestaw wyników. Na przykład:Używanie python MySQLDB SScursor z zapytaniami zagnieżdżonymi

cursor.execute("SELECT id, name FROM `table`") 
for i in xrange(cursor.rowcount): 
    id, name = cursor.fetchone() 
    print id, name 

Jest opcja, że ​​kursor będzie pobierać tylko jeden wiersz na raz, rzeczywiście przyspieszając scenariusz i cięcia zużycie pamięci skryptu dużo.

import MySQLdb 
import MySQLdb.cursors 

conn = MySQLdb.connect(user="user", passwd="password", db="dbname", 
         cursorclass = MySQLdb.cursors.SSCursor) 
cur = conn.cursor() 
cur.execute("SELECT id, name FROM users") 
row = cur.fetchone() 
while row is not None: 
    doSomething() 
    row = cur.fetchone()  
cur.close() 
conn.close() 

Ale nie mogę znaleźć nic na temat korzystania z SSCursor z zagnieżdżonych zapytań. Jeśli jest to definicja doSomething():

def doSomething() 
    cur2 = conn.cursor() 
    cur2.execute('select id,x,y from table2') 
    rows = cur2.fetchall() 
    for row in rows: 
     doSomethingElse(row) 
    cur2.close() 

następnie skrypt generuje następujący błąd:

_mysql_exceptions.ProgrammingError: (2014, "Commands out of sync; you can't run this command now") 

Brzmi jakby SSCursor nie jest kompatybilny z zagnieżdżonych zapytań. Czy to prawda? Jeśli tak, to źle, ponieważ główna pętla wydaje się działać zbyt wolno przy standardowym kursorze.

Odpowiedz

6

Problem ten w omawianym kawałku w podręczniku MySQLdb Użytkownika, pod nagłówkiem the threadsafety attribute (kopalnia nacisk):

The MySQL protocol can not handle multiple threads using the same connection at once. Some earlier versions of MySQLdb utilized locking to achieve a threadsafety of 2. While this is not terribly hard to accomplish using the standard Cursor class (which uses mysql_store_result()), it is complicated by SSCursor (which uses mysql_use_result() ; with the latter you must ensure all the rows have been read before another query can be executed.

Dokumentacja dla funkcji API MySLQ C mysql_use_result() daje więcej informacji na temat komunikatu o błędzie:

When using mysql_use_result() , you must execute mysql_fetch_row() until a NULL value is returned, otherwise, the unfetched rows are returned as part of the result set for your next query. The C API gives the error Commands out of sync; you can't run this command now if you forget to do this!

innymi słowy, trzeba całkowicie pobrać zestaw wyników z dowolnego niebuforowanym kursorem (czyli taka, która wykorzystuje mysql_use_result() zamiast mysql_store_result() - z MySQLdb, to znaczy, s SSCursor i SSDictCursor) przed wykonaniem innej instrukcji za pośrednictwem tego samego połączenia.

W Twojej sytuacji najbardziej bezpośrednim rozwiązaniem byłoby otwarcie drugiego połączenia, które będzie używane podczas iteracji po zbiorze wynikowym zapytania niebuforowanego. (Nie byłoby po prostu pobierać buforowanego kursora z tego samego połączenia, trzeba by przejść dalej poza zbuforowanym zestawem wyników przed użyciem buforowanego kursora.)

Jeśli Twój przepływ pracy jest podobny do "przepuszczania przez duży zestaw wyników, wykonując N małych zapytań dla każdego wiersza, "rozważ zaglądanie do przechowywanych procedur MySQL jako alternatywy dla zagnieżdżonych kursorów z różnych połączeń. Nadal możesz używać MySQLdb do wywoływania procedury i uzyskiwania wyników, ale na pewno będziesz chciał read the documentation of MySQLdb's callproc() method, ponieważ nie jest zgodny z database API specs Pythona podczas pobierania wyników procedury.


Drugą opcją jest trzymanie się zbuforowanych kursorów, ale podzielenie zapytania na partie. Właśnie to robiłem w zeszłym roku w ramach projektu, w którym musiałem przechodzić przez setki milionów wierszy, analizować niektóre dane za pomocą modułu wewnętrznego, a po przetworzeniu każdego wiersza wykonać niektóre zapytania o wartości INSERT i UPDATE. Ogólna idea wygląda mniej więcej tak:

QUERY = r"SELECT id, name FROM `table` WHERE id BETWEEN %s and %s;" 
BATCH_SIZE = 5000 

i = 0 
while True: 
    cursor.execute(QUERY, (i + 1, i + BATCH_SIZE)) 
    result = cursor.fetchall() 

    # If there's no possibility of a gap as large as BATCH_SIZE in your table ids, 
    # you can test to break out of the loop like this (otherwise, adjust accordingly): 
    if not result: 
     break 

    for row in result: 
     doSomething() 

    i += BATCH_SIZE 

Jeszcze jedno chciałbym pamiętać o swojej przykład kodu jest to, że można iteracyjne bezpośrednio nad kursorem w MySQLdb zamiast dzwonić fetchone() wyraźnie ponad xrange(cursor.rowcount).Jest to szczególnie ważne w przypadku korzystania z niebuforowanego kursora, ponieważ atrybut rowcount jest niezdefiniowany i da bardzo nieoczekiwany wynik (patrz: Python MysqlDB using cursor.rowcount with SSDictCursor returning wrong count).

+0

python - tak poprawny i tak wolny w tym samym czasie! – shigeta

Powiązane problemy