[解決済み] MySQLdbでカーソルを閉じるタイミング
質問
WSGI ウェブアプリを構築しており、MySQL データベースを使用しています。ステートメントを実行し、結果を得るためのカーソルを提供する MySQLdb を使用しています。 カーソルの取得と終了のための標準的なプラクティスは何ですか? 特に、カーソルはどのくらいの期間持続させるべきですか?各トランザクションのために新しいカーソルを取得すべきですか?
接続をコミットする前にカーソルをクローズする必要があると思います。トランザクションごとに新しいカーソルを取得する必要がないように、中間コミットを必要としないトランザクションのセットを見つけることに何か大きな利点がありますか。新しいカーソルを取得するための多くのオーバーヘッドがありますか、それとも大したことではありませんか?
どのように解決するのですか?
標準的な方法は何かと尋ねる代わりに、それはしばしば不明瞭で主観的なので、ガイダンスのためにモジュール自体に目を向けてみるのもよいでしょう。一般的には
with
キーワードを使用することは素晴らしいアイデアですが、この特定の状況では、あなたが期待する機能性を全く与えないかもしれません。
モジュールのバージョン 1.2.5 の時点では
MySQLdb.Connection
を実装しています。
コンテキストマネージャプロトコル
を次のコードで実装しています (
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()
について、いくつかの既存の Q&A があります。
with
を読むか、あるいは
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に"cursor is open; connection is open"という出力が出力されるのが確認できるはずです。
接続をコミットする前にカーソルをクローズする必要があると思います。
なぜですか?それは
MySQL C API
の基礎である
MySQLdb
の基礎である MySQL C API は、モジュール ドキュメントで暗示されているように、カーソル オブジェクトを実装していません。
MySQL はカーソルをサポートしませんが、カーソルは簡単にエミュレートできます。
実際
MySQLdb.cursors.BaseCursor
クラスを直接継承しています。
object
を継承し、コミット/ロールバックに関してカーソルにそのような制限を課していません。ある Oracle 開発者は
は次のように言っています。
:
cur.close()の前にcnx.commit()を実行するのが最も論理的に聞こえますが、どうでしょうか? もしかしたら カーソルが不要になったら、カーソルを閉じる。 したがって、カーソルを閉じる前にcommit()します。結局のところ Connector/Pythonでは、それは大きな違いはありませんが、他のデータベースでは、それは可能かもしれません。 データベースではそうかもしれません。
この件に関しては、quot;標準的な実践に近いと期待しています。
<ブロッククオートトランザクションごとに新しいカーソルを取得する必要がないように、中間コミットを必要としないトランザクションのセットを見つけることに何か大きな利点がありますか。
そうすることで、さらなるヒューマンエラーが発生する可能性があります。そうしようとすると、さらなるヒューマンエラーが発生する可能性があります。
新しいカーソルを取得するために多くのオーバーヘッドがあるのでしょうか。
オーバーヘッドはごくわずかで、データベースサーバには全く触れていません。これは完全に MySQLdb の実装の範囲内です。あなたは
を見る
BaseCursor.__init__
github
をご覧ください。新しいカーソルを作成するときに何が起こっているのか、本当に知りたければ。
以前、私たちが
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
.) これには2つの重要な意味があります。
まず、自動コミットモードが有効な場合、MySQLdb は
BEGIN
を使用すると、サーバ上で明示的なトランザクションが発生します。
with connection
を使用し、ブロックの最後でトランザクションをコミットまたはロールバックします。これらは MySQLdb のデフォルトの動作で、あらゆる DML ステートメントを直ちにコミットするという MySQL のデフォルトの動作からユーザーを保護することを目的としています。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
関連
-
[解決済み] 関数内でグローバル変数を使用する
-
[解決済み] MySQLでコマンドラインを使用してSQLファイルをインポートするにはどうすればよいですか?
-
[解決済み] モジュールの関数名(文字列)を使って、モジュールの関数を呼び出す。
-
[解決済み] MySQLでコマンドラインを使用してユーザーアカウントのリストを取得するにはどうすればよいですか?
-
[解決済み] PythonのMySQLdbモジュールをpipでインストールする方法は?
-
[解決済み】forループを使った辞書の反復処理
-
[解決済み] Djangoで2つの日付の間を選択する
-
[解決済み] 古いバージョンのPythonにおける辞書のキーの並び順
-
[解決済み] あるメソッドが複数の引数のうち1つの引数で呼び出されたことを保証する
-
[解決済み] データクラスとtyping.NamedTupleの主な使用例
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] SQLAlchemy: セッションの作成と再利用
-
[解決済み] Pythonです。未束縛のメソッドを束縛する?
-
[解決済み] 古いバージョンのPythonにおける辞書のキーの並び順
-
[解決済み] PyMongoで.sortを使用する
-
[解決済み] Cythonのコードを含むPythonパッケージはどのように構成すればよいのでしょうか?
-
[解決済み] Flaskで非同期タスクを作る
-
[解決済み] virtualenv の `--no-site-packages` オプションを元に戻す。
-
[解決済み] Pythonの検索パスを他のソースに展開する
-
[解決済み] Python 言語を決定するには?
-
[解決済み] Pythonでリストが空かどうかをチェックする方法は?重複