Em vez de perguntar o que é prática padrão, já que isso geralmente é obscuro e subjetivo, você pode tentar consultar o próprio módulo para orientação. Em geral, usar a withpalavra - chave sugerida por outro usuário é uma ótima ideia, mas, nesta circunstância específica, pode não oferecer a funcionalidade que você espera.
A partir da versão 1.2.5 do módulo, MySQLdb.Connectionimplementa o protocolo do gerenciador de contexto com o seguinte código ( 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()
Existem várias perguntas e respostas existentes sobre withjá, ou você pode ler a instrução "with" do Python , mas essencialmente o que acontece é que é __enter__executado no início do withbloco e __exit__ao sair do withbloco. Você pode usar a sintaxe opcional with EXPR as VARpara vincular o objeto retornado por __enter__a um nome se pretende fazer referência a esse objeto posteriormente. Portanto, dada a implementação acima, aqui está uma maneira simples de consultar seu banco de dados:
connection = MySQLdb.connect(...)
with connection as cursor:
cursor.execute('select 1;')
result = cursor.fetchall()
print result
A questão agora é: quais são os estados da conexão e do cursor após sair do withbloco? O __exit__método mostrado acima chama apenas self.rollback()ou self.commit(), e nenhum desses métodos continua para chamar o close()método. O próprio cursor não tem __exit__método definido - e não importaria se tivesse, porque withestá apenas gerenciando a conexão. Portanto, a conexão e o cursor permanecem abertos após a saída do withbloco. Isso é facilmente confirmado adicionando o seguinte código ao exemplo acima:
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'
Você deve ver a saída "cursor is open; connection is open" impressa em stdout.
Eu acredito que você precisa fechar o cursor antes de confirmar a conexão.
Por quê? A API C do MySQL , que é a base para MySQLdb, não implementa nenhum objeto cursor, conforme implícito na documentação do módulo: "O MySQL não oferece suporte a cursores; no entanto, os cursores são facilmente emulados." Na verdade, a MySQLdb.cursors.BaseCursorclasse herda diretamente objecte não impõe tal restrição aos cursores com relação ao commit / rollback. Um desenvolvedor Oracle disse o seguinte :
cnx.commit () antes de cur.close () parece mais lógico para mim. Talvez você possa seguir a regra: "Feche o cursor se não precisar mais dele." Portanto, commit () antes de fechar o cursor. No final, para o Connector / Python, não faz muita diferença, mas para outros bancos de dados pode fazer.
Espero que isso seja o mais próximo que você chegará da "prática padrão" neste assunto.
Existe alguma vantagem significativa em localizar conjuntos de transações que não requerem confirmações intermediárias, de forma que você não precise obter novos cursores para cada transação?
Duvido muito e, ao tentar fazer isso, você pode introduzir um erro humano adicional. Melhor decidir sobre uma convenção e segui-la.
Há muita sobrecarga para obter novos cursores ou simplesmente não é um grande problema?
A sobrecarga é insignificante e não afeta o servidor de banco de dados; está inteiramente dentro da implementação do MySQLdb. Você pode olhar no BaseCursor.__init__github se estiver realmente curioso para saber o que está acontecendo quando você cria um novo cursor.
Voltando a quando estávamos discutindo with, talvez agora você possa entender por que a MySQLdb.Connectionclasse __enter__e os __exit__métodos fornecem um objeto de cursor totalmente novo em cada withbloco e não se preocupe em mantê-lo ou fechá-lo no final do bloco. É bastante leve e existe exclusivamente para sua conveniência.
Se for realmente tão importante para você microgerenciar o objeto cursor, você pode usar contextlib.closing para compensar o fato de que o objeto cursor não tem um __exit__método definido . Além disso, você também pode usá-lo para forçar o objeto de conexão a se fechar ao sair de um withbloco. Isso deve gerar "my_curs is closed; my_conn is closed":
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'
Observe que with closing(arg_obj)não chamará os métodos __enter__e do objeto do argumento __exit__; ele apenas chamará o closemétodo do objeto de argumento no final do withbloco. (Para ver isso em ação, basta definir uma classe Foocom __enter__, __exit__e closemétodos contendo simples printdeclarações, e comparar o que acontece quando você faz with Foo(): passcom o que acontece quando você faz with closing(Foo()): pass.) Isso tem duas implicações importantes:
Primeiro, se o modo autocommit estiver habilitado, o MySQLdb fará BEGINuma transação explícita no servidor quando você usar with connectione confirmar ou reverter a transação no final do bloco. Esses são os comportamentos padrão do MySQLdb, com o objetivo de protegê-lo do comportamento padrão do MySQL de confirmar imediatamente todas e quaisquer instruções DML. O MySQLdb assume que quando você usa um gerenciador de contexto, você quer uma transação, e usa o explícito BEGINpara ignorar a configuração de autocommit no servidor. Se você está acostumado a usar with connection, pode pensar que o autocommit está desabilitado, quando na verdade estava apenas sendo ignorado. Você pode ter uma surpresa desagradável se adicionarclosingao seu código e perder integridade transacional; você não será capaz de reverter as alterações, você pode começar a ver bugs de simultaneidade e pode não ser imediatamente óbvio o porquê.
Em segundo lugar, with closing(MySQLdb.connect(user, pass)) as VARvincula o objeto de conexão a VAR, em contraste com with MySQLdb.connect(user, pass) as VAR, que vincula um novo objeto cursor a VAR. No último caso, você não teria acesso direto ao objeto de conexão! Em vez disso, você teria que usar o connectionatributo do cursor , que fornece acesso de proxy à conexão original. Quando o cursor é fechado, seu connectionatributo é definido como None. Isso resulta em uma conexão abandonada que permanecerá até que uma das seguintes opções aconteça:
- Todas as referências ao cursor são removidas
- O cursor sai do escopo
- A conexão atinge o tempo limite
- A conexão é fechada manualmente por meio de ferramentas de administração do servidor
Você pode testar isso monitorando conexões abertas (no Workbench ou usandoSHOW PROCESSLIST ) enquanto executa as seguintes linhas uma por uma:
with MySQLdb.connect(...) as my_curs:
pass
my_curs.close()
my_curs.connection
my_curs.connection.close()
del my_curs