Когда закрыть курсоры с помощью MySQLdb
Я создаю веб-приложение WSGI, и у меня есть база данных MySQL. Я использую MySQLdb, который предоставляет курсоры для выполнения инструкций и получения результатов. какова стандартная практика получения и закрытия курсоров? в частности, как долго должны длиться мои курсоры? Должен ли я получить новый курсор для каждой транзакции?
Я считаю, что вам нужно закрыть курсор перед фиксацией соединения. Есть ли существенное преимущество в поиске наборов транзакций, которые не требуют промежуточных коммитов, так что вам не нужно получать новые курсоры для каждой транзакции? Есть много ресурсов для получения новых курсоров, или это просто не важно?
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
методы, содержащие простые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
связывает подключение toVAR
в отличие отwith MySQLdb.connect(user, pass) as VAR
, который обязывает новый объект курсора toVAR
. В последнем случае у вас не будет прямого доступа к объекту подключения! Вместо этого вам придется использовать курсор , которым предоставляет прокси-доступ к исходному соединению. Когда курсор закрыт, его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
(Не помню, что внутренняя ошибка) сообщение об ошибке. И держите его открытым в течение всего сеанса и закройте его, когда вы знаете, что он вам больше не понадобится.