Вместо того, чтобы спрашивать, что является стандартной практикой, поскольку это часто неясно и субъективно, вы можете попробовать обратиться к самому модулю за руководством. В общем, использование 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
cursor.close()
часть API Python DB , который не был написан специально для MySQL.my_curs
содержит последнюю ссылку наconnection
объект. Как только эта ссылка больше не существует,connection
объект следует очистить от мусора.with
иMySQLdb.Connection
-х__enter__
и__exit__
функций. Еще раз спасибо @Air.Лучше его переписать с помощью ключевого слова with. '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()
источник
with
это хороший вариант, если вы хотите использовать его во Flask или другом веб-фреймворке. Если ситуацияhttp://flask.pocoo.org/docs/patterns/sqlite3/#sqlite3
сложится, то будут проблемы.with closing(self.db.cursor()) as cur: cur.execute("UPDATE table1 SET status = %s WHERE id = %s",(self.INTEGR_STATUS_PROCESSING, id)) self.db.commit()
Примечание: этот ответ предназначен для PyMySQL , который является заменой MySQLdb и, по сути, последней версией MySQLdb, так как MySQLdb перестал поддерживаться. Я считаю , что все , что здесь также относится к унаследованным MySQLdb, но не проверял.
Прежде всего, некоторые факты:
with
Синтаксис Python вызывает метод диспетчера контекста__enter__
перед выполнением телаwith
блока, а затем его__exit__
метод.__enter__
метод, который ничего не делает, кроме создания и возврата курсора, и__exit__
метод, который либо фиксирует, либо откатывает (в зависимости от того, было ли создано исключение). Он не закрывает соединение.__enter__
метод, который ничего не делает, и__exit__
метод, который «закрывает» курсор (что означает просто обнуление ссылки курсора на его родительское соединение и отбрасывание любых данных, хранящихся в курсоре).__del__
метод, который их закрываетСобирая все это вместе, мы видим, что такой наивный код теоретически проблематичен:
# 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 и здесь не актуальны.
источник
Я думаю, вам будет лучше попытаться использовать один курсор для всех ваших выполнений и закрыть его в конце вашего кода. С ним легче работать, и он может иметь преимущества в эффективности (не цитируйте меня по этому поводу).
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 () и вам нужно выполнить второе выполнение курсора, прежде чем вы перебираете все результаты из первого запроса.
В противном случае я бы сказал, просто закройте ваши курсоры, как только вы закончите извлекать из них все данные. Таким образом, вам не придется беспокоиться о том, что в дальнейшем в вашем коде будут лишены дела.
источник
Предлагаю сделать это как php и mysql. Запустите i в начале вашего кода перед печатью первых данных. Поэтому, если вы получаете сообщение об ошибке подключения, вы можете отобразить сообщение об ошибке
50x
(не помню, что такое внутренняя ошибка). И держите его открытым в течение всего сеанса и закройте, когда знаете, что он вам больше не нужен.источник