MySQLdb를 사용하여 커서를 닫는 경우
WSGI 웹 앱을 만들고 있는데 MySQL 데이터베이스가 있습니다. 문을 실행하고 결과를 얻기위한 커서를 제공하는 MySQLdb를 사용하고 있습니다. 커서를 가져오고 닫는 표준 방법은 무엇입니까? 특히 커서는 얼마나 오래 지속되어야합니까? 각 트랜잭션에 대해 새 커서를 가져와야합니까?
커밋하기 전에 커서를 닫아야한다고 생각합니다. 중간 커밋이 필요하지 않은 트랜잭션 집합을 찾아서 각 트랜잭션에 대해 새 커서를 가져올 필요가없는 중요한 이점이 있습니까? 새 커서를 가져 오는 데 많은 오버 헤드가 있습니까, 아니면 그다지 큰 문제가 아닙니까?
종종 불분명하고 주관적이기 때문에 표준 관행이 무엇인지 묻는 대신 모듈 자체에서 지침을 찾아 볼 수 있습니다. 일반적 with
으로 다른 사용자가 제안한 키워드를 사용하는 것은 좋은 생각이지만 이러한 특정 상황에서는 기대하는 기능을 충분히 제공하지 못할 수 있습니다.
모듈 버전 1.2.5 부터 다음 코드 ( github )로 컨텍스트 관리자 프로토콜 을 MySQLdb.Connection
구현합니다 .
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
이미 몇 가지 기존 Q & A가 있거나 Python의 "with"문 이해 를 읽을 수 있지만, 본질적으로 일어나는 일은 블록 __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'
stdout에 "커서가 열려 있고 연결이 열려 있습니다."라는 출력이 표시되어야합니다.
커밋하기 전에 커서를 닫아야한다고 생각합니다.
왜? MySQL의 C API 의 기초가, MySQLdb
어떤 커서 객체를 구현하지 않습니다, 모듈 문서에 묵시적으로 : "MySQL은 커서를 지원하지 않습니다하지만, 커서 쉽게 에뮬레이트합니다." 실제로 MySQLdb.cursors.BaseCursor
클래스는 object
커밋 / 롤백과 관련하여 커서 에서 직접 상속 되며 커서에 이러한 제한을 적용하지 않습니다. 오라클 개발자 는 다음과 같이 말했습니다 .
cur.close () 전에 cnx.commit ()은 나에게 가장 논리적으로 들립니다. "더 이상 필요하지 않으면 커서를 닫으십시오."라는 규칙을 따를 수 있습니다. 따라서 커서를 닫기 전에 commit (). 결국 Connector / Python의 경우 큰 차이는 없지만 다른 데이터베이스는 그럴 수 있습니다.
이 주제에 대한 "표준 연습"에 도달하는 것만큼이나 비슷할 것으로 예상합니다.
중간 커밋이 필요하지 않은 트랜잭션 집합을 찾아서 각 트랜잭션에 대해 새 커서를 가져올 필요가없는 중요한 이점이 있습니까?
나는 그것을 매우 의심하며 그렇게하려고 노력할 때 추가적인 인적 오류가 발생할 수 있습니다. 컨벤션을 결정하고 그것에 충실하는 것이 좋습니다.
새 커서를 가져 오는 데 많은 오버 헤드가 있습니까, 아니면 그다지 큰 문제가 아닙니까?
오버 헤드는 무시할 수 있으며 데이터베이스 서버에 전혀 영향을주지 않습니다. 전적으로 MySQLdb 구현 내에 있습니다. 새 커서를 만들 때 무슨 일이 일어나는지 정말 궁금하다면 github 에서 볼BaseCursor.__init__
수 있습니다 .
논의 할 때 이전으로 돌아 가면 with
아마도 이제 MySQLdb.Connection
클래스 __enter__
와 __exit__
메서드가 모든 with
블록 에 새로운 커서 객체를 제공 하고이를 추적하거나 블록 끝에서 닫는 것을 귀찮게하지 않는 이유를 이해할 수있을 것 입니다. 상당히 가볍고 순전히 편의를 위해 존재합니다.
커서 객체를 미세 관리하는 것이 정말 중요하다면 contextlib.closing 을 사용 하여 커서 객체에 정의 된 __exit__
메서드 가 없다는 사실을 보완 할 수 있습니다 . 이를 위해 with
블록 을 종료 할 때 연결 개체를 강제로 닫는 데 사용할 수도 있습니다 . 그러면 "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'
참고 with closing(arg_obj)
인수 객체의 호출하지 않습니다 __enter__
및 __exit__
방법; 그것은 것입니다 만 인수 객체의 호출 close
의 끝에 방법을 with
차단합니다. (단순히 클래스 정의, 행동이를 보려면 Foo
와 __enter__
, __exit__
및 close
방법은 간단한 포함 print
문을, 그리고 당신이 할 때 발생하는 비교 with Foo(): pass
당신이 할 때 발생에 with closing(Foo()): pass
.)이 두 가지 중요한 의미가 있습니다 :
첫째, 자동 커밋 모드가 활성화 된 경우 MySQLdb는 블록 끝에서 트랜잭션 BEGIN
을 사용 with connection
하고 커밋하거나 롤백 할 때 서버에서 명시 적 트랜잭션을 수행합니다 . 이는 모든 DML 문을 즉시 커밋하는 MySQL의 기본 동작으로부터 사용자를 보호하기위한 MySQLdb의 기본 동작입니다. 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
'with'키워드를 사용하여 다시 작성하는 것이 좋습니다. 'With'는 커서 닫기를 자동으로 처리합니다 (관리되지 않는 리소스이므로 중요 함). 이점은 예외의 경우 커서를 닫을 것입니다.
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()
참고 :이 답변은 PyMySQL 에 대한 것입니다 . 이는 MySQLdb의 드롭 인 대체물이며 MySQLdb가 유지 관리를 중단 한 이후 사실상 MySQLdb의 최신 버전입니다. 여기에있는 모든 것이 레거시 MySQLdb 에도 해당 한다고 생각 하지만 확인하지는 않았습니다.
우선, 몇 가지 사실 :
- Python's
with
syntax calls the context manager's__enter__
method before executing the body of thewith
block, and its__exit__
method afterwards. - Connections have an
__enter__
method that does nothing besides create and return a cursor, and an__exit__
method that either commits or rolls back (depending upon whether an exception was thrown). It does not close the connection. - Cursors in PyMySQL are purely an abstraction implemented in Python; there is no equivalent concept in MySQL itself.1
- Cursors have an
__enter__
method that doesn't do anything and an__exit__
method which "closes" the cursor (which just means nulling the cursor's reference to its parent connection and throwing away any data stored on the cursor). - Cursors hold a reference to the connection that spawned them, but connections don't hold a reference to the cursors that they've created.
- Connections have a
__del__
method which closes them - Per https://docs.python.org/3/reference/datamodel.html, CPython (the default Python implementation) uses reference counting and automatically deletes an object once the number of references to it hits zero.
Putting these things together, we see that naive code like this is in theory problematic:
# Problematic code, at least in theory!
import pymysql
with pymysql.connect() as cursor:
cursor.execute('SELECT 1')
# ... happily carry on and do something unrelated
The problem is that nothing has closed the connection. Indeed, if you paste the code above into a Python shell and then run SHOW FULL PROCESSLIST
at a MySQL shell, you'll be able to see the idle connection that you created. Since MySQL's default number of connections is 151, which isn't huge, you could theoretically start running into problems if you had many processes keeping these connections open.
However, in CPython, there is a saving grace that ensures that code like my example above probably won't cause you to leave around loads of open connections. That saving grace is that as soon as cursor
goes out of scope (e.g. the function in which it was created finishes, or cursor
gets another value assigned to it), its reference count hits zero, which causes it to be deleted, dropping the connection's reference count to zero, causing the connection's __del__
method to be called which force-closes the connection. If you already pasted the code above into your Python shell, then you can now simulate this by running cursor = 'arbitrary value'
; as soon as you do this, the connection you opened will vanish from the SHOW PROCESSLIST
output.
However, relying upon this is inelegant, and theoretically might fail in Python implementations other than CPython. Cleaner, in theory, would be to explicitly .close()
the connection (to free up a connection on the database without waiting for Python to destroy the object). This more robust code looks like this:
import contextlib
import pymysql
with contextlib.closing(pymysql.connect()) as conn:
with conn as cursor:
cursor.execute('SELECT 1')
This is ugly, but doesn't rely upon Python destructing your objects to free up your (finite available number of) database connections.
Note that closing the cursor, if you're already closing the connection explicitly like this, is entirely pointless.
Finally, to answer the secondary questions here:
Is there a lot of overhead for getting new cursors, or is it just not a big deal?
Nope, instantiating a cursor doesn't hit MySQL at all and basically does nothing.
Is there any significant advantage to finding sets of transactions that don't require intermediate commits so that you don't have to get new cursors for each transaction?
This is situational and difficult to give a general answer to. As https://dev.mysql.com/doc/refman/en/optimizing-innodb-transaction-management.html puts it, "an application might encounter performance issues if it commits thousands of times per second, and different performance issues if it commits only every 2-3 hours". You pay a performance overhead for every commit, but by leaving transactions open for longer, you increase the chance of other connections having to spend time waiting for locks, increase your risk of deadlocks, and potentially increase the cost of some lookups performed by other connections.
1 MySQL does have a construct it calls a cursor but they only exist inside stored procedures; they're completely different to PyMySQL cursors and are not relevant here.
I think you'll be better off trying to use one cursor for all of your executions, and close it at the end of your code. It's easier to work with, and it might have efficiency benefits as well (don't quote me on that one).
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()
The point is that you can store the results of a cursor's execution in another variable, thereby freeing your cursor to make a second execution. You run into problems this way only if you're using fetchone(), and need to make a second cursor execution before you've iterated through all results from the first query.
Otherwise, I'd say just close your cursors as soon as you're done getting all of the data out of them. That way you don't have to worry about tying up loose ends later in your code.
I suggest to do it like php and mysql. Start i at the beginning of your code before printing of the first data. So if you get a connect error you can display a 50x
(Don't remember what internal error is) error message. And keep it open for the whole session and close it when you know you wont need it anymore.
참고URL : https://stackoverflow.com/questions/5669878/when-to-close-cursors-using-mysqldb
'code' 카테고리의 다른 글
종속성이있는 AngularJS 팩토리 단위 테스트 (0) | 2020.10.17 |
---|---|
브라우저는 JSP로 전달되는 서블릿을 호출 할 때 CSS, 이미지 및 링크와 같은 관련 리소스에 액세스하거나 찾을 수 없습니다. (0) | 2020.10.17 |
자바 메소드 호출 비용 (0) | 2020.10.17 |
JavaScript-문자열 일치에 변수 사용 (0) | 2020.10.16 |
Android의 16 진수 색상은 8 자리 숫자입니다. (0) | 2020.10.16 |