PDBへのサービス追加（非RAC構成）

# 前置き
RACやOracle Restart構成でサービスを追加（srvctl）することはあれど、
シングルインスタンスの構成でサービスを追加する機会というのはなかなかありませぬ。

コンテナDBではPDBへの接続にサービスを利用しているようなのですが、
とある案件でPDBにたくさんサービスを付けたい！みたいな話になりまして。
しかもシングルインスタンス構成で。

最初は無邪気に初期化パラメータの```service_names```パラメータに複数のサービス名を登録してみたのですが、、、

# 本題
ということで、マニュアルなどなどをぐるぐる巡回して無事に追加することができました。
その時のことを備忘ということでまとめておきたいと思います。

## サービスの作成
PDBにサービスを作成する時は、PDBに接続したうえで作成する必要があります。
SYSDBA権限を持つユーザでやりましょう。

> DBMS_SERVICEパッケージには、特定のセキュリティ要件があります。
> 
> **権限** 
> このパッケージを使用するクライアントは、ALTER SYSTEM実行権限、およびV$SESSION表読込み権限を持っている必要があります。
> 
> **スキーマ**
> このパッケージは、SYSスキーマの下に作成する必要があります。
> 
> **ロール**
> このパッケージのEXECUTE権限は、DBAロールにのみ付与されます。

以下のプロシージャでサービスを作成します（複数可）
最低限、以下の2つで作成は可能です。欲張りさんはDBMS_SERVICEのマニュアル参照ください。
| パラメータ | 説明 |
|:--|:--|
| service_name  | サービス名を指定。<br> データディクショナリ用。64byteまで。  |
| network_name  | リスナーが認識する名前。<br> tnsnames.ora の SERVICE_NAME などでも使用する。 |

```sql:DBMS_SERVICE.CREATE_SERVICE
BEGIN
  DBMS_SERVICE.CREATE_SERVICE(
    service_name => 'new_service',
    network_name => 'new_service');
  END;
/
```
上記のプロシージャを複数回実行することで、1つのPDBに対して複数のサービスを紐付けることができます。ただ、それだけでは新しいサービスは使えないので、以下のコマンドでサービスを開始する必要があります。

```sql:DBMS_SERVICE.START_SERVICE
BEGIN
  DBMS_SERVICE.START_SERVICE(
    service_name => 'new_service');
  END;
/
```
これで新しいサービスが利用できるようになります。

```sql
SQL>  select NAME, NETWORK_NAME, PDB from v$services order by 1;

NAME                                     NETWORK_NAME                   PDB
---------------------------------------- ------------------------------ ----------
new_service                              new_service                    PDB
pdb                                      pdb                            PDB
```

最後におまじないをかけておくと、少し早く使えるようになります。

```sql
alter system register;
```

```shell-session
$ lsnrctl status
...（略）...
サービス"new_service.local"には、1件のインスタンスがあります。
  インスタンス"cdb"、状態READYには、このサービスに対する1件のハンドラがあります...
```

:::note warn
DBにドメイン設定（db_domainパラメータ）がある場合は、サービス名もドメイン付きのFQDNで指定する必要があります。ドメインなしで接続した場合は「ORA-12514: TNS: リスナーは接続奇術師でリクエストされたサービスを現在認識していません」が表示され接続できません。
:::

```shell-session
$ sqlplus system@localhost.local:1521/new_service.local
...（略）...
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.20.0.0.0
に接続されました。
SQL> show con_name

CON_NAME
------------------------------
PDB
```

以上でサービスの登録と開始の手順はおしまいです。
削除、変更なども同じプロシージャでできるのですが、詳細はDBMS_SERVICEプロシージャのマニュアル参照ください。


## サービス系の情報表示
以下あたりを使うことでサービスの状態が分かります。
リスナーとかも見ておきたいですね。

### ディクショナリ表
列名まで同じですが、v\\$servicesとall_servicesで登録されている情報が異なります。
細かい動作までは検証できていませんが、v$services には開始しているサービスが表示されており、all_servicesには```DBMS_SERVICE.CREATE_SERVICE```で登録したサービス、```alter system set service_names```で登録したサービスなどが登録されている様子。
他にも登録される条件（例えば、```srvctl add service```で登録したサービス等）はあると思います。

例えば、先ほどの DBMS_SERVICE.CREATE_SERVICEのみを実行し、START_SERVICEを実行していない場合は以下のような表示になります（開始していないのでリスナーも認識しません）

```sql:v$services
select NAME, NETWORK_NAME, PDB from v$services order by 1;
```
```:（CDBで実行した結果）
NAME                                     NETWORK_NAME                   PDB
---------------------------------------- ------------------------------ ----------
SYS$BACKGROUND                                                          CDB$ROOT
SYS$USERS                                                               CDB$ROOT
cdb.local                                cdb.local                      CDB$ROOT
cdbXDB                                   cdbXDB                         CDB$ROOT
pdb                                      pdb                            PDB
```
```:（PDBで実行した結果）
NAME                                     NETWORK_NAME                   PDB
---------------------------------------- ------------------------------ ----------
pdb                                      pdb                            PDB
```
※開始されているサービスであれば、CDBで実行することですべてのPDBのサービスを確認できますね。

all_services表の場合はCDB, PDBそれぞれ、自分のサービスしか認識していない。
```sql:all_services
select NAME, NETWORK_NAME, PDB from all_services order by 1;
```
```
NAME                                     NETWORK_NAME                   PDB
---------------------------------------- ------------------------------ ----------
SYS$BACKGROUND                                                          CDB$ROOT
SYS$USERS                                                               CDB$ROOT
cdb.local                                cdb.local                      CDB$ROOT
cdbXDB                                   cdbXDB                         CDB$ROOT
```
```:（PDBで実行した結果）
NAME                                     NETWORK_NAME                   PDB
---------------------------------------- ------------------------------ ----------
PDB                                      PDB                            PDB
new_service                              new_service                    PDB
```


### 初期化パラメータ
:::note warn
初期化パラメータ SERVICE_NAMES は19cから非推奨パラメータとなっています。
また、PDB個別には設定できないパラメータです。つまり、CDBに接続するためのサービスしか作成することができないため、（CDBに接続するのはたいていが管理者や保守員だろうから）個人的には使わない方針がよいかと思います。

なお、service_names を変更するたびに、ディクショナリ表に履歴という名のゴミが蓄積されていきます。何気にこの理由も重要です（メンテが大変）
:::

```sql:初期化パラメータ（SERVICE_NAMES）
select NAME, VALUE from v$parameter
 where NAME = 'service_names';
```
```
NAME                                     VALUE
---------------------------------------- ----------------------------------------
service_names                            cdb.local
```

最終的にはリスナー経由で接続することになるので、リスナー登録されているかは重要。

:::note info
19c からOSの環境変数 ORACLE_PDB_SID を設定することで Beq 接続がPDBでもできるようになりましたが、今回の趣旨からは逸れてしまうので割愛してます。
:::

### リスナーステータス
```shell-session:lsnrctl status <リスナー名>
$ lsnrctl status LISTENER
```
```
...（略）...
サービスのサマリー...
サービス"06cb96ed80151311e065322c6b579ae4.local"には、1件のインスタンスがあります。
  インスタンス"cdb"、状態READYには、このサービスに対する1件のハンドラがあります...
サービス"06cc0df247892328e065322c6b579ae4.local"には、1件のインスタンスがあります。
  インスタンス"cdb"、状態READYには、このサービスに対する1件のハンドラがあります...
サービス"cdb.local"には、1件のインスタンスがあります。
  インスタンス"cdb"、状態READYには、このサービスに対する1件のハンドラがあります...
サービス"cdbXDB.local"には、1件のインスタンスがあります。
  インスタンス"cdb"、状態READYには、このサービスに対する1件のハンドラがあります...
サービス"pdb.local"には、1件のインスタンスがあります。
  インスタンス"cdb"、状態READYには、このサービスに対する1件のハンドラがあります...
コマンドは正常に終了しました。
```

「登録されてないな？」と思っ時に使いがちな魔法の言葉
```sql
alter system register;
```