[cx_Oracle入門](第13回) コネクションプールを使用した接続(クライアントサイド)

# 連載目次
[連載：cx_Oracle入門　目次](https://qiita.com/nakaie/items/df170c3fa6895cf1b44d)

# 検証環境
- Oracle Cloud利用
- Oracle Linux 7.7 (VM.Standard2.1)
- Python 3.6
- cx_Oracle 8.0
- Oracle Database 19.5 (ATP, 1OCPU)
- Oracle Instant Client 18.5

# 概要
Oracle Databaseの標準の接続方法は、決して早くはありません。むしろ遅いと揶揄されがちです。しかし、コネクションプールの機能を利用することで、他の製品とそん色ない接続性能を出すことが可能です。そして、現在は、サーバーサイド、クライアントサイドの両方でOracle Database自身の機能としてコネクションプールの機能を提供しています。Oracle Databaseでは、Pythonに限らず、どの接続環境であっても、コネクションプールの恩恵を受けることが可能です。
サーバーサイドのコネクションプール機能(DRCP(Database Resident Connection Pool)、データベース常駐接続プーリング)はサーバーサイドの機能故どのような環境でも利用できます。サーバーサイドの設定に興味がある方は以下をご覧ください。とはいえ、DRCPの利用のためには接続時にオプションの指定が必要なので、コーディングが必要な部分は別の回で解説します。今回はクライアントサイドのコネクションプールを対象に解説を行います。

(参考)[Oracle Database の DRCP（データベース常駐接続プーリング）の MINSIZE と MAXSIZE を 1 に設定して、複数セッションから接続してみる。](https://qiita.com/ora_gonsuke777/items/67148cd793bc09afa067)


# クライアントサイドのコネクションプール
cx_OracleはOracle Clientが持つ、クライアントサイドのコネクションプールの機能を利用するためのAPIを備えています。クライアントサイドのコネクションプールを使用する場合は、まずコネクションプール(セッションプール)を作成します。そしてcx_Oracleオブジェクトのconnect()メソッドではなく、Connectionオブジェクトのacquire()メソッドを使用してプールからセッションを獲得することで、接続が完了します。

```python:sample13a.py
import cx_Oracle
import time

USERID = "admin"
PASSWORD = "FooBar"
DESTINATION = "atp1_low"

# 通常接続の速度計測
t1 = time.time()
connection = cx_Oracle.connect(USERID, PASSWORD, DESTINATION)
t2 = time.time()
connection.close()
print(f"プール未使用時接続時間 : {t2 - t1}秒")

# コネクションプール接続の速度計測
pool = cx_Oracle.SessionPool(USERID, PASSWORD, DESTINATION, min=1, max=2, increment=1)
t1 = time.time()
connection = pool.acquire()
t2 = time.time()
pool.release(connection)
print(f"プール使用時接続時間 : {t2 - t1}秒")
```

下から6行目でcx_Oracle.SessionPool()をコールしてセッションプール(コネクションプール)を作成しています。最初の3つの引数はConnection.connect()と同様です。4つ目以降の引数は作成するコネクションプールの大きさを指定しています。minは最小の接続数、maxは最大の接続数、incrementはminからmaxまでセッション数を増やす際の増分値です。3個ともサンプル上の値がデフォルト値です。もしアプリケーションがマルチスレッドを使用している場合は、thread引数にTrueを指定してください。
セッション数の増加・減少に伴うパフォーマンスのオーバーヘッドを避けるために、minとmaxの値は同じ値にして、セッション数を固定にすることが推奨されています。指定する数については、アプリケーション稼働状況により調整してください。
確保した接続を終了(解放)する際は、release()メソッドをコールします。引数にacquire()した接続(この例の場合「connection」)を指定してください。

実行結果は大人の事情で掲載できませんが、動かしてみていただければ、圧倒的にコネクションプール使用時の方が高速な接続であることがわかります。ただし、このサンプルではセッションプールの作成時間を接続時間に含めていません。もし含めるとなると、上記例の例の値ならともかく、minの値が大きくなるほど、プール作成に時間を要するので、さすがに通常接続の方が高速になります。
通常接続とコネクションプール利用の使い分けに関しては、バッチ処理のアプリケーションのような、接続回数が1回となるケースが多いアプリケーションでは通常接続を、サーバーレスアプリケーションのような何度もDBに接続することが想定されるようなアプリケーションではコネクションプールを使用するようにするのがよいと考えます。

# 異種コネクションプールと同種コネクションプール
タイトルの異種(Heterogeneous)と同種(Homogeneous)は、同一コネクションプール内で接続ユーザーをすべて同じにする(同種)か否(異種)かを表します。デフォルトは同種です。今まで説明してきた同種コネクションプールの場合は接続ユーザーは固定となりますが、これから説明する異種コネクションプールの場合はユーザーを混在させることが可能になります。柔軟性に優れる一方、都度認証しないといけないため、接続スピードは同種コネクションプールより低下します。
cx_Oracle.SessionPool()にはhomogeneousというデフォルトTrueの引数が存在します。これをFalseに変更して接続すると、セッション単位に異なるユーザーを指定することが可能になります。ユーザー名とパスワードの指定は、cx_Oracle.SessionPool()ではなく、Connection.acquire()にて行います。

```python:sample13b.py
import cx_Oracle
import time

USERID = "admin"
PASSWORD = "FooBar"
DESTINATION = "atp1_low"

# 同種コネクションプール
pool = cx_Oracle.SessionPool(USERID, PASSWORD, DESTINATION, min=2, max=2)
t1 = time.time()
connection = pool.acquire()
t2 = time.time()
pool.release(connection)
print(f"同種コネクションプール使用時接続時間 : {t2 - t1}秒")

# 異種コネクションプール
pool = cx_Oracle.SessionPool(dsn=DESTINATION, min=2, max=2, homogeneous=False)
t1 = time.time()
connection = pool.acquire(user=USERID, password=PASSWORD)
t2 = time.time()
pool.release(connection)
print(f"異種コネクションプール使用時接続時間 : {t2 - t1}秒")
```

ユーザー名、パスワード、接続先情報の引数に引数名(それぞれuser, password, dsn)が必須となる点、注意してください。