47324


[python bottle+sqlite3で複数ユーザの更新を受け付ける方法](https://ja.stackoverflow.com/q/47249)
の質問をした時に、下記のコメントをいただきました。

> answer 関数や init_table 関数で with sqlite3.connect(db_name) as ...
> としていますけれども、ここは context manager を使って(from contextlib import closing)、with
> closing(sqlite3.connect(db_name)) as ... とすべきではないでしょうか

Python 3.xからSQLite3を呼び出した後に **クローズ処理は必ず行うべき** ものでしょうか。  
そしてクローズ処理を行う場合、どのコードが推奨されるでしょうか。

  1. [contextlib.closing](https://docs.python.jp/3/library/contextlib.html)  
`with closing(sqlite3.connect('hoge.db')) as c:`

  2. [try-finally](https://docs.python.jp/3/tutorial/errors.html#defining-clean-up-actions)  
`finally:` `c.close()`

  3. withのみ(クローズ無し？)  
`with sqlite3.connect('hoge.db') as c:`

無学ゆえにコメントをいただくまで`contextlib.closing`の存在を知りませんでした。  
日本語でsqlite3のサンプルコードを見ても、closingを使わずにwithのみが書かれているものや、`finally`ブロックを使わずに正常処理として`close()`を行うもの、`close()`しないものが多く見つかります。

[本家SOの類似質問への回答](https://stackoverflow.com/questions/20793283/python-with-
statement-should-i-use-contextlib-
closing)では、下記について言及するとともに`contextlib.closing`の使用を推奨しています。

> **_However_** , the `connection.__exit__()` method doesn't close the
> connection, it commits the transaction on a successful completion, or aborts
> it when there is an exception.

**_意訳:_**  
(flaskチュートリアルの`connect_db()`は`connection manager`のようにsqlite3のconnectionを返す。)
**だかしかし**
、`connection.__exit__()`関数は接続を切断しない。これはブロックの処理を正常に実行したらコミットし、例外が発生したら破棄する。

[本家SOの別質問](https://stackoverflow.com/questions/2330344/in-python-with-sqlite-
is-it-necessary-to-close-a-cursor)では、「クローズした方が良いんじゃない？」程度に軽く推奨しているように読み取れます。  
クローズすることで利用者にはどのようなメリットがあるのでしょうか。

なお、withを使いclosingを使わない下記のスクリプトを並列処理しても、ロックはかからずに正常終了しました。  
下記は複数ユーザが個別のプログラムで1つのデータベースファイルに同時アクセスする状況を再現する状況を、並列処理で **強引に** 再現しようとしています。

[code]

    import sqlite3
    from contextlib import closing
    from multiprocessing import Pool
    
    #DBがなければ作る
    def get_db():
        p = 'quiz.db'
        conn = sqlite3.connect(p)
        init_answer(conn)
        return conn
    
    def init_answer(conn):
        if has_table(conn, 'answer'):
            return
        conn.execute(u"""
    create table answer (
        auid varchar(50) primary key,
        avalue integer
    );
    """
    )
    
    def has_table(conn, name):
        q = conn.execute("select count(*) from sqlite_master where type='table' and name='{0}'".format(name))
        c = q.fetchone()[0]
        return c != 0
    
    def replace_row(i):
        # closingの有無にかかわらず、同時実行に正常終了する
        with sqlite3.connect('quiz.db') as conn:
        #with closing(sqlite3.connect('quiz.db')) as conn:
            conn.execute("replace into answer values(?, ?)", ('fuga', i))
    
    if __name__ == '__main__':
        conn = get_db()
        conn.close()
        #同時アクセスのために効率の悪い並列処理を行っています。このコードを再利用する際にはベストアンサーもご参照願います。
        p = Pool(4)
        p.map(replace_row, list(range(100)))
        p.close()
    
[/code]


CC BY-SA 4.0
2018-08-07T07:28:28.797
47318
2018-08-08T00:57:50.223
2018-08-08T00:57:50.223
3060
9820
question
python python3 sqlite
PythonでSQLiteを使う時にクローズ処理は行うべき？


Pythonのドキュメントで
[sqlite3.connect()](https://docs.python.org/ja/3.7/library/sqlite3.html#sqlite3.connect)について以下のようにように説明されています。

> sqlite3.connect(database[, timeout, detect_types, isolation_level,
> check_same_thread, factory, cached_statements, uri])  
>  （中略）  
>  データベースが複数の接続からアクセスされている状況で、その内の一つがデータベースに変更を加えたとき、SQLite
> データベースはそのトランザクションがコミットされるまでロックされます。timeout
> パラメータで、例外を送出するまで接続がロックが解除されるのをどれだけ待つかを決めます。デフォルトは 5.0 (5秒) です。

したがって、接続を閉じずにどんどん作っていってもすぐに問題が発生することはなく、メモリーを無駄に消費してしまうという問題だけです。

今回のサンプルプログラムでは、100個の接続を作って終了してしまうプログラムなので問題が発生することはないと思われます。しかしながら、メモリーを浪費することはいいことでなく、特にエンタープライ系の場合はこういうことがバグの要因になりやすいので厳しい意見になります。

さらに言えば、毎回接続を作ること自体が無駄です。今回のサンプルプログラムであれば、変更を加えることができる接続は1つだけなので並列処理を使わずに単純に次のようにした方が処理が速いはずです。

[code]

    def replace_row(i):
        with conn:
            conn.execute("replace into answer values(?, ?)", ('fuga', i))
    
    if __name__ == '__main__':
        conn = get_db()
        p.map(replace_row, list(range(100)))
        p.close()
    
[/code]

毎回データベースに新たに接続して閉じるという無駄を避けるため、マルチスレッドのアプリケーションでは、`SQLAlchemy`の`Connection
Pooling`がよく使われます。また、Webでは、`bottle`であれば、`bottle-sqlalchemy`、`flask`であれば、`flask-
sqlalchemy`という専用のパッケージを使います。

最後になりましたが、接続を閉じる処理に関しては、`contextlib.closing`と`try-
finally`は同じことです。`contextlib.closing`の方が短く書けるのでそちらを使えばいいのではないですか。また、`withのみ`では、`commit`をするだけで接続は閉じません。


CC BY-SA 4.0
2018-08-07T11:35:44.810
47324
2018-08-07T21:20:09.197
2018-08-07T21:20:09.197
15171
15171
47318
answer