はじめに

深夜の残業中、外部APIから返されたJSONデータを保存するとい簡単そうな要件に取り組んでいます。コードはスラスラと書けていたのに、突然ORA-06502: PL/SQL: numeric or value errorになりました。調査した結果、連結したJSON文字列が4000バイトを超えて、VARCHAR2に入らないことが判明しました。

もっと不思議なシーンもあります:UTF8データベースでテーブルを作成する際、VARCHAR2(4000 CHAR)と書いているのに、ORA-00972: identifier is too longエラーが発生しました。原因は?マルチバイト文字セットでは、日本語1文字が3バイトを占有し、4000文字は12000バイトになり、VARCHAR2のバイト上限を大幅に超えてしまうからです。

最終的にCLOBを使わざるを得なくなり、また別の問題に直面します:WHERE DBMS_LOB.SUBSTR(log_field, 3000, 1) LIKE '%ERROR%'といったクエリでは、パフォーマンスが極端に低下し、実行に非常に時間がかかってしまいます。

核心的な課題はVARCHAR2/NVARCHAR2/RAWの4000/2000バイトの制限が、システムの可能性を大きく制約しています。CLOBは本当に唯一の解決策なのでしょうか?

いいえ!MAX_STRING_SIZE=EXTENDED は32,767バイトの威力を解放し、データベースの文字列処理能力を飛躍させます!

MAX_STRING_SIZEとは

MAX_STRING_SIZEはOracleデータベースの重要なパラメータで、文字列型の最大格納容量を決定します:

パラメータ定義:

  • STANDARD:デフォルト値。VARCHAR2/NVARCHAR2の上限は4000バイトRAWの上限は2000バイト
  • EXTENDED:拡張モード。3つとも上限が32,767バイト(32K)に引き上げられる

4000バイト制限の歴史的背景:

この制限はOracle 8i時代まで遡ることができます。当時、SQLエンジンとPL/SQLエンジンは別々に設計されており、SQL層のVARCHAR2は4000バイトに制限されていましたが、PL/SQLのVARCHAR2は32Kに達することができました。この違いは、初期のハードウェア制限とメモリ管理戦略に起因します。メモリが高価だった時代、4000バイトはすでに「大きなフィールド」と考えられていました。

EXTENDEDの核心的価値:

CLOBからの解放が最も直接的なメリットです。VARCHAR2(15000)を直接使用してJSONフラグメント、XML設定、アプリケーションログなどを保存できます。慣れ親しんだすべての文字列関数(SUBSTRINSTRLIKE、正規表現)が直接使用でき、開発効率が大幅に向上します。

パフォーマンスの優位性も重要です。実測によると、10-20KBのテキストデータの場合、VARCHAR2(32K)のフルテーブルスキャンはCLOBより3-5倍高速です。さらに重要なのは、VARCHAR2(32K)フィールドに直接B-Treeインデックスを作成できることです。一方、CLOBは関数インデックスを介してのみ間接的に実現でき、メンテナンスコストが高く効率も低いです。

マルチバイト文字セットの落とし穴を回避できることも大きな利点です。AL32UTF8文字セットでは、日本語1文字が3バイトを占有するため、元の4000バイト制限は約1333文字の日本語しか格納できないことを意味します。EXTENDED有効化後は、10000文字以上の日本語を格納でき、国際化アプリケーションの痛みを完全に解決します。

EXTENDEDの適用タイミング

EXTENDED強く推奨するシナリオ:

4000バイトを超える構造化/半構造化データの格納は最も典型的な使用例です。現代のアプリケーションはデータ交換にJSONを多用しており、詳細情報を含むJSONオブジェクトは簡単に4000バイトを超えます。VARCHAR2(32K)を使用してこれらのデータを格納すると、JSON_VALUEなどの関数で直接処理でき、CLOBの複雑さを回避できます。

頻繁にクエリ/変更される長いテキスト、例えばアプリケーションログ、エラースタック、ユーザーフィードバックなどの場合、EXTENDEDモードの利点はさらに明白です。LIKEREGEXP_LIKEを直接使用してパターンマッチングを行え、パフォーマンスはCLOBを大幅に上回ります。

長いフィールドに効率的なB-Treeインデックスを作成する必要がある場合、EXTENDEDモードはほぼ唯一の選択肢です。インデックスキー自体には依然として長さ制限(約1500バイト)がありますが、CREATE INDEX idx_json_key ON table_name(JSON_VALUE(json_column, '$.key'))のような関数インデックスを通じて正確なクエリを実現できます。

EXTENDED使用に慎重になるべき、または避けるべきシナリオ:

テキストファイル(契約書、論文)を格納する場合、CLOBは依然としてより良い選択です。このようなデータは通常非常に大きく(32Kを超える可能性がある)、頻繁な文字列操作を必要としません。

フィールドが頻繁に更新され、長さが20000バイトを超える場合は特に注意が必要です。EXTENDEDモードでは、4000バイトを超えるデータは行チェーン(Row Chaining)をトリガーし、頻繁な更新は深刻なパフォーマンス問題を引き起こします。

EXTENDED有効化のコストとトレードオフ:

ストレージのオーバーヘッドは比較的小さいです。AL32UTF8文字セットでは、EXTENDEDモードはSTANDARDモードより1-3%多くのスペースを使用する可能性があります。これは主に行チェーンメカニズムによる追加のポインタオーバーヘッドによるものです。

不可逆操作が最大のリスクです。一度MAX_STRING_SIZE=EXTENDEDを設定し、変換スクリプトを実行すると、STANDARDに戻ることはほぼ不可能です。唯一信頼できるロールバック方法は、新しいSTANDARDデータベースを作成してデータをエクスポート/インポートすることです。したがって、決定は慎重に行う必要があります!

アップグレードプロセスは大量のUNDOスペースを必要とし、所要時間はデータディクショナリのサイズとテーブル数に依存します。完全なバックアップは必須です!

実践編:19cでMAX_STRING_SIZE=EXTENDEDを設定する

前提条件(すべて必須):

まずCOMPATIBLEパラメータをチェック:

SELECT name, value FROM v$parameter WHERE name = 'compatible';
-- 19cのデフォルトは19.0.0、要件を満たす(>=12.1.0が必要)

文字セットの互換性を確認:

SELECT * FROM nls_database_parameters WHERE parameter = 'NLS_CHARACTERSET';
-- AL32UTF8またはUTF8はどちらもEXTENDEDをサポート

UNDO表領域のサイズを確認:

SELECT tablespace_name, SUM(bytes)/1024/1024/1024 AS size_gb 
FROM dba_data_files 
WHERE tablespace_name LIKE '%UNDO%' 
GROUP BY tablespace_name;
-- 最大表領域の少なくとも50%のUNDOスペースを推奨

シナリオ1:非CDBデータベースまたは個別PDB操作(19cベストプラクティス)

これは19c環境でのベストプラクティスであり、最大の柔軟性と最小の影響範囲を提供します:

-- ターゲットデータベース(非CDB)またはターゲットPDB(CDB環境)に接続
SHUTDOWN IMMEDIATE;
STARTUP UPGRADE;  -- 重要!UPGRADEモードで操作する必要があります

-- パラメータを変更(SPFILEに書き込み)
ALTER SYSTEM SET MAX_STRING_SIZE=EXTENDED SCOPE=SPFILE;

-- コア変換スクリプトを実行
@?/rdbms/admin/utl32k.sql
-- このスクリプトはデータディクショナリを変更し、所要時間はオブジェクト数に依存
-- 通常10-30分かかります

SHUTDOWN IMMEDIATE;
STARTUP;  -- 通常起動

-- 無効なオブジェクトを再コンパイル
EXEC UTL_RECOMP.recomp_serial();
-- または使用:@?/rdbms/admin/utlrp.sql

シナリオ2:CDB全体の変更(推奨されない、互換性のためのみ)

CDBレベルで設定する必要がある場合(すべてのPDBに影響):

-- CDB$ROOTに接続
SHUTDOWN IMMEDIATE;
STARTUP UPGRADE;
ALTER SYSTEM SET MAX_STRING_SIZE=EXTENDED SCOPE=SPFILE;
@?/rdbms/admin/utl32k.sql  -- ROOTで実行するとすべてのPDBに伝播

SHUTDOWN IMMEDIATE;
STARTUP;

-- 各PDBで個別に再コンパイルする必要があります
ALTER SESSION SET CONTAINER = PDB1;
@?/rdbms/admin/utlrp.sql;
-- すべてのPDBに対して繰り返す

操作後の検証:

-- パラメータが有効であることを確認
SELECT name, value FROM v$parameter WHERE name = 'max_string_size';
-- 'EXTENDED'が返されるはず

-- 無効なオブジェクトをチェック
SELECT COUNT(*) FROM dba_objects WHERE status = 'INVALID';
-- 0に近いはず

-- 大きなフィールドの作成をテスト
CREATE TABLE test_extended (
    id NUMBER,
    large_text VARCHAR2(10000 CHAR)  -- CHAR意味論を使用するとより直感的
);

移行後のアプリケーション調整:

新しい機能を活用するために既存のテーブル構造を変更:

ALTER TABLE app_log MODIFY (error_details VARCHAR2(10000 CHAR));
ALTER TABLE json_storage MODIFY (json_data VARCHAR2(20000 CHAR));

CDB vs PDB:マルチテナントにおける核心的な違いとベストプラクティス

機能 CDBレベル設定(CDB$ROOTで実行) PDBレベル設定(ターゲットPDBで実行、19c+)
スコープ すべてのPDBに強制継承(将来作成されるものも含む) 現在のPDBのみに影響
柔軟性 極めて低い(一律適用) 極めて高い(オンデマンド)
操作影響範囲 CDB全体とすべてのPDB 単一PDB
停止時間の範囲 CDB全体の停止 ターゲットPDBのみ停止
推奨度(19c+) 推奨されない(レガシー互換性) 強く推奨(ベストプラクティス)
最小バージョン 12.1 12.2(18c、19c、21c、23ai)

CDB/PDB Setup MAX_STRING_SIZE Flowchart

19cマルチテナントのベストプラクティス:

常にPDBレベルでの設定を優先しましょうこれにより最大の柔軟性が得られます。異なるアプリケーションのニーズに基づいて、一部のPDBでEXTENDEDモード(JSONを格納するアプリ用)を使用し、他のPDBでSTANDARDモード(従来のERPシステム)を維持できます。

PDBレベルの操作もより安全で、ターゲットPDBのみ短時間の停止が必要で、同じCDB内の他のPDBには影響しません。これは本番環境では特に重要です。

本質を理解する:EXTENDEDはどのように4000バイトの壁を突破するのか?

データディクショナリ革命が鍵です。utl32k.sqlスクリプトの中核タスクは、SYS.COL$などの基礎となるデータディクショナリテーブルを変更し、列長定義を格納するメタデータフィールドを拡張することです。これが操作をUPGRADEモードで実行する必要がある理由です — Oracleのコアメタデータ構造を変更する必要があるためです。

行格納メカニズムの変化:

STANDARDモードでは、VARCHAR2データ(≤4000バイト)は完全にインライン(行内)に格納され、最適なアクセスパフォーマンスを保証します。

EXTENDEDモードでは、格納戦略がよりスマートになります:

  • データ≤4000バイト:依然としてインライン格納、STANDARDモードと同じパフォーマンス
  • データ>4000バイト:自動的に行チェーンをトリガー。最初の4000バイトは元の行に格納され、残りのデータへのポインタを含みます。残りのデータは他のデータブロック(アウトオブライン)に格納されます

このメカニズムを理解することは、パフォーマンスチューニングにとって重要です。行チェーンは、超長フィールドを読み取るために複数のデータブロックにアクセスする必要があることを意味し、I/Oオーバーヘッドが増加します。しかし、CLOBのLOBロケータメカニズムと比較すると、行チェーンのオーバーヘッドははるかに小さいです。

standard vs extended storage architecture

インデックスの制限は依然として存在します。EXTENDEDを有効にしても、通常のB-Treeインデックスのキー値の合計長は依然として約1500バイトに制限されています(ブロックサイズに依存)。超長フィールドの場合、関数インデックス(SUBSTRなど)またはOracle Textインデックスを使用する必要があります。

まとめ

MAX_STRING_SIZE=EXTENDEDは、VARCHAR2の4000バイト制限に対処するためのOracleが提供する標準ソリューションです。JSON、XML、ログなどの構造化/半構造化された中長テキスト(>4KBかつ<32KB)の格納に特に適しており、パフォーマンスと使いやすさの両面でCLOBを大幅に上回ります。

重要な注意事項(再度強調):

操作前にデータベースをバックアップする必要があります!
文字セットの互換性を確認してください(AL32UTF8/UTF8)!
十分なUNDOスペースを確保してください!
これが不可逆的な操作であることを理解してください!
19c+マルチテナント環境では、常にPDBレベルの設定を堅持してください!

これは大量のテキストを格納するための万能な解決策ではありませんが、構造化/半構造化された中長テキストを処理し、開発効率とクエリパフォーマンスを追求するための強力なツールです。アプリケーションシナリオを評価し、このガイドの操作手順を参照して、安全かつ効率的に32K大フィールド機能をアンロックし、Oracleデータベースに新しい活力を与えましょう!