はじめに
深夜の残業中、外部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設定、アプリケーションログなどを保存できます。慣れ親しんだすべての文字列関数(SUBSTR
、INSTR
、LIKE
、正規表現)が直接使用でき、開発効率が大幅に向上します。
パフォーマンスの優位性も重要です。実測によると、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モードの利点はさらに明白です。LIKE
、REGEXP_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) |
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ロケータメカニズムと比較すると、行チェーンのオーバーヘッドははるかに小さいです。
インデックスの制限は依然として存在します。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データベースに新しい活力を与えましょう!