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 with
palavra - 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.Connection
implementa 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 with
já, ou você pode ler a instrução "with" do Python , mas essencialmente o que acontece é que é __enter__
executado no início do with
bloco e __exit__
ao sair do with
bloco. Você pode usar a sintaxe opcional with EXPR as VAR
para 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 with
bloco? 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 with
está apenas gerenciando a conexão. Portanto, a conexão e o cursor permanecem abertos após a saída do with
bloco. 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.BaseCursor
classe herda diretamente object
e 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.Connection
classe __enter__
e os __exit__
métodos fornecem um objeto de cursor totalmente novo em cada with
bloco 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 with
bloco. 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 close
método do objeto de argumento no final do with
bloco. (Para ver isso em ação, basta definir uma classe Foo
com __enter__
, __exit__
e close
métodos contendo simples print
declarações, e comparar o que acontece quando você faz with Foo(): pass
com 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á BEGIN
uma transação explícita no servidor quando você usar with connection
e 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 BEGIN
para 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 adicionarclosing
ao 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 VAR
vincula 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 connection
atributo do cursor , que fornece acesso de proxy à conexão original. Quando o cursor é fechado, seu connection
atributo é 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