93

私は WSGI Web アプリを構築しており、MySQL データベースを持っています。ステートメントを実行して結果を取得するためのカーソルを提供する MySQLdb を使用しています。カーソルを取得して閉じるための標準的な方法は何ですか? 特に、カーソルの持続時間はどのくらいですか? トランザクションごとに新しいカーソルを取得する必要がありますか?

接続をコミットする前にカーソルを閉じる必要があると思います。トランザクションごとに新しいカーソルを取得する必要がないように、中間コミットを必要としない一連のトランザクションを見つけることに大きな利点はありますか? 新しいカーソルを取得するために多くのオーバーヘッドがありますか?それとも大したことではないのでしょうか?

4

5 に答える 5

86

多くの場合、それは不明確で主観的であるため、標準的な方法を尋ねる代わりに、モジュール自体にガイダンスを求めてみてください。一般に、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()

すでにいくつかの既存の Q&A があります。または、 Python の "with" ステートメントを理解withするを読むこともできますが、本質的に何が起こるかは、ブロックの開始時に実行され、ブロックを離れるときに実行されます。後でそのオブジェクトを参照する場合は、オプションの構文を使用して、返されたオブジェクトを名前にバインド できます。したがって、上記の実装を考えると、データベースにクエリを実行する簡単な方法は次のとおりです。__enter__with__exit__withwith 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__withwith

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'

「cursor is open; connection is open」という出力が stdout に出力されるはずです。

接続をコミットする前にカーソルを閉じる必要があると思います。

なんで?の基礎であるMySQL C APIMySQLdbは、モジュールのドキュメントに示されているように、カーソル オブジェクトを実装していません。「MySQL はカーソルをサポートしていません。ただし、カーソルは簡単にエミュレートできます。実際、MySQLdb.cursors.BaseCursorクラスは直接継承しobject、コミット/ロールバックに関してカーソルにそのような制限を課しません。Oracle の開発者は次のように述べています

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__メソッドを呼び出さないことに注意してください。ブロックの最後で引数オブジェクトのメソッドを呼び出すだけです。(これを実際に見るには、単純なステートメントを含む、、およびメソッドでクラスを定義し、 を実行したときに何が起こるかを比較します。) これには 2 つの重要な意味があります。closewithFoo__enter____exit__closeprintwith Foo(): passwith closing(Foo()): pass

まず、自動コミット モードが有効になっている場合、ブロックの最後でトランザクションを使用してコミットまたはロールバックすると、MySQLdb はBEGINサーバー上で明示的なトランザクションを実行します。with connectionこれらは MySQLdb のデフォルトの動作であり、すべての DML ステートメントを即座にコミットするという MySQL のデフォルトの動作からユーザーを保護することを目的としています。MySQLdb は、コンテキスト マネージャを使用する場合にトランザクションが必要であると想定し、明示的に使用BEGINしてサーバーの自動コミット設定をバイパスします。の使用に慣れている場合with connectionは、実際にはバイパスされているだけなのに、自動コミットが無効になっていると思うかもしれません。追加すると、不快な驚きが生じる可能性がありますclosingコードに影響を与え、トランザクションの整合性を失います。変更をロールバックできなくなり、並行性のバグが見られるようになり、その理由がすぐにはわからない可能性があります。

次に、新しいカーソル オブジェクトを にバインドする とは対照的にwith closing(MySQLdb.connect(user, pass)) as VAR接続オブジェクトをにバインドします。後者の場合、接続オブジェクトに直接アクセスすることはできません! 代わりに、元の接続へのプロキシ アクセスを提供するカーソルの属性を使用する必要があります。カーソルが閉じると、その属性は に設定されます。これにより、次のいずれかが発生するまで接続が放棄されたままになります。VARwith MySQLdb.connect(user, pass) as VARVARconnectionconnectionNone

  • カーソルへのすべての参照が削除されます
  • カーソルが範囲外になる
  • 接続がタイムアウトしました
  • 接続は、サーバー管理ツールを介して手動で閉じられます

次の行を 1 行ずつ実行しながら、(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
于 2014-03-24T19:26:39.473 に答える
5

すべての実行に1つのカーソルを使用し、コードの最後でカーソルを閉じる方がよいと思います。作業が簡単で、効率のメリットもあるかもしれません(それについては引用しないでください)。

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()

重要なのは、カーソルの実行結果を別の変数に格納して、カーソルを解放して2回目の実行を行うことができるということです。この方法で問題が発生するのは、fetchone()を使用している場合のみであり、最初のクエリのすべての結果を繰り返す前に、2回目のカーソル実行を行う必要があります。

それ以外の場合は、すべてのデータの取得が完了したらすぐにカーソルを閉じてください。そうすれば、コードの後半でルーズエンドを拘束することを心配する必要がありません。

于 2011-07-30T19:06:12.787 に答える
-6

phpやmysqlのようにすることをお勧めします。最初のデータを印刷する前に、コードの最初からiを開始します。したがって、接続エラーが発生した場合は、50x(内部エラーが何であるかを覚えていない)エラーメッセージを表示できます。また、セッション全体を通して開いたままにし、不要になったことがわかったら閉じます。

于 2011-04-14T21:40:40.297 に答える