Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Quando fechar cursores usando MySQLdb


Em vez de perguntar qual é a prática padrão, já que isso geralmente não é claro e é subjetivo, você pode tentar consultar o próprio módulo para obter orientação. Em geral, usando o with palavra-chave como outro usuário sugeriu é uma ótima ideia, mas nesta circunstância específica ela pode não fornecer 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 sobre with já, ou você pode ler Compreendendo a instrução "with" do Python , mas essencialmente o que acontece é que __enter__ é executado no início do with bloquear e __exit__ executa ao sair do with quadra. Você pode usar a sintaxe opcional with EXPR as VAR para vincular o objeto retornado por __enter__ a um nome se pretender referenciar esse objeto posteriormente. Então, dada a implementação acima, aqui está uma maneira simples de consultar seu banco de dados:
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,),)"

A questão agora é, quais são os estados da conexão e do cursor após sair do with quadra? O __exit__ método mostrado acima chama apenas self.rollback() ou self.commit() , e nenhum desses métodos chama o método close() método. O próprio cursor não tem __exit__ método definido – e não importaria se o fizesse, porque with está apenas gerenciando a conexão. Portanto, tanto a conexão quanto o cursor permanecem abertos após sair do with quadra. 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 está aberto; conexão está aberta" impressa em stdout.

Eu acredito que você precisa fechar o cursor antes de confirmar a conexão.

Por quê? A API MySQL C , que é a base para MySQLdb , não implementa nenhum objeto cursor, conforme implícito na documentação do módulo:"MySQL não suporta cursores; no entanto, os cursores são facilmente emulados." De fato, o MySQLdb.cursors.BaseCursor classe herda diretamente de object e não impõe tal restrição aos cursores em relação ao commit/rollback. Um desenvolvedor da Oracle tinha isso a dizer :

cnx.commit() antes de cur.close() soa mais lógico para mim. Talvez você possa seguir a regra:"Feche o cursor se você não precisar mais dele." Assim, commit() antes de fechar o cursor. No final, para Connector/Python, não faz muita diferença, mas ou outros bancos de dados pode.

Espero que seja o mais próximo que você vai chegar da "prática padrão" sobre este assunto.

Existe alguma vantagem significativa em encontrar conjuntos de transações que não exigem confirmações intermediárias para que você não precise obter novos cursores para cada transação?

Duvido muito e, ao tentar fazê-lo, você pode introduzir um erro humano adicional. Melhor decidir sobre uma convenção e ficar com ela.

Há muita sobrecarga para obter novos cursores ou não é grande coisa?

A sobrecarga é insignificante e não afeta o servidor de banco de dados; está inteiramente dentro da implementação do MySQLdb. Você pode ver BaseCursor.__init__ no github se você estiver realmente curioso para saber o que está acontecendo quando você cria um novo cursor.

Voltando ao início, quando estávamos discutindo with , talvez agora você possa entender porque o MySQLdb.Connection classe __enter__ e __exit__ métodos fornecem um novo objeto de cursor em cada with bloco e não se preocupe em acompanhá-lo ou fechá-lo no final do bloco. É bastante leve e existe puramente para sua conveniência.

Se for realmente importante para você microgerenciar o objeto cursor, você pode usar contextlib.closing para compensar o fato de que o objeto cursor não tem __exit__ definido método. 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 quadra. 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á o __enter__ do objeto de argumento e __exit__ métodos; será somente chame o objeto de argumento close método no final do with quadra. (Para ver isso em ação, basta definir uma classe Foo com __enter__ , __exit__ e close métodos contendo simples print e compare o que acontece quando você faz with Foo(): pass para o que acontece quando você faz with closing(Foo()): pass .) Isso tem duas implicações significativas:

Primeiro, se o modo autocommit estiver habilitado, o MySQLdb irá BEGIN uma transação explícita no servidor quando você usa with connection e confirmar ou reverter a transação no final do bloco. Estes são comportamentos padrão do MySQLdb, destinados a protegê-lo do comportamento padrão do MySQL de confirmar imediatamente qualquer e todas as instruções DML. 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 confirmação automática no servidor. Se você está acostumado a usar with connection , você pode pensar que o autocommit está desabilitado quando na verdade ele estava apenas sendo ignorado. Você pode ter uma surpresa desagradável se adicionar closing ao seu código e perder a integridade transacional; você não poderá reverter as alterações, poderá começar a ver bugs de simultaneidade e pode não ser imediatamente óbvio o porquê.

Segundo, with closing(MySQLdb.connect(user, pass)) as VAR vincula o objeto de conexão para VAR , em contraste com with MySQLdb.connect(user, pass) as VAR , que vincula um novo objeto cursor para VAR . Neste último caso, você não teria acesso direto ao objeto de conexão! Em vez disso, você teria que usar a connection do cursor atributo, que fornece acesso proxy à conexão original. Quando o cursor é fechado, sua connection atributo está definido como None . Isso resulta em uma conexão abandonada que permanecerá até que uma das seguintes situações ocorra:
  • Todas as referências ao cursor são removidas
  • O cursor sai do escopo
  • A conexão expira
  • 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 por usando SHOW PROCESSLIST ) ao executar as seguintes linhas uma a uma:
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