Ответ 1
Вместо того, чтобы спрашивать, что такое стандартная практика, так как это часто неясно и субъективно, вы можете попробовать посмотреть на сам модуль для руководства. В общем, использование ключевого слова with
в качестве другого предлагаемого пользователя - отличная идея, но в этом конкретном случае это может не дать вам достаточно функциональности, которую вы ожидаете.
Начиная с версии 1.2.5 модуля MySQLdb.Connection
реализует протокол контекстного менеджера со следующим кодом (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()
Уже существует несколько существующих Q & A about 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'
Вы должны увидеть, что "курсор открыт, соединение открыто" напечатано в стандартном формате.
Я считаю, что вам нужно закрыть курсор перед выполнением соединения.
Почему? MySQL C API, который является основой для MySQLdb
, не реализует никакого объекта курсора, как это подразумевается в документации модуля: "MySQL не поддерживает курсоры, однако курсоры легко эмулируются" . Действительно, класс MySQLdb.cursors.BaseCursor
наследуется непосредственно из object
и не налагает таких ограничений на курсоры в отношении фиксации/отката. Разработчик Oracle имел это, чтобы сказать:
cnx.commit() до того, как cur.close() звучит наиболее логично для меня. Может быть, вы может пойти по правилу: "Закройте курсор, если он вам больше не нужен". Таким образом commit() перед закрытием курсора. В конце концов, для Коннектор /Python, это не имеет большого значения, но и другое базы данных.
Я ожидаю, что так близко, как вы собираетесь перейти к "стандартной практике" по этому вопросу.
Есть ли существенное преимущество в поиске наборов транзакций, которые не требуют промежуточных коммитов, чтобы вам не приходилось получать новые курсоры для каждой транзакции?
Я очень сомневаюсь в этом, и, пытаясь сделать это, вы можете ввести дополнительную человеческую ошибку. Лучше выбрать соглашение и придерживаться его.
Есть много накладных расходов для получения новых курсоров, или это просто не большая сделка?
Накладные расходы незначительны и вообще не касаются сервера базы данных; это полностью в рамках реализации MySQLdb. Вы можете посмотреть BaseCursor.__init__
на github, если вам действительно интересно узнать, что происходит при создании нового курсора.
Возвращаясь к предыдущему, когда мы обсуждали with
, возможно, теперь вы можете понять, почему методы MySQLdb.Connection
class __enter__
и __exit__
дают вам новый объект курсора в каждом блоке with
t следить за ним или закрывать его в конце блока. Он довольно легкий и существует исключительно для вашего удобства.
Если вам действительно важно, чтобы микромеханировать объект курсора, вы можете использовать contextlib.closing, чтобы восполнить тот факт, что курсор объект не имеет определенного метода __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
, к тому, что происходит, когда вы do with closing(Foo()): pass
.) Это имеет два существенных значения:
Во-первых, если включен режим autocommit, MySQLdb будет BEGIN
явной транзакцией на сервере при использовании with connection
и фиксации транзакции в конце блока. Это поведение MySQLdb по умолчанию, предназначенное для защиты вас от поведения по умолчанию MySQL, которое немедленно совершает любые и все заявления DML. MySQLdb предполагает, что при использовании диспетчера контекстов вы хотите выполнить транзакцию и используете явный BEGIN
для обхода параметра autocommit на сервере. Если вы привыкли использовать with connection
, вы можете подумать, что автокоммит отключен, когда на самом деле он только обходит. У вас может возникнуть неприятный сюрприз, если вы добавите closing
в свой код и потеряете целостность транзакций; вы не сможете отменить изменения, вы можете увидеть ошибки concurrency, и это может быть не сразу очевидным.
Во-вторых, with closing(MySQLdb.connect(user, pass)) as VAR
привязывает объект подключения к VAR
, в отличие от with MySQLdb.connect(user, pass) as VAR
, который привязывает новый объект курсора к VAR
. В последнем случае у вас не будет прямого доступа к объекту подключения! Вместо этого вам нужно будет использовать атрибут курсора connection
, который обеспечивает прокси-доступ к исходному соединению. Когда курсор закрыт, для атрибута 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