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

# 連載目次
[連載：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.6 (DBCS HP, 2OCPU)
- Oracle Instant Client 18.5

# 概要
[前回](https://qiita.com/nakaie/items/e83f371465813639fb81)はcx_Oracle自身の持つクライアントサイドのコネクションプール(セッションプール)について解説しましたが、今回はサーバーサイドのコネクションプール機能であるDRCP(Database Resident Connection Pool、データベース常駐接続プーリング)を使用した接続を採り上げます。サーバーサイドと言いつつ、オプションを指定するだけとはいえ、cx_Oracle側でもコーディングが必要です。なお、DRCPとクライアントサイドのコネクションプールは併用が可能です。

# 準備：Oracle Database側
DRCPの操作はDBMS_CONNECTION_POOLというPL/SQLユーティリティ・パッケージを使用します。以下SQL*Plusでの利用例です。マルチテナント構成の場合は、コンテナ・データベースに接続して実施する必要があります。

■DRCPの設定
`execute dbms_connection_pool.configure_pool(minsize=>5, maxsize=>5)`

■DRCPの起動
`execute dbms_connection_pool.start_pool`

■DRCPの起動
`execute dbms_connection_pool.stop_pool`

■DRCPの定義内容の確認
ディクショナリDBA_CPOOL_INFOを検索してください。

■DRCPの稼働状況の確認
動的パフォーマンスビューV\$CPOOL_STATSを検索してください。他にもV\$CPOOL_CC_STATS、V\$CPOOL_CONN_INFOといったビューが存在します。

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

# 準備：Oracle Client側
EZCONNECTを利用する場合は事前の準備は不要です。tnsnames.oraを使用する方は、事前に、DRCPを利用する形の設定を指定する必要があります。既存の接続定義への設定追加でも、新規の接続設定追加でも、どちらでも構いません。追加すべき設定は、以下のサンプルの「(SERVER=POOLED)」の部分が該当します。既に「SERVER=DEDICATED」や「SERVER=SHARED」の設定が記述されている場合は置き換える必要があります。

```tnsnames.ora
PDB1 =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = 10.0.1.2)(PORT = 1521))
    (CONNECT_DATA =
      (SERVICE_NAME = pdb1.sub04070214581.vcn0.oraclevcn.com)
      (SERVER=POOLED)
    )
  )
```

# PythonアプリケーションのDRCP接続
まずはサンプルを見てみましょう。

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

USERID = "test"
PASSWORD = "FooBar"
DESTINATION = "10.0.1.2/pdb1.sub04070214581.vcn0.oraclevcn.com:pooled" # 1.

# DRCPのみ利用
t1 = time.time()
connection = cx_Oracle.connect(USERID, PASSWORD, DESTINATION,
        cclass="MYCLASS", purity=cx_Oracle.ATTR_PURITY_SELF) # 2.
t2 = time.time()
connection.close()
print(f"DRCP利用時接続時間 : {t2 - t1}秒")

# DRCPとクライアントサイドのコネクションプールを併用
pool = cx_Oracle.SessionPool(USERID, PASSWORD, DESTINATION, min=2, max=2)
t1 = time.time()
connection = pool.acquire(cclass="MYCLASS", purity=cx_Oracle.ATTR_PURITY_SELF) # 3.
t2 = time.time()
pool.release(connection)
print(f"両コネクションプール併用時接続時間 : {t2 - t1}秒")
```

本サンプルではまだ解説していないEZCONNECTを使用しています。EZCONNECT利用時にDRCP接続を行うには、コメント1.の部分のように、最後に「:pooled」の設定を追加する必要があります。tnsnames.ora利用時は、SERVER=POOLEDを設定している接続子を指定してください。しかし、cx_Oracleでは、SQL*Plusなどとは異なり、接続設定でDRCPを利用する指定を行っただけでは実際にはDRCPは利用されず、通常接続となります。

cx_OracleでDRCPを利用するためには、コメント2.の部分のように、接続コマンド実施の際にcclass引数とpurity引数の設定が必要となります。
cclass引数は接続クラス名を指定します。接続クラスとは接続に付与する論理名のようなものです。1024バイトまでの文字列が指定可能です。複数の接続で接続クラスを分けると、異なる接続クラスとは該当のコネクションプールが共有されないことが保証されます。一例としては、異なるDBユーザー、異なるアプリケーションなどで異なる接続クラスを指定することで、セッションのより効率的な共有が可能になります。
purity引数はセッションの再利用に関する動きを指定します。以下の値から選択して指定します。

| 値 | 説明 |
|:-----------------|:------------------|
|  cx_Oracle.ATTR_PURITY_NEW | クライアントサイドのコネクションプールを利用していない場合のデフォルト値。接続要求の都度新しい接続を利用する |
|  cx_Oracle.ATTR_PURITY_SELF | クライアントサイドのコネクションプールを利用している場合のデフォルト値。古い空き接続があれば再利用する |
|  cx_Oracle.ATTR_PURITY_DEFAULT | 引数無指定時のデフォルト。上記条件により、cx_Oracle.ATTR_PURITY_NEWもしくはcx_Oracle.ATTR_PURITY_SELFのいずれかになる |

最後に、両サイドのコネクションプールを併用する場合、cclass引数とpurity引数は、コメント3.の部分のように、cx_Oracle.SessionPool()ではなく、Connection.acquire()にて指定する必要があります。