Mysql
 sql >> база данни >  >> RDS >> Mysql

Кога да затворите курсорите с MySQLdb

Вместо да питате каква е стандартната практика, тъй като това често е неясно и субективно, може да опитате да потърсите насоки в самия модул. Като цяло, с помощта на with ключова дума, както предложи друг потребител, е страхотна идея, но при това конкретно обстоятелство може да не ви предостави напълно функционалността, която очаквате.

От версия 1.2.5 на модула, MySQLdb.Connection внедрява протокола за управление на контекста със следния код (github.com/connections. ):

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:            # 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(), преди да затворите курсора. В крайна сметка, за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          # None
my_curs.connection.close()  # throws AttributeError, but connection still open
del my_curs                 # connection will close here


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. MySQL DROP FOREIGN KEY Ограничение

  2. Деактивирайте ONLY_FULL_GROUP_BY

  3. Настройте база данни и създайте потребител само за четене в AWS Redshift и Mysql

  4. mysql име на поле от променлива

  5. MySQL DROP ТАБЛИЦА