Когда закрыть курсоры с помощью MySQLdb


Я создаю веб-приложение WSGI, и у меня есть база данных MySQL. Я использую MySQLdb, который предоставляет курсоры для выполнения инструкций и получения результатов. какова стандартная практика получения и закрытия курсоров? в частности, как долго должны длиться мои курсоры? Должен ли я получить новый курсор для каждой транзакции?

Я считаю, что вам нужно закрыть курсор перед фиксацией соединения. Есть ли существенное преимущество в поиске наборов транзакций, которые не требуют промежуточных коммитов, так что вам не нужно получать новые курсоры для каждой транзакции? Есть много ресурсов для получения новых курсоров, или это просто не важно?

5 66

5 ответов:

вместо того, чтобы спрашивать, Что такое стандартная практика, поскольку это часто неясно и субъективно, вы можете попробовать обратиться к самому модулю за руководством. В общем, используя with ключевое слово, как предложил другой пользователь, - отличная идея, но в этом конкретном случае она может не дать вам достаточно функциональности, которую вы ожидаете.

начиная с версии 1.2.5 модуля,MySQLdb.Connection осуществляет context manager protocol со следующим кодом (github):

def __enter__(self):
    if self.get_autocommit():
        self.query("BEGIN")
    return self.cursor()

def __exit__(self, exc, value, tb):
    if exc:
        self.rollback()
    else:
        self.commit()

есть несколько существующих вопросов и ответов о with уже, или вы можете прочитать понимание Python" с " заявление, но по существу происходит то, что __enter__ выполняется в начале with блок, а __exit__ выполняется при выходе из with блок. Вы можете использовать дополнительный синтаксис with EXPR as VAR для привязки объекта, возвращаемого __enter__ к имени, если вы собираетесь ссылаться на этот объект позже. Итак, учитывая вышесказанное реализация, вот простой способ запросить вашу базу данных:

connection = MySQLdb.connect(...)
with connection as cursor:            # connection.__enter__ executes at this line
    cursor.execute('select 1;')
    result = cursor.fetchall()        # connection.__exit__ executes after this line
print result                          # prints "((1L,),)"

теперь вопрос в том, каковы состояния соединения и курсора после выхода из with блок? Элемент __exit__ метод, показанный выше, вызывает только self.rollback() или self.commit(), и ни один из этих методов пойти на вызов close() метод. Сам курсор не имеет __exit__ метод определен – и не имело бы значения, если бы это было так, потому что with управляет только соединением. Таким образом, как соединение, так и курсор остается открытым после выхода из with блок. Это легко подтвердить, добавив следующий код в приведенный выше пример:

try:
    cursor.execute('select 1;')
    print 'cursor is open;',
except MySQLdb.ProgrammingError:
    print 'cursor is closed;',
if connection.open:
    print 'connection is open'
else:
    print 'connection is closed'

вы должны увидеть вывод "курсор открыт; соединение открыто", напечатанный на stdout.

я считаю, что вам нужно закрыть курсор перед фиксацией соединения.

почему? Элемент MySQL C API, что является основанием для MySQLdb, не реализует какой-либо объект курсора, как подразумевается в документации модуля: "MySQL не поддерживает курсоры; однако, курсоры легко эмулируются." действительно,MySQLdb.cursors.BaseCursor класс наследуется непосредственно от object и не накладывает такого ограничения на курсоры в отношении фиксации / отката. Разработчик Oracle сказал:

cnx.совершить() прежде чем шавка.закрыть() звучит наиболее логично для меня. Может быть, вы можно пойти по правилу: "закройте курсор, если он вам больше не нужен." Таким образом commit () перед закрытием курсора. В конце концов, для Соединитель / Python, это не имеет большого значения, но или другое базы данных это может быть.

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

есть ли какое-либо существенное преимущество в поиске наборов транзакций, которые не требуют промежуточных коммитов, так что вам не нужно получать новые курсоры для каждой транзакции?

я очень очень сомневаюсь в этом, и в попытке сделать это, вы можете ввести дополнительную человеческую ошибку. Лучше определиться с Конвенцией и придерживаться ее.

есть много ресурсов для получения новых курсоров, или это просто не важно?

накладные расходы незначительны и не касаются сервера базы данных вообще; это полностью в рамках реализации MySQLdb. Вы можете посмотреть BaseCursor.__init__ на github если вам действительно интересно знать, что происходит при создании нового курсора.

возвращаясь к ранее, когда мы обсуждали with, возможно, теперь вы можете понять, почему MySQLdb.Connection класс __enter__ и __exit__ методы дадут вам совершенно новый объект курсора в каждом with блок и не беспокойтесь о том, чтобы отслеживать его или закрывать его в конце блока. Это довольно легкий и существует исключительно для вашего удобства.

если это действительно так важно для вас, чтобы контролировать объект курсора, вы можете используйте contextlib.закрытие чтобы компенсировать тот факт, что объект курсора не имеет определенного __exit__ метод. Если на то пошло, вы также можете использовать его, чтобы заставить объект связи, чтобы закрыть себя при выходе из with блок. Это должно вывести "my_curs закрыт; my_conn закрыт":

from contextlib import closing
import MySQLdb

with closing(MySQLdb.connect(...)) as my_conn:
    with closing(my_conn.cursor()) as my_curs:
        my_curs.execute('select 1;')
        result = my_curs.fetchall()
try:
    my_curs.execute('select 1;')
    print 'my_curs is open;',
except MySQLdb.ProgrammingError:
    print 'my_curs is closed;',
if my_conn.open:
    print 'my_conn is open'
else:
    print 'my_conn is closed'

обратите внимание, что with closing(arg_obj) не будет вызывать аргумент объекта __enter__ и __exit__ методы; это только вызов объекта аргумента close метод в конец with блок. (Чтобы увидеть это в действии, просто определите класс Foo С __enter__,__exit__ и close методы, содержащие простые print заявления, и сравните, что происходит, когда вы делаете with Foo(): pass что происходит, когда вы делаете with closing(Foo()): pass.) Это имеет два существенных последствия:

во-первых, если включен режим автоматической фиксации, MySQLdb будет BEGIN явная транзакция на сервере при использовании with connection и зафиксировать или откатить транзакцию в конце блок. Это поведение по умолчанию MySQLdb, предназначенное для защиты вас от поведения MySQL по умолчанию немедленного совершения любых и всех операторов DML. MySQLdb предполагает, что при использовании менеджера контекста вы хотите транзакцию и используете явный BEGIN для обхода автоматической фиксации на сервере. Если вы привыкли использовать with connection, вы можете подумать, что autocommit отключен, когда на самом деле он только был обойден. Вы можете получить неприятный сюрприз, если вы добавите closing в ваш код и потерять целостность транзакций; вы не сможете откатить изменения, вы можете начать видеть ошибки параллелизма, и это может быть не сразу очевидно, почему.

второе, with closing(MySQLdb.connect(user, pass)) as VAR связывает подключение to VAR в отличие от with MySQLdb.connect(user, pass) as VAR, который обязывает новый объект курсора to VAR. В последнем случае у вас не будет прямого доступа к объекту подключения! Вместо этого вам придется использовать курсор , которым предоставляет прокси-доступ к исходному соединению. Когда курсор закрыт, его connection атрибут имеет значение None. Это приводит к заброшенному соединению, которое будет оставаться до тех пор, пока не произойдет одно из следующих событий:

  • все ссылки на курсор удаляются
  • курсор выходит за рамки
  • время ожидания соединения
  • соединение закрывается вручную через администрирование сервера инструменты

вы можете проверить это, контролируя открытые соединения (в Workbench или по используя SHOW PROCESSLIST) при выполнении следующих строк по одной:

with MySQLdb.connect(...) as my_curs:
    pass
my_curs.close()
my_curs.connection          # None
my_curs.connection.close()  # throws AttributeError, but connection still open
del my_curs                 # connection will close here

лучше переписать его с помощью ключевого слова 'with'. "С" позаботится о закрытии курсора (это важно, потому что это неуправляемый ресурс) автоматически. Преимущество в том, что он также закроет курсор в случае исключения.

from contextlib import closing
import MySQLdb

''' At the beginning you open a DB connection. Particular moment when
  you open connection depends from your approach:
  - it can be inside the same function where you work with cursors
  - in the class constructor
  - etc
'''
db = MySQLdb.connect("host", "user", "pass", "database")
with closing(db.cursor()) as cur:
    cur.execute("somestuff")
    results = cur.fetchall()
    # do stuff with results

    cur.execute("insert operation")
    # call commit if you do INSERT, UPDATE or DELETE operations
    db.commit()

    cur.execute("someotherstuff")
    results2 = cur.fetchone()
    # do stuff with results2

# at some point when you decided that you do not need
# the open connection anymore you close it
db.close()

Я думаю, вам будет лучше попытаться использовать один курсор для всех ваших исполнений и закрыть его в конце вашего кода. С ним легче работать, и он также может иметь преимущества эффективности (Не цитируйте меня по этому поводу).

conn = MySQLdb.connect("host","user","pass","database")
cursor = conn.cursor()
cursor.execute("somestuff")
results = cursor.fetchall()
..do stuff with results
cursor.execute("someotherstuff")
results2 = cursor.fetchall()
..do stuff with results2
cursor.close()

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

в противном случае, я бы сказал, просто закройте курсоры, как только вы закончите получать все данные из них. Таким образом, вам не придется беспокоиться о обрубает концы в коде.

Примечание: этот ответ для PyMySQL, который является падающей заменой для MySQLdb и эффективно последней версии MySQLdb, так как MySQLdb перестал поддерживаться. Я считаю, что все здесь и правда о наследии MySQLdb, но не проверял.

прежде всего, некоторые факты:

  • в Python with синтаксис вызывает менеджер контекста __enter__ метод перед выполнением тела из with блок, и его __exit__ потом способ.
  • подключения есть __enter__ метод, который ничего не делает, кроме создания и возврата курсора, и __exit__ метод фиксации или отката (в зависимости от того, произошло исключение). Это не закрыть соединение.
  • курсоры в PyMySQL-это чисто абстракция, реализованная в Python; в MySQL нет эквивалентной концепции себя.1
  • курсоры есть __enter__ метод, который ничего не делает и __exit__ метод, который "закрывает" курсор (что просто означает обнуление ссылки курсора на его родительское соединение и выбрасывание любых данных, хранящихся на курсоре).
  • курсоры содержат ссылку на соединение, которое их породило, но соединения не содержат ссылки на созданные ими курсоры.
  • подключения есть __del__ метод, который закрывает их
  • за https://docs.python.org/3/reference/datamodel.html, CPython (реализация Python по умолчанию) использует подсчет ссылок и автоматически удаляет объект, как только количество ссылок на него достигает нуля.

поставить эти вещи вместе, мы видим, что наивный такой код в теории проблематично:

# Problematic code, at least in theory!
import pymysql
with pymysql.connect() as cursor:
    cursor.execute('SELECT 1')

# ... happily carry on and do something unrelated

проблема в том, что ничего закрыл соединение. Действительно, если вы вставите код выше в оболочку Python, а затем запустите SHOW FULL PROCESSLIST в оболочке MySQL вы сможете увидеть простое соединение, которое вы создали. Так как число соединений по умолчанию MySQL является 151, не огромный, теоретически вы могли бы начать сталкиваться с проблемами, если бы у вас было много процессов, поддерживающих эти соединения открытыми.

однако, в CPython, есть спасительная благодать, которая гарантирует, что код, как мой пример выше наверное не заставит вас оставить вокруг множество открытых соединений. Что спасение в том, что как только cursor выходит из области видимости (например, функция, в которой он был создан заканчивается, или cursor получает другое значение, присвоенное ему), его счетчик ссылок достигает нуля, что приводит к его удалению, сбрасывая счетчик ссылок соединения до нуля, вызывая соединения __del__ вызываемый метод, который принудительно закрывает соединение. Если вы уже вставили код выше в вашей оболочке Python, то теперь вы можете имитировать это, запустив cursor = 'arbitrary value'; как только вы это сделаете, соединение, которое вы открыли, исчезнет из SHOW PROCESSLIST выход.

однако полагаться на это неэлегантно и теоретически может потерпеть неудачу в реализациях Python, отличных от CPython. Чище, по идее, было бы явно .close() соединение (чтобы освободить соединение в базе данных, не дожидаясь, пока Python уничтожит объект). Это более надежный код выглядит так:

import contextlib
import pymysql
with contextlib.closing(pymysql.connect()) as conn:
    with conn as cursor:
        cursor.execute('SELECT 1')

это некрасиво, но не полагается на Python, разрушающий ваши объекты, чтобы освободить ваше (конечное доступное количество) соединений с базой данных.

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

наконец, чтобы ответить на второстепенные вопросы здесь:

есть много ресурсов для получения новых курсоров, или это просто ничего особенного?

нет, создание экземпляра курсора вообще не попадает в MySQL и в основном ничего не делает.

есть ли какое-либо существенное преимущество в поиске наборов транзакций, которые не требуют промежуточных коммитов, так что вам не нужно получать новые курсоры для каждой транзакции?

это ситуативно и трудно дать общий ответ. Как https://dev.mysql.com/doc/refman/en/optimizing-innodb-transaction-management.html ставит его,"приложение может столкнуться с проблемами производительности, если он совершает тысячи раз в секунду, и различные проблемы производительности, если он совершает только каждые 2-3 часа". Вы платите накладные расходы на производительность за каждую фиксацию, но, оставляя транзакции открытыми дольше, вы увеличиваете вероятность того, что другим соединениям придется тратить время на ожидание блокировки, увеличиваете риск взаимоблокировки и потенциально увеличивают стоимость некоторых поисков, выполняемых другими соединениями.


1 MySQL тут есть конструкция, которую он называет курсор но они существуют только внутри хранимых процедур; они полностью отличаются от курсоров PyMySQL и здесь не актуальны.

Я предлагаю сделать это как php и mysql. Запустите i в начале вашего кода перед печатью первых данных. Так что если вы получаете сообщение об ошибке подключения вы можете отобразить 50x(Не помню, что внутренняя ошибка) сообщение об ошибке. И держите его открытым в течение всего сеанса и закройте его, когда вы знаете, что он вам больше не понадобится.