Вместо того, чтобы спрашивать, что является стандартной практикой, поскольку это часто неясно и субъективно, вы можете попробовать обратиться к самому модулю за руководством. В общем, использование 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()
Уже есть несколько вопросов и ответов with, или вы можете прочитать оператор «with» в Python , но по сути происходит то, что __enter__выполняется в начале withблока и __exit__выполняется после выхода из withблока. Вы можете использовать необязательный синтаксис with EXPR as VARдля привязки объекта, возвращаемого __enter__к имени, если вы намереваетесь ссылаться на этот объект позже. Итак, учитывая приведенную выше реализацию, вот простой способ запроса вашей базы данных:
connection = MySQLdb.connect(...)
with connection as cursor:
cursor.execute('select 1;')
result = cursor.fetchall()
print result
Теперь вопрос в том, каковы состояния соединения и курсора после выхода из 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 () перед закрытием курсора. В конце концов, для Connector / Python это не имеет большого значения, но для других баз данных может.
Я полагаю, что это самое близкое к «стандартной практике» по этому вопросу.
Есть ли какое-либо существенное преимущество в нахождении наборов транзакций, не требующих промежуточных фиксаций, чтобы вам не приходилось получать новые курсоры для каждой транзакции?
Я очень в этом сомневаюсь, и, пытаясь сделать это, вы можете внести дополнительную человеческую ошибку. Лучше определиться с соглашением и придерживаться его.
Есть ли много накладных расходов на получение новых курсоров или это просто не имеет большого значения?
Накладные расходы незначительны и вообще не затрагивают сервер базы данных; это полностью в рамках реализации MySQLdb. Вы можете посмотреть BaseCursor.__init__на github, если вам действительно интересно узнать, что происходит, когда вы создаете новый курсор.
Возвращаясь к тому withмоменту, когда мы обсуждали , возможно, теперь вы можете понять, почему MySQLdb.Connectionкласс __enter__и __exit__методы предоставляют вам новый объект курсора в каждом withблоке и не беспокоятся о его отслеживании или закрытии в конце блока. Он довольно легкий и существует исключительно для вашего удобства.
Если для вас действительно так важно управлять объектом курсора, вы можете использовать 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то , что происходит , когда вы делаете with closing(Foo()): pass.) Это имеет два существенных последствия:
Во-первых, если включен режим автоматической фиксации, MySQLdb будет выполнять BEGINявную транзакцию на сервере, когда вы используете with connectionи фиксируете или откатываете транзакцию в конце блока. Это поведение MySQLdb по умолчанию, предназначенное для защиты вас от поведения MySQL по умолчанию, заключающегося в немедленной фиксации любых без исключения операторов DML. MySQLdb предполагает, что когда вы используете диспетчер контекста, вам нужна транзакция, и использует явное, BEGINчтобы обойти настройку автоматической фиксации на сервере. Если вы привыкли использовать with connection, вы можете подумать, что автоматическая фиксация отключена, хотя на самом деле она только обходилась. Вы можете получить неприятный сюрприз, если добавитеclosingвашему коду и потеряете транзакционную целостность; вы не сможете откатить изменения, вы можете начать видеть ошибки параллелизма, и может быть не сразу понятно почему.
Во- вторых, 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
my_curs.connection.close()
del my_curs