sql >> データベース >  >> RDS >> 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()
    

    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から直接継承します コミット/ロールバックに関してカーソルにそのような制限を課しません。 Oracle開発者

    cur.close()の前のcnx.commit()は、私にとって最も論理的に聞こえます。たぶん、「もう必要ない場合はカーソルを閉じる」というルールに従うことができます。したがって、カーソルを閉じる前にcommit()を実行します。結局のところ、Connector / Pythonの場合、大きな違いはありませんが、他のデータベースでは大きな違いがあります。

    これは、このテーマに関する「標準的な実践」に到達するのと同じくらい近いと思います。

    トランザクションごとに新しいカーソルを取得する必要がないように、中間コミットを必要としないトランザクションのセットを見つけることに大きな利点はありますか?

    私はそれを非常に疑っています、そしてそうしようとすると、あなたは追加のヒューマンエラーを導入するかもしれません。大会を決めてそれを守るほうがいいです。

    新しいカーソルを取得するためのオーバーヘッドはたくさんありますか、それとも大したことではありませんか?

    オーバーヘッドはごくわずかであり、データベースサーバーにはまったく影響しません。それは完全にMySQLdbの実装内にあります。 BaseCursor.__init__を見ることができます githubで 新しいカーソルを作成したときに何が起こっているのかを知りたい場合は、

    withについて話し合っていた以前の話に戻ります 、おそらく今、あなたはMySQLdb.Connectionの理由を理解することができます クラス__enter__ および__exit__ メソッドは、すべてのwithでまったく新しいカーソルオブジェクトを提供します ブロックし、それを追跡したり、ブロックの最後で閉じたりしないでください。それはかなり軽量で、純粋にあなたの便宜のために存在します。

    カーソルオブジェクトを細かく管理することが本当に重要な場合は、 contextlib.closing カーソルオブジェクトに定義された__exit__がないという事実を補うため 方法。さらに言えば、これを使用して、withを終了するときに接続オブジェクトを強制的に閉じることもできます。 ブロック。これにより、「my_cursは閉じています。my_connは閉じています」と出力されます:

    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を使用する )次の行を1つずつ実行している間:

    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
    


    1. MariaDB SUBSTR()の説明

    2. Eコマースのデータベースモデルパート1:ニュースレター

    3. PL / SQLで現在の日付を取得する方法は?

    4. MySQLIN条件制限