Вместо того, чтобы спрашивать, что является стандартной практикой, поскольку это часто неясно и субъективно, вы можете попробовать обратиться к самому модулю за руководством. В общем, использование 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