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
cursor.close()
faz parte da API Python DB , que não foi escrita especificamente com o MySQL em mente.my_curs
contém a última referência aoconnection
objeto. Quando essa referência não existir mais, oconnection
objeto deve ser coletado como lixo.with
eMySQLdb.Connection
s'__enter__
e__exit__
funções. Mais uma vez, obrigado @Air.É melhor reescrevê-lo usando a palavra-chave 'with'. 'Com' cuidará de fechar o cursor (é importante porque é um recurso não gerenciado) automaticamente. A vantagem é que ele também fecha o cursor em caso de exceção.
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()
fonte
with
seja uma boa opção se você quiser usá-lo no Flask ou em outro framework web. Se for a situação,http://flask.pocoo.org/docs/patterns/sqlite3/#sqlite3
então haverá problemas.with closing(self.db.cursor()) as cur: cur.execute("UPDATE table1 SET status = %s WHERE id = %s",(self.INTEGR_STATUS_PROCESSING, id)) self.db.commit()
Nota: esta resposta é para PyMySQL , que é um substituto para o MySQLdb e efetivamente a versão mais recente do MySQLdb desde que o MySQLdb parou de ser mantido. Acredito que tudo aqui também se aplica ao MySQLdb legado, mas não verifiquei.
Em primeiro lugar, alguns fatos:
with
sintaxe do Python chama o__enter__
método do gerenciador de contexto antes de executar o corpo dowith
bloco e seu__exit__
método depois.__enter__
método que não faz nada além de criar e retornar um cursor e um__exit__
método que confirma ou reverte (dependendo se uma exceção foi lançada). Ele não fechar a conexão.__enter__
método que não faz nada e um__exit__
método que "fecha" o cursor (o que significa apenas anular a referência do cursor à sua conexão pai e jogar fora todos os dados armazenados no cursor).__del__
método que as fechaJuntando essas coisas, vemos que um código ingênuo como esse é teoricamente problemático:
# Problematic code, at least in theory! import pymysql with pymysql.connect() as cursor: cursor.execute('SELECT 1') # ... happily carry on and do something unrelated
O problema é que nada fechou a conexão. Na verdade, se você colar o código acima em um shell Python e, em seguida, executar
SHOW FULL PROCESSLIST
em um shell MySQL, poderá ver a conexão ociosa que criou. Como o número padrão de conexões do MySQL é 151 , o que não é muito grande , teoricamente você poderia começar a ter problemas se tivesse muitos processos mantendo essas conexões abertas.No entanto, no CPython, há uma graça salvadora que garante que um código como o meu exemplo acima provavelmente não fará com que você deixe muitas conexões abertas. Essa graça é que, assim que
cursor
sai do escopo (por exemplo, a função na qual foi criada termina oucursor
recebe outro valor atribuído a ela), sua contagem de referência atinge zero, o que faz com que seja excluída, descartando a contagem de referência da conexão para zero, fazendo com que o__del__
método da conexão seja chamado, o que força o fechamento da conexão. Se você já colou o código acima em seu shell Python, agora você pode simular isso executandocursor = 'arbitrary value'
; assim que você fizer isso, a conexão aberta desaparecerá daSHOW PROCESSLIST
saída.No entanto, confiar nisso é deselegante e, teoricamente, pode falhar em implementações Python diferentes do CPython. Mais limpo, em teoria, seria explicitamente
.close()
a conexão (para liberar uma conexão no banco de dados sem esperar que o Python destrua o objeto). Este código mais robusto tem a seguinte aparência:import contextlib import pymysql with contextlib.closing(pymysql.connect()) as conn: with conn as cursor: cursor.execute('SELECT 1')
Isso é feio, mas não depende do Python destruindo seus objetos para liberar suas (número finito disponível de) conexões de banco de dados.
Observe que fechar o cursor , se já estiver fechando a conexão explicitamente assim, é totalmente inútil.
Finalmente, para responder às perguntas secundárias aqui:
Não, instanciar um cursor não atinge o MySQL e basicamente não faz nada .
Isso é situacional e difícil de dar uma resposta geral. Como https://dev.mysql.com/doc/refman/en/optimizing-innodb-transaction-management.html coloca, "um aplicativo pode encontrar problemas de desempenho se comprometer milhares de vezes por segundo, e diferentes problemas de desempenho se ele comete apenas a cada 2-3 horas " . Você paga uma sobrecarga de desempenho para cada confirmação, mas ao deixar as transações abertas por mais tempo, você aumenta a chance de outras conexões terem que perder tempo esperando por bloqueios, aumenta o risco de deadlocks e, potencialmente, aumenta o custo de algumas pesquisas realizadas por outras conexões .
1 MySQL faz tem uma construção que chama de um cursor , mas eles só existem procedimentos dentro armazenados; eles são completamente diferentes dos cursores PyMySQL e não são relevantes aqui.
fonte
Acho que você ficará melhor tentando usar um cursor para todas as suas execuções e fechá-lo no final do código. É mais fácil de trabalhar e pode ter benefícios de eficiência também (não me diga sobre isso).
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()
A questão é que você pode armazenar os resultados da execução de um cursor em outra variável, liberando assim o cursor para fazer uma segunda execução. Você terá problemas dessa forma apenas se estiver usando fetchone () e precisar fazer uma segunda execução do cursor antes de iterar todos os resultados da primeira consulta.
Caso contrário, eu diria apenas fechar seus cursores assim que terminar de obter todos os dados deles. Dessa forma, você não precisa se preocupar em amarrar pontas soltas mais tarde em seu código.
fonte
Eu sugiro fazer isso como php e mysql. Comece i no início do seu código antes de imprimir os primeiros dados. Portanto, se você receber um erro de conexão, poderá exibir uma
50x
mensagem de erro (Não me lembro qual é o erro interno). E mantenha-o aberto durante toda a sessão e feche-o quando souber que não precisará mais dele.fonte